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("https://") || 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("https://") || 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
最後更新:2017-11-03 20:33:34