由于WebView
并未暴露出设置DNS的接口,因而在WebView
场景下使用HTTPDNS
存在很多限制,但如果接入WEEX
,则可以较好地植入HTTPDNS
,本文主要介绍在WEEX
场景下接入HTTPDNS
的方案细节。
当前最佳实践文档只针对结合使用时,如何使用HTTPDNS解析出的IP,关于HTTPDNS本身的解析服务,请先查看Android SDK接入。
代码示例
HTTPDNS+Weex方案详细代码参见Weex+HTTPDNS Android Demo。
概述
在WEEX
运行时环境下,所有的逻辑最终都会转换到Native Runtime
中执行,网络请求也不例外。同时WEEX
也提供了自定义相应实现的接口,通过重写网络请求适配器,我们可以较为简单地接入HTTPDNS。在WEEX运行环境中,主要有两种网络请求:
通过Stream进行的网络请求
标签指定的加载图片的网络请求
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; } }
<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; } }
- 本页导读 (0)