背景
App的開發(fā)過程中,通常需要切換環(huán)境來進行調(diào)試。雖然我們可以通過切換App內(nèi)部域名的方式來達到切換環(huán)境的目的。但是對于M頁來說,并不能影響到M頁請求地址。
通常的處理方案,是手機的wifi連上電腦的代理,然后電腦配置對應的host,然后才能進行調(diào)試或者測試。
因此這里做一個可以在app內(nèi)部進行代理切換的功能。
配置域名
配置你需要的域名,這里給出實例代碼,實際中可以通過讀取自己頁面的配置來實現(xiàn)。
public class ProxyList {
private static HashMap<String, String> mMap;
public static HashMap<String, String> getList() {
if (mMap == null) {
synchronized (ProxyList.class) {
if (mMap == null) {
mMap = new HashMap<String, String>();
// 可以從實際的配置中取,這是只是演示
mMap.put("host.com", "testhost.com");
mMap.put("ip.com", "192.168.1.1");
}
}
}
return mMap;
}
}
上述代碼存在兩個問題
1、數(shù)據(jù)未put進去的時候,但是mMap已經(jīng)創(chuàng)建的時候,這時get方法得到的未空。
2、極小的概率會造成mMap為空。說明請看這篇文章http://blog.csdn.net/glory1234work2115/article/details/50814419吧。
最后這里還是把整個方法改成synchronized來解決了。最簡單的解決方式了。
okhttp的DNS攔截(可選)
因為本人搬磚的項目用的是okttp,因此這里給下okhttp攔截方法。將okhttp的Dns參數(shù)替換成自定義的MyDns,這樣連okhttp都會使用我們配置的dns。這個對webview是沒有影響的,因此可不配置。
public class MyDns implements Dns {
@Override
public List<InetAddress> lookup(String hostname) throws UnknownHostException {
if (hostname == null) throw new UnknownHostException("hostname == null");
if (BuildConfig.RELEASE) {
return Dns.SYSTEM.lookup(hostname);
}
String ip = ProxyList.getList().get(hostname);
ZLog.d("DNS LOG: start intercept:" + hostname);
if (StringUtils.isNotEmpty(ip)){
ZLog.d("DNS LOG:" + hostname + " is replaced by ip " + ip);
return Arrays.asList(InetAddress.getAllByName(ip));
} else {
ZLog.d("DNS LOG:" + hostname + " is not replaced");
return Arrays.asList(InetAddress.getAllByName(hostname));
}
}
}
webview資源攔截
使用WebviewClient的攔截資源請求的方法來實現(xiàn)。
//WebviewClient 中重寫這兩個方法,攔截資源
@Override
public WebResourceResponse shouldInterceptRequest(WebView view, WebResourceRequest request) {
if (BuildConfig.RELEASE) {
return super.shouldInterceptRequest(view, request);
} else {
return WebviewDnsInterceptUtil.getDnsInterceptRequest(view, request);
}
}
@Override
public WebResourceResponse shouldInterceptRequest(WebView view, String url) {
if (BuildConfig.RELEASE) {
return super.shouldInterceptRequest(view, url);
} else {
return WebviewDnsInterceptUtil.getDnsInterceptUrl(view, url);
}
}
然后就是攔截資源了
1、查看是否需要替換域名
2、建立urlconnection獲取資源返回資源,不同擔心,這里是子線程。
攔截資源代碼如下:
public class WebviewDnsInterceptUtil {
@SuppressLint("NewApi")
public static WebResourceResponse getDnsInterceptRequest(WebView view, WebResourceRequest request) {
if (request != null && request.getUrl() != null && request.getMethod().equalsIgnoreCase("get")) {
return getWebResourceFromUrl(request.getUrl().toString());
}
return null;
}
public static WebResourceResponse getDnsInterceptUrl(WebView view, String url) {
if (!TextUtils.isEmpty(url) && Uri.parse(url).getScheme() != null) {
return getWebResourceFromUrl(url);
}
return null;
}
//核心攔截方法
private static WebResourceResponse getWebResourceFromUrl(String url) {
String scheme = Uri.parse(url).getScheme().trim();
ZLog.d("web log 請求 url: " + url);
String ips = ProxyList.getList().get(Uri.parse(url).getHost());
if (StringUtils.isEmpty(ips)) {
ZLog.d("web log 不攔截:" + url);
return null;
}
// HttpDns解析css文件的網(wǎng)絡請求及圖片請求
if ((scheme.equalsIgnoreCase("http") || scheme.equalsIgnoreCase("https"))) {
try {
URL oldUrl = new URL(url);
URLConnection connection = oldUrl.openConnection();
// 獲取HttpDns域名解析結(jié)果 // 通過HTTPDNS獲取IP成功,進行URL替換和HOST頭設置
ZLog.d("HttpDns ips are: " + ips + " for host: " + oldUrl.getHost());
String ip;
if (ips.contains(";")) {
ip = ips.substring(0, ips.indexOf(";"));
} else {
ip = ips;
}
String newUrl = url.replaceFirst(oldUrl.getHost(), ip);
ZLog.d("newUrl a is: " + newUrl);
connection = new URL(newUrl).openConnection(); // 設置HTTP請求頭Host域
connection.setRequestProperty("Host", oldUrl.getHost());
ZLog.d("ContentType a: " + connection.getContentType());
//有可能是text/html; charset=utf-8的形式,只需要第一個
String[] strings = connection.getContentType().split(";");
return new WebResourceResponse(strings[0], "UTF-8", connection.getInputStream());
} catch (MalformedURLException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
return null;
}
}
然后測試,發(fā)現(xiàn)攔截成功了,域名會變成我們改的域名或者ip。
(補充)解決https證書錯誤等問題
測試環(huán)境下沒有真正的https證書,因此會產(chǎn)生異常。
這里的解決方案就是忽略證書錯誤。
修改后的代碼:
// HttpDns解析css文件的網(wǎng)絡請求及圖片請求
if ((scheme.equalsIgnoreCase("http") || scheme.equalsIgnoreCase("https"))) {
HttpsURLConnection connection = null;
try {
URL oldUrl = new URL(url);
// 獲取HttpDns域名解析結(jié)果 // 通過HTTPDNS獲取IP成功,進行URL替換和HOST頭設置
ZLog.d("HttpDns ips are: " + ips + " for host: " + oldUrl.getHost());
String ip;
if (ips.contains(";")) {
ip = ips.substring(0, ips.indexOf(";"));
} else {
ip = ips;
}
String newUrl = url.replaceFirst(oldUrl.getHost(), ip);
ZLog.d("newUrl a is: " + newUrl);
connection = (HttpsURLConnection) new URL(newUrl).openConnection(); // 設置HTTP請求頭Host域
connection.setHostnameVerifier(getNullHostNameVerifier());
connection.setSSLSocketFactory(getIgnoreSSLContext().getSocketFactory());
connection.setRequestProperty("Host", oldUrl.getHost());
ZLog.d("ContentType a: " + connection.getContentType());
String encode = connection.getContentEncoding();
if (encode == null) {
encode = "UTF-8";
}
if (connection.getContentType() != null) {
String[] strings = connection.getContentType().split(";");
return new WebResourceResponse(strings[0], encode, connection.getInputStream());
} else {
return new WebResourceResponse("document", encode, connection.getInputStream());
}
} catch (Exception e) {
ZLog.d(e.toString());
return new WebResourceResponse("document", "UTF-8", null);
} finally {
if (connection != null) {
connection.disconnect();
}
}
}
return null;
兩個get方法:
private static volatile SSLContext mIgnoreSSLContext;
private static volatile HostnameVerifier mNullHostNameVerifier;
public static SSLContext getIgnoreSSLContext () {
if (mIgnoreSSLContext == null) {
synchronized (WebviewDnsInterceptUtil.class) {
if (mIgnoreSSLContext == null) {
try {
mIgnoreSSLContext = SSLContext.getInstance("TLS");
TrustManager[] trustAllCerts = new TrustManager[]{new X509TrustManager(){
public X509Certificate[] getAcceptedIssuers(){return null;}
public void checkClientTrusted(X509Certificate[] certs, String authType){}
public void checkServerTrusted(X509Certificate[] certs, String authType){}
}};
mIgnoreSSLContext.init(null, trustAllCerts, new SecureRandom());
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
return mIgnoreSSLContext;
}
public static HostnameVerifier getNullHostNameVerifier() {
if (mNullHostNameVerifier == null) {
synchronized (WebviewDnsInterceptUtil.class) {
mNullHostNameVerifier = new NullHostNameVerifier();
}
}
return mNullHostNameVerifier;
}
NullHostNameVerifier:
public class NullHostNameVerifier implements HostnameVerifier {
@Override
public boolean verify(String hostname, SSLSession session) {
return true;
}
}
總結(jié)
實現(xiàn)這個功能還是不難的,關(guān)鍵是要去想實現(xiàn)這個功能,多查查資料我們總能找到辦法去解決。當然,這里只是把功能的實現(xiàn)列出來,要實際用起來,還需要寫一些配置頁面,開關(guān)的攔截等。要做成完整的功能,還需要大家自己去實現(xiàn)了。
感悟
要肯想解決問題,才會去解決問題。