深刻Weex系列(九)Weex SDK可借鑑細節總結

一、前言

通過前面五篇文章的源碼分析及總結,咱們對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的加載

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的成功加載,總結下流程圖:多線程

Weex中So加載流程圖

四、Weex的線程模型

各位老司機都知道多線程的好處也知道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的線程模型:

  • JSBridge在WeexJSBridgeThread負責JS與Native的通訊;
  • 切換具體的Dom指令到WeeXDomThread負責關於Dom的各項如:解析、Rebuild Dom Tree、Layout等操做;
  • 切換到UI線程,負責原生View的建立、佈局、事件添加、數據綁定等;

優點:

  • 避免主線程的卡頓風險;
  • 避免了線程的建立與銷燬等資源消耗;
  • 同時支持串行操做;

五、交互函數參數類型的處理

對於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是如何實現不一樣方法簽名的交互的:

  • Module註冊階段保存下來Method;
  • JS發送指令調用Module方法傳遞的原始參數是Json格式;
  • 真正反射調用方法的時候從Method中拿到參數的具體類型,而後從Json中讀到相應的值,再進行轉換。

六、後記

本文主要記錄了我在Weex源碼閱讀過程當中以爲不錯能夠借鑑的細節,限於文章篇幅不能面面俱到。實際上不只Weex的總體思路,Weex SDK的代碼也很是優秀,很是建議你們仔細閱讀,學習優秀的源碼對本身的編碼能力會有必定程度的提高!

歡迎持續關注Weex源碼分析項目:Weex-Analysis-Project

歡迎關注微信公衆號:按期分享Java、Android乾貨!

歡迎關注
相關文章
相關標籤/搜索