Android端WEEX_+_HTTPDNS_最佳实践
Posted 请Java和Android开发者吃点干货
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android端WEEX_+_HTTPDNS_最佳实践相关的知识,希望对你有一定的参考价值。
由于WebView
并未暴露处设置DNS的接口,因而在WebView
场景下使用HttpDns
存在很多无法限制,但如果接入WEEX
,则可以较好地植入HTTPDNS
,本文主要介绍在WEEX
场景下接入HTTPDNS
的方案细节。
在WEEX
运行时环境下,所有的逻辑最终都会转换到Native Runtime
中执行,网络请求也不例外。同时WEEX
也提供了自定义相应实现的接口,通过重写网络请求适配器,我们可以较为简单地接入HTTPDNS。在WEEX运行环境中,主要有两种网络请求:
通过Stream进行的网络请求
标签指定的加载图片的网络请求
1 Stream
网络请求 + HTTPDNS
Stream
网络请求在android端最终会通过DefaultWXHttpAdapter完成,同时WEEX
也提供了相应的接口自定义网络请求适配器。具体的逻辑如下:
第一步:创建自定义网络请求适配器,实现IWXHttpAdapter
接口
public class WXHttpdnsAdatper implements IWXHttpAdapter { @Override
public void sendRequest(final WXRequest request, final OnHttpListener listener) {
......
}
}
该接口需要实现sendRequest
方法,该方法传入一个WXRequest
对象的实例,该对象提供了以下信息:
public class WXRequest { // The request parameter
public Map<String, String> paramMap; // The request URL
public String url; // The request method
public String method; // The request body
public String body; // The request time out
public int timeoutMs = WXRequest.DEFAULT_TIMEOUT_MS; // The default timeout
public static final int DEFAULT_TIMEOUT_MS = 3000;
}
通过该对象我们可以获取到请求报头、URL、方法以及body。
第二步:在WEEX
初始化时注册自定义网络适配器,替换默认适配器:
InitConfig config=new InitConfig.Builder()
.setHttpAdapter(new WXHttpdnsAdatper()) // 注册自定义网络请求适配器
......
.build();
WXSDKEngine.initialize(this,config);
之后左右的网络请求都会通过WXHttpdnsAdatper
实现,所以只需要在WXHttpdnsAdapter
中植入HTTPDNS逻辑即可,具体逻辑可以参考如下代码:
public class WXHttpdnsAdatper implements IWXHttpAdapter { ......
private void execute(Runnable runnable){ if(mExecutorService==null){
mExecutorService = Executors.newFixedThreadPool(3);
}
mExecutorService.execute(runnable);
}
@Override public void sendRequest(final WXRequest request, final OnHttpListener listener) { if (listener != null) {
listener.onHttpStart();
} Log.e(TAG, "URL:" + request.url);
execute(new Runnable() {
@Override public void run() {
WXResponse response = new WXResponse();
WXHttpdnsAdatper.IEventReporterDelegate reporter = getEventReporterDelegate();
try { // 创建连接
HttpURLConnection connection = openConnection(request, listener);
reporter.preConnect(connection, request.body); ......
} catch (IOException |IllegalArgumentException e) { ......
}
}
});
} private HttpURLConnection openConnection(WXRequest request, OnHttpListener listener) throws IOException {
URL url = new URL(request.url); // 创建一个植入HTTPDNS的连接
HttpURLConnection connection = openHttpDnsConnection(request, request.url, listener, null); return connection;
} private HttpURLConnection openHttpDnsConnection(WXRequest request, String path, OnHttpListener listener, String reffer) {
HttpURLConnection conn = null;
URL url = null;
try {
url = new URL(path);
conn = (HttpURLConnection) url.openConnection(); // 创建一个接入httpdns的连接
HttpURLConnection tmpConn = httpDnsConnection(url, path); ......
int code = conn.getResponseCode();// Network block
Log.e(TAG, "code:" + code); // SNI场景下通常涉及重定向,重新建立新连接
if (needRedirect(code)) { Log.e(TAG, "need redirect"); String location = conn.getHeaderField("Location"); if (location == null) {
location = conn.getHeaderField("location");
} if (location != null) { if (!(location.startsWith("http://") || location
.startsWith("https://"))) { //某些时候会省略host,只返回后面的path,所以需要补全url
URL originalUrl = new URL(path);
location = originalUrl.getProtocol() + "://"
+ originalUrl.getHost() + location;
} Log.e(TAG, "code:" + code + "; location:" + location + "; path" + path); return openHttpDnsConnection(request, location, listener, path);
} else { return conn;
}
} else { // redirect finish.
Log.e(TAG, "redirect finish"); return conn;
}
} ......
return conn;
} private HttpURLConnection httpDnsConnection(URL url, String path) {
HttpURLConnection conn = null; // 通过HTTPDNS SDK接口获取IP
String ip = HttpDnsManager.getInstance().getHttpDnsService().getIpByHostAsync(url.getHost()); if (ip != null) { // 通过HTTPDNS获取IP成功,进行URL替换和HOST头设置
Log.d(TAG, "Get IP: " + ip + " for host: " + url.getHost() + " from HTTPDNS successfully!"); String newUrl = path.replaceFirst(url.getHost(), ip);
try {
conn = (HttpURLConnection) new URL(newUrl).openConnection();
} catch (IOException e) { return null;
} // 设置HTTP请求头Host域
conn.setRequestProperty("Host", url.getHost()); // HTTPS场景
if (conn instanceof HttpsURLConnection) {
final HttpsURLConnection httpsURLConnection = (HttpsURLConnection)conn;
WXTlsSniSocketFactory sslSocketFactory = new WXTlsSniSocketFactory((HttpsURLConnection) conn); // SNI场景,创建SSLScocket解决SNI场景下的证书问题
conn.setInstanceFollowRedirects(false);
httpsURLConnection.setSSLSocketFactory(sslSocketFactory); // HTTPS场景,证书校验
httpsURLConnection.setHostnameVerifier(new HostnameVerifier() {
@Override public boolean verify(String hostname, SSLSession session) { String host = httpsURLConnection.getRequestProperty("Host"); Log.e(TAG, "verify host:" + host); if (null == host) {
host = httpsURLConnection.getURL().getHost();
} return HttpsURLConnection.getDefaultHostnameVerifier().verify(host, session);
}
});
}
} else { Log.e(TAG, "no corresponding ip found, return null"); return null;
} return conn;
}
}
1.2 <image>
网络请求 + HTTPDNS
WEEX
并没有提供默认的图片适配器实现,所以用户必须自行实现才能完成图片请求逻辑,具体步骤分为以下几步:
第一步:自定义图片请求适配器,实现IWXImgLoaderAdapter
接口
public class HttpDnsImageAdapter implements IWXImgLoaderAdapter { @Override
public void setImage(final String url, final ImageView view, WXImageQuality quality, final WXImageStrategy strategy) {
......
}
}
第二步:在WEEX
初始化时注册该图片适配器:
private void initWeex() {
InitConfig config=new InitConfig.Builder()
.setImgAdapter(new HttpDnsImageAdapter())
.build();
WXSDKEngine.initialize(this,config);
......
}
所以同WXHttpdnsAdatper
一样,我们只需在HttpDnsImageAdapter
植入HTTPDNS
逻辑即可。具体代码可参考:
/** * Created by liyazhou on 2017/10/22. */public class WXHttpDnsImageAdapter implements IWXImgLoaderAdapter { @Override
public void setImage(final String url, final ImageView view, WXImageQuality quality, final WXImageStrategy strategy) {
Log.e(TAG, "img url:" + url);
execute(new Runnable() { @Override
public void run() {
......
HttpURLConnection conn = null; try {
conn = createConnection(url);
.... // 将得到的数据转化成InputStream
InputStream is = conn.getInputStream(); // 将InputStream转换成Bitmap
final Bitmap bitmap = BitmapFactory.decodeStream(is);
WXSDKManager.getInstance().postOnUiThread(new Runnable() { @Override
public void run() {
view.setImageBitmap(bitmap);
}
}, 0);
......
}
});
} protected HttpURLConnection createConnection(String originalUrl) throws IOException {
mHttpDnsService = HttpDnsManager.getInstance().getHttpDnsService(); if (mHttpDnsService == null) {
URL url = new URL(originalUrl); return (HttpURLConnection) url.openConnection();
} else { return httpDnsRequest(originalUrl, null);
}
} private HttpURLConnection httpDnsRequest(String path, String reffer) {
HttpURLConnection httpDnsConn = null;
HttpURLConnection originalConn = null;
URL url = null; try {
url = new URL(path);
originalConn = (HttpURLConnection) url.openConnection(); // 异步接口获取IP
String ip = HttpDnsManager.getInstance().getHttpDnsService().getIpByHostAsync(url.getHost()); if (ip != null) { // 通过HTTPDNS获取IP成功,进行URL替换和HOST头设置
Log.d(TAG, "Get IP: " + ip + " for host: " + url.getHost() + " from HTTPDNS successfully!");
String newUrl = path.replaceFirst(url.getHost(), ip);
httpDnsConn = (HttpURLConnection) new URL(newUrl).openConnection(); // 设置HTTP请求头Host域
httpDnsConn.setRequestProperty("Host", url.getHost()); // HTTPS场景
if (httpDnsConn instanceof HttpsURLConnection) { final HttpsURLConnection httpsURLConnection = (HttpsURLConnection)httpDnsConn;
WXTlsSniSocketFactory sslSocketFactory = new WXTlsSniSocketFactory((HttpsURLConnection) httpDnsConn); // sni场景,创建SSLScocket解决SNI场景下的证书问题
httpsURLConnection.setSSLSocketFactory(sslSocketFactory); // https场景,证书校验
httpsURLConnection.setHostnameVerifier(new HostnameVerifier() { @Override
public boolean verify(String hostname, SSLSession session) {
String host = httpsURLConnection.getRequestProperty("Host"); if (null == host) {
host = httpsURLConnection.getURL().getHost();
} return HttpsURLConnection.getDefaultHostnameVerifier().verify(host, session);
}
});
}
} else { return originalConn;
}
...... int code = httpDnsConn.getResponseCode();// Network block
if (needRedirect(code)) {
String location = httpDnsConn.getHeaderField("Location"); if (location == null) {
location = httpDnsConn.getHeaderField("location");
} if (location != null) { if (!(location.startsWith("http://") || location
.startsWith("https://"))) { //某些时候会省略host,只返回后面的path,所以需要补全url
URL originalUrl = new URL(path);
location = originalUrl.getProtocol() + "://"
+ originalUrl.getHost() + location;
}
Log.e(TAG, "code:" + code + "; location:" + location + "; path" + path); return httpDnsRequest(location, path);
} else { return originalConn;
}
} return originalConn;
}
}
上述方案详细代码建:WeexAndroid
阅读全文请点击下面的阅读原文
以上是关于Android端WEEX_+_HTTPDNS_最佳实践的主要内容,如果未能解决你的问题,请参考以下文章
HTTPDNS开源 Android SDK,赋能更多开发者参与共建
App域名劫持之DNS高可用 - 开源版HttpDNS方案详解(转)
Weex Android SDK源码分析之Module(modal)
Weex 解决Print: Entry, ":CFBundleIdentifier", Does Not Exist 错误方法