背景
App的開(kāi)發(fā)過(guò)程中弟疆,通常需要切換環(huán)境來(lái)進(jìn)行調(diào)試钧排。雖然我們可以通過(guò)切換App內(nèi)部域名的方式來(lái)達(dá)到切換環(huán)境的目的括勺。但是對(duì)于M頁(yè)來(lái)說(shuō),并不能影響到M頁(yè)請(qǐng)求地址潜秋。
通常的處理方案蛔琅,是手機(jī)的wifi連上電腦的代理,然后電腦配置對(duì)應(yīng)的host峻呛,然后才能進(jìn)行調(diào)試或者測(cè)試罗售。
因此這里做一個(gè)可以在app內(nèi)部進(jìn)行代理切換的功能。
配置域名
配置你需要的域名钩述,這里給出實(shí)例代碼寨躁,實(shí)際中可以通過(guò)讀取自己頁(yè)面的配置來(lái)實(shí)現(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>();
// 可以從實(shí)際的配置中取牙勘,這是只是演示
mMap.put("host.com", "testhost.com");
mMap.put("ip.com", "192.168.1.1");
}
}
}
return mMap;
}
}
上述代碼存在兩個(gè)問(wèn)題
1职恳、數(shù)據(jù)未put進(jìn)去的時(shí)候,但是mMap已經(jīng)創(chuàng)建的時(shí)候谜悟,這時(shí)get方法得到的未空话肖。
2北秽、極小的概率會(huì)造成mMap為空葡幸。說(shuō)明請(qǐng)看這篇文章http://blog.csdn.net/glory1234work2115/article/details/50814419吧。
最后這里還是把整個(gè)方法改成synchronized來(lái)解決了贺氓。最簡(jiǎn)單的解決方式了蔚叨。
okhttp的DNS攔截(可選)
因?yàn)楸救税岽u的項(xiàng)目用的是okttp,因此這里給下okhttp攔截方法辙培。將okhttp的Dns參數(shù)替換成自定義的MyDns蔑水,這樣連okhttp都會(huì)使用我們配置的dns。這個(gè)對(duì)webview是沒(méi)有影響的扬蕊,因此可不配置搀别。
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的攔截資源請(qǐng)求的方法來(lái)實(shí)現(xiàn)。
//WebviewClient 中重寫(xiě)這兩個(gè)方法尾抑,攔截資源
@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獲取資源返回資源再愈,不同擔(dān)心榜苫,這里是子線程。
攔截資源代碼如下:
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 請(qǐng)求 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)絡(luò)請(qǐng)求及圖片請(qǐng)求
if ((scheme.equalsIgnoreCase("http") || scheme.equalsIgnoreCase("https"))) {
try {
URL oldUrl = new URL(url);
URLConnection connection = oldUrl.openConnection();
// 獲取HttpDns域名解析結(jié)果 // 通過(guò)HTTPDNS獲取IP成功翎冲,進(jìn)行URL替換和HOST頭設(shè)置
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(); // 設(shè)置HTTP請(qǐng)求頭Host域
connection.setRequestProperty("Host", oldUrl.getHost());
ZLog.d("ContentType a: " + connection.getContentType());
//有可能是text/html; charset=utf-8的形式垂睬,只需要第一個(gè)
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;
}
}
然后測(cè)試,發(fā)現(xiàn)攔截成功了,域名會(huì)變成我們改的域名或者ip驹饺。
(補(bǔ)充)解決https證書(shū)錯(cuò)誤等問(wèn)題
測(cè)試環(huán)境下沒(méi)有真正的https證書(shū)钳枕,因此會(huì)產(chǎn)生異常。
這里的解決方案就是忽略證書(shū)錯(cuò)誤赏壹。
修改后的代碼:
// HttpDns解析css文件的網(wǎng)絡(luò)請(qǐng)求及圖片請(qǐng)求
if ((scheme.equalsIgnoreCase("http") || scheme.equalsIgnoreCase("https"))) {
HttpsURLConnection connection = null;
try {
URL oldUrl = new URL(url);
// 獲取HttpDns域名解析結(jié)果 // 通過(guò)HTTPDNS獲取IP成功么伯,進(jìn)行URL替換和HOST頭設(shè)置
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(); // 設(shè)置HTTP請(qǐng)求頭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;
兩個(gè)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é)
實(shí)現(xiàn)這個(gè)功能還是不難的,關(guān)鍵是要去想實(shí)現(xiàn)這個(gè)功能卡儒,多查查資料我們總能找到辦法去解決田柔。當(dāng)然,這里只是把功能的實(shí)現(xiàn)列出來(lái)骨望,要實(shí)際用起來(lái)硬爆,還需要寫(xiě)一些配置頁(yè)面,開(kāi)關(guān)的攔截等擎鸠。要做成完整的功能缀磕,還需要大家自己去實(shí)現(xiàn)了。
感悟
要肯想解決問(wèn)題劣光,才會(huì)去解決問(wèn)題袜蚕。