通過前面五篇文章的源碼分析及總結,咱們對Weex的總體架構及核心源碼都有了清晰的認識。本篇文章主要總結我在Weex SDK源碼閱讀時以爲能夠借鑑的細節。java
備註:本文側重講Weex SDK源碼級別的可借鑑細節,對大方向上的可借鑑點好比動態化+Native思路、一項技術完整的生態等方面能夠參考上一篇文章《深刻Weex系列(八)之Weex SDK架構分析》。android
在使用Weex以前咱們都會進行Weex SDK的初始化,對於Weex SDK它的輔助配置類就使用到了建造者模式。git
建造者模式主要解決:一個模塊各個部分子對象的構建算法可能變化,可是各個部分子對象相互結合在一塊兒的算法確實穩定的。一句話總結就是:模塊總體構建過程穩定,可是構建的每一步可能有出入。github
咱們結合Weex的場景來具體分析下:Weex配置模塊的構建過程是穩定的(都須要提供一樣的能力),可是構建的每一步則可能有出入(每一個配置的能力提供卻能夠多樣)。算法
舉例說明:例如Weex須要提供網絡請求的基礎能力(這個構建過程穩定),可是網絡請求能夠有不一樣的實現方式(具體的構建算法可能變化)。性能優化
InitConfig config = new InitConfig.Builder().
setImgAdapter(new WeexImageAdapter()).
setHttpAdapter(new WeexHttpAdapter).
setSoLoader(new WeexSoLoaderAdapter).
build();
複製代碼
好處:調用者無需知道構建模塊如何組裝,也不會忘記組裝某一部分,同時也提供給了開發者定製的能力。bash
So的成功加載對Weex的運行相當重要,畢竟Weex須要V8引擎執行Js與Native的交互,源碼中也能夠看出So沒有加載成功則Weex的各個模塊不會執行。微信
而在線上Bug收集中咱們會遇到UnsatisfiedLinkError錯誤,雖然不是頻發性Bug,可是對於Weex而言一旦出現那麼Weex就不可能再運行。因而Weex SDK對So加載這塊作了優化,咱們看下So加載的代碼邏輯:網絡
public static boolean initSo(String libName, int version, IWXUserTrackAdapter utAdapter) {
String cpuType = _cpuType();
if (cpuType.equalsIgnoreCase(MIPS) ) {
return false; // mips架構不支持,直接返回
}
boolean InitSuc = false;
if (checkSoIsValid(libName, BuildConfig.ARMEABI_Size) ||checkSoIsValid(libName, BuildConfig.X86_Size)) { // 校驗So大小是否正常
/**
* Load library with {@link System#loadLibrary(String)}
*/
try {
// If a library loader adapter exists, use this adapter to load library
// instead of System.loadLibrary.
if (mSoLoader != null) {
mSoLoader.doLoadLibrary(libName);// 自定義SoLoader加載的話本身去加載
} else {
System.loadLibrary(libName);// 默認加載的方式
}
commit(utAdapter, null, null);
InitSuc = true;
} catch (Exception | Error e2) {// So加載失敗
if (cpuType.contains(ARMEABI) || cpuType.contains(X86)) {
commit(utAdapter, WXErrorCode.WX_ERR_LOAD_SO.getErrorCode(), WXErrorCode.WX_ERR_LOAD_SO.getErrorMsg() + ":" + e2.getMessage());
}
InitSuc = false;
}
try {
if (!InitSuc) {
// 沒有加載成功的話則從文件中加載
//File extracted from apk already exists.
if (isExist(libName, version)) {
boolean res = _loadUnzipSo(libName, version, utAdapter);// 從解壓包中加載So
if (res) {
return res;
} else {
//Delete the corrupt so library, and extract it again.
removeSoIfExit(libName, version);// 解壓包也加載失敗,刪除;
}
}
//Fail for loading file from libs, extract so library from so and load it.
if (cpuType.equalsIgnoreCase(MIPS)) {
return false;
} else {
try {
InitSuc = unZipSelectedFiles(libName, version, utAdapter);// 從apk中解壓出來So,而後加載;
} catch (IOException e2) {
e2.printStackTrace();
}
}
}
} catch (Exception | Error e) {
InitSuc = false;
e.printStackTrace();
}
}
return InitSuc;
}
複製代碼
能夠看到Weex中有多項保障去保證So的成功加載,總結下流程圖:多線程
各位老司機都知道多線程的好處也知道Android只有主線程才能更新UI,對於Weex來講它有本身完整的一套工做機制,若是全部任務都在主線程那勢必會積壓太多任務,致使任務得不到及時執行同時也有卡頓的風險。
Weex SDK也考慮到了這些,分析Weex的機制能夠知道任務主要花費在三方面:JSBridge相關、Dom相關、UI相關。因而對這三方面進行了細分,JSBridge相關的操做挪到JSBridge線程執行,Dom相關操做在Dom線程執行,避免了主線程積壓太多任務。此處咱們能夠想到使用異步線程。同時對於單項的任務例如Dom操做,須要是串行的。若是使用線程池,實際上也發揮不出線程池的威力。
分析到了這裏。咱們的需求其實就很明確了:避免異步線程的建立及銷燬過程消耗資源,同時支持串行執行。咱們能夠設想一種線程能力:有任務的時候則執行,沒有任務的時候則等待,是否是完美的符合咱們的需求。
幸運的是Android其實已經爲咱們提供了這樣的一個類:HandlerThread。你們能夠參考我以前的一篇文章《Android性能優化(十一)之正確的異步姿式》。
// 貼出Weex中使用的HandlerThread實例
// JSBridge工做的Thread
mJSThread = new WXThread("WeexJSBridgeThread", this);
mJSHandler = mJSThread.getHandler();
// Dom工做的Thread
mDomThread = new WXThread("WeeXDomThread", new WXDomHandler(this));
mDomHandler = mDomThread.getHandler();
複製代碼
總結下Weex的線程模型:
優點:
對於Weex的RunTime,再怎麼強大也少不了與Native的交互(方法調用,使用Native的能力),前面的系列文章也詳細分析了Module的交互原理。可是有一個細節問題前面沒有說到,就是JS與Native交互的方法簽名,參數類型只能是String嗎?
回到WXBridge這個通訊的橋樑,調用Native的方法都會走到callNative方法,而後走到WxBridgeManager.callNative方法,會發現函數體內有一行:
JSONArray array = JSON.parseArray(tasks);
複製代碼
由此能夠判定JS傳遞給Native的參數首先不只僅是普通String字符串,而是Json格式。實際上不論是斷點查看或者翻閱WXStreamModule的代碼,均可以發現Json的蹤跡。
@JSMethod(uiThread = false)
public void fetch(String optionsStr, final JSCallback callback, JSCallback progressCallback){
JSONObject optionsObj = null;
try {
optionsObj = JSON.parseObject(optionsStr);
}catch (JSONException e){
WXLogUtils.e("", e);
}
......
}
複製代碼
不過以上發現還不足以解決咱們的疑惑:參數類型只能是String嗎?那必須不是!
首先回顧下在Module的註冊過程當中會有一步是獲取Module中被打上註解的方法而後存在mMethodMap中;而在真正調用方法的地方是NativeInvokeHelper的invoke方法:
public Object invoke(final Object target,final Invoker invoker,JSONArray args) throws Exception {
final Object[] params = prepareArguments(invoker.getParameterTypes(),args);// 解析參數
if (invoker.isRunOnUIThread()) {// 要求在主線程執行則拋到主線程執行;
WXSDKManager.getInstance().postOnUiThread(new Runnable() {
@Override
public void run() {
try {
invoker.invoke(target, params);// 反射調用方法執行
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}, 0);
} else {
return invoker.invoke(target, params);
}
return null;
}
複製代碼
咱們再來詳細跟蹤下解析參數這步:
private Object[] prepareArguments(Type[] paramClazzs, JSONArray args) throws Exception {
Object[] params = new Object[paramClazzs.length];
Object value;
Type paramClazz;
for (int i = 0; i < paramClazzs.length; i++) {
paramClazz = paramClazzs[i];
if(i>=args.size()){
if(!paramClazz.getClass().isPrimitive()) {
params[i] = null;
continue;
}else {
throw new Exception("[prepareArguments] method argument list not match.");
}
}
value = args.get(i);
// JSONObject與JSCallback類型單獨處理
if (paramClazz == JSONObject.class) {
params[i] = value;
} else if(JSCallback.class == paramClazz){
if(value instanceof String){
params[i] = new SimpleJSCallback(mInstanceId,(String)value);
}else{
throw new Exception("Parameter type not match.");
}
} else {
// 其它類型的參數
params[i] = WXReflectionUtils.parseArgument(paramClazz,value);
}
}
return params;
}
複製代碼
看下其它參數類型的解析:
public static Object parseArgument(Type paramClazz, Object value) {
if (paramClazz == String.class) {
return value instanceof String ? value : JSON.toJSONString(value);
} else if (paramClazz == int.class) {
return value.getClass().isAssignableFrom(int.class) ? value : WXUtils.getInt(value);
} else if (paramClazz == long.class) {
return value.getClass().isAssignableFrom(long.class) ? value : WXUtils.getLong(value);
} else if (paramClazz == double.class) {
return value.getClass().isAssignableFrom(double.class) ? value : WXUtils.getDouble(value);
} else if (paramClazz == float.class) {
return value.getClass().isAssignableFrom(float.class) ? value : WXUtils.getFloat(value);
} else {
return JSON.parseObject(value instanceof String ? (String) value : JSON.toJSONString(value), paramClazz);
}
}
複製代碼
跟蹤到此處就顯而易見:JS與Native的交互參數不只僅支持String。
咱們再來總結下Weex是如何實現不一樣方法簽名的交互的:
本文主要記錄了我在Weex源碼閱讀過程當中以爲不錯能夠借鑑的細節,限於文章篇幅不能面面俱到。實際上不只Weex的總體思路,Weex SDK的代碼也很是優秀,很是建議你們仔細閱讀,學習優秀的源碼對本身的編碼能力會有必定程度的提高!
歡迎持續關注Weex源碼分析項目:Weex-Analysis-Project
歡迎關注微信公衆號:按期分享Java、Android乾貨!