Weex Android原始碼解析(三)—— 進入正題

weixin_33936401發表於2018-04-08

版本:0.18.0
自己對於Weex原始碼學習的一些記錄,幫助理解,如有錯誤請指出,部分圖片以及描述來源於官方的原始碼解析文章,文末會附上鍊接。

前面我們大體瞭解了Weex三個模組中的前兩個:Bridge和Dom,但是我們還不太清楚一個非常重要的內容:渲染過程,也就是第三個模組:Render。另外,我們知道了三個模組的主要作用還完全不足以讓我們清晰地瞭解整個Weex的工作流程,所以在瞭解Render之前,我們首先要從整體上對Weex的工作原理有一定的瞭解,這樣幫助我們更好的理解之後的渲染流程。

一、Native SDK之外,Weex做了什麼

首先這張圖大家肯定都看過,來自於官方文件:

8498017-5b606e0c9eaa495d.jpg
Weex架構圖

1、基本流程

從這張圖我們可以知道Weex的基本流程是這樣的:

(1)我們通過前端技術棧,產出JS Bundle,部署在伺服器端

(2)客戶端獲取此JS Bundle,使用JS引擎進行JS Bundle的執行

(3)在執行的過程中,需要Native的地方以及渲染指令,通過我們之前提到的Bridge模組進行互動(發出相關指令),交給Native SDK進行執行,完成最終的渲染

(1)這個部分主要就是前端知識,暫時還不瞭解,以後有機會再慢慢看,這個(3)也就是我們一直分析的,Android SDK的部分,而實際上我們昨天分析的那個比較複雜的Dom過程,就是createBody指令執行的(圖中的callNative),透傳到Native端的Bridge模組進行指令的傳送以及後續的一系列執行。

2、指令的轉換

這裡的指令,在Weex中被稱為Native Dom API,在SDK端,被轉換之後執行的,稱為Platform API,這個我們先知道一下就好:

(1)Native Dom API(原生Dom API):Weex在JS Bundle執行中的指令

(2)Platform API(平臺相關API):Android/iOS端的真正原生執行指令

二、初始化過程

為了避免我們還處在不明不白的流程梳理上,我們先從初始化過程開始看,仔細的看一下整個Android端SDK的初始化過程。初始化的入口使用過API應該清楚,就是在自定義的Application中,我們開始看一下呼叫過程。

//自定義Application中
@Override
public void onCreate() {
    super.onCreate();
    InitConfig config=new InitConfig.Builder().setImgAdapter(new ImageAdapter()).build();
    WXSDKEngine.initialize(this,config);

    //註冊自定義模組
    try {
        WXSDKEngine.registerModule("TestModule", TestModule.class);
    } catch (Exception e) {
        e.printStackTrace();
    }
}

在完成InitConfig的建立和設定之後(這裡運用了Builder模式),呼叫了關鍵函式initialize,進入真正的初始化中。

步驟一:WXSDKEngine.initialize函式呼叫

傳入引數:

(1)application:App的Application物件

(2)config:自己建立的InitConfig物件,設定了一系列自定義的Adapter

通過程式碼可以看到關鍵還是doInitInternal這個函式呼叫,傳入引數同initialize

public static void initialize(Application application,InitConfig config){
    synchronized (mLock) {
      if (mIsInit) {
        return;
      }
      ...//效能相關監測
      doInitInternal(application,config);
      ...
      mIsInit = true;
    }
}

doInitInternal的過程我們分為兩個步驟來說。第一個是主要是執行了一個Runnable,第二個就是註冊元件。

步驟二:初始化Bridge模組,並且執行一個Runnable

WXBridgeManager.getInstance().post(new Runnable() {
      @Override
      public void run() {
        //2.1 WXSDKManager的初始化
        WXSDKManager sm = WXSDKManager.getInstance();
        sm.onSDKEngineInitialize();
        if(config != null ) {
          sm.setInitConfig(config);
        }
        //2.2 初始化so
        WXSoInstallMgrSdk.init(application,
                              sm.getIWXSoLoaderAdapter(),
                              sm.getWXStatisticsListener());
        boolean isSoInitSuccess = WXSoInstallMgrSdk.initSo(V8_SO_NAME, 1, config!=null?config.getUtAdapter():null);
        if (!isSoInitSuccess) {
          ...
          return;
        }
        //2.3 初始化JSFramework
        sm.initScriptsFramework(config!=null?config.getFramework():null);
        ...
      }
});

1、Bridge模組的初始化

這個就是第一次呼叫WXBridgeManager.getInstance()時的初始化過程,在第一篇文章中我們已經分析過了,Bridge模組使用了一個HandlerThread(Android層面)進行Bridge的任務處理,這裡不再贅述。

2、執行一個Runnable中的內容

這裡我們應該知道的是這個post實際上是交給了Bridge Thread的Handler並且執行在Bridge Thread中的,然後我們看看它做了什麼:

2.1 WXSDKManager的初始化

呼叫其getInstance函式,完成初始化工作,在這個過程中,可以從程式碼中看到,常見Manager建立完成:

//呼叫的建構函式
private WXSDKManager() {
    this(new WXRenderManager());
}
//Manager都被建立
private WXSDKManager(WXRenderManager renderManager) {
    mWXRenderManager = renderManager;
    mWXDomManager = new WXDomManager(mWXRenderManager);
    mBridgeManager = WXBridgeManager.getInstance();
    mWXWorkThreadManager = new WXWorkThreadManager();
}

2.2 載入so檔案,也就是Android端的JS引擎,關鍵函式WXSoInstallMgrSdk.initSo

//關鍵程式碼
System.loadLibrary(libName);

在完成了一系列檢查等操作之後,可以看到最終還是呼叫系統API進行so庫的載入

2.3 初始化JSFramework,呼叫initScriptsFramework

先知道一下,這裡的引數是一個String,是一個可以由InitConfig設定的framework名稱,但是一般第一次是沒有設定的。然後我們可以看到這個任務實際上交給了BridgeManager執行:

public void initScriptsFramework(String framework) {
    mBridgeManager.initScriptsFramework(framework);
}

//BridgeManager中:
public synchronized void initScriptsFramework(String framework) {
    Message msg = mJSHandler.obtainMessage();
    msg.obj = framework;
    msg.what = WXJSBridgeMsgType.INIT_FRAMEWORK;
    msg.setTarget(mJSHandler);
    msg.sendToTarget();
}

實際上又是傳送了一個msg,交給Handler從而進入Bridge Thread中執行,對應的what為:WXJSBridgeMsgType.INIT_FRAMEWORK,這個Handler在第一篇我們就簡單介紹了一下,這裡再說一個細節,就是這個Handler在建立時,實際上是有一個callback的,而這個callback就是BridgeManager本身(有點繞,還是看程式碼就懂了):

//BridgeManager的建構函式中
mJSThread = new WXThread("WeexJSBridgeThread", this);
mJSHandler = mJSThread.getHandler();

//WXThread的建構函式中:
public WXThread(String name, Callback callback) {
    super(name);
    start();
    mHandler = new Handler(getLooper(), secure(callback)); //傳入callback
}

在我們瞭解Android本身的Handler的基礎上,我們可以知道實際上這個任務最重還是交給了Manager來處理(作為callback),執行在Bridge Thread執行緒中,所以我們去看一下BridgeManager的handleMessage函式:

//BridgeManager的handleMessage函式
switch (what) {
      case WXJSBridgeMsgType.INIT_FRAMEWORK:
        invokeInitFramework(msg); //實際呼叫
        break;
      ...
      default:
        break;
}

這樣一來就清晰了,實際上還是給invokeInitFramework處理了,那麼我們看一下傳入的引數msg:就是initScriptsFramework中建立的那個msg,然後可以發現,invokeInitFramework實際上也沒做什麼,檢測了一下可用記憶體之後,就把之前傳入的framework名稱傳給了initFramework函式(注意,第一次一定為空),我們接著往下看:

//initFramework中
if (TextUtils.isEmpty(framework)) {
    WXLogUtils.d("weex JS framework from assets");
    framework = WXFileUtils.loadAsset("main.js", WXEnvironment.getApplication());
}

在framework為空的情況下(第一次一般沒有設定),載入本地的main.js檔案,這裡實際上就是把main.js載入進入了framework這個string中,之後可以看到呼叫了native的方法,完成JSFramework的載入:

try {
    ...//效能檢測相關
    if (mWXBridge.initFrameworkEnv(framework, assembleDefaultOptions(), crashFile, pieSupport) == INIT_FRAMEWORK_OK) {
          setJSFrameworkInit(true);
          if (WXSDKManager.getInstance().getWXStatisticsListener() != null) {
            WXSDKManager.getInstance().getWXStatisticsListener().onJsFrameworkReady();
          }
          execRegisterFailTask();
          WXEnvironment.JsFrameworkInit = true;
          //向JSFramework註冊Dom模組的方法,使得JS可以呼叫
          registerDomModule();
          String reinitInfo = "";
          if (reInitCount > 1) {
            reinitInfo = "reinit Framework:";
          }
    } else {
    ...//錯誤處理  
    }
} catch(Throwable e) {
    ...//錯誤處理
}

這裡還有一個關鍵步驟就是:registerDomModule,向JSFramework註冊Dom模組的方法,使得JS可以呼叫,我們可以看一下具體都註冊了什麼方法:

private void registerDomModule() throws WXException {
    /** Tell Javascript Framework what methods you have. This is Required.**/
    Map<String, Object> domMap = new HashMap<>();
    domMap.put(WXDomModule.WXDOM, WXDomModule.METHODS);
    registerModules(domMap);
}

//WXDOM
public static final String WXDOM = "dom";
//METHODS
public static final String[] METHODS = {CREATE_BODY, UPDATE_ATTRS, UPDATE_STYLE,
      REMOVE_ELEMENT, ADD_ELEMENT, MOVE_ELEMENT, ADD_EVENT, REMOVE_EVENT, CREATE_FINISH,
      REFRESH_FINISH, UPDATE_FINISH, SCROLL_TO_ELEMENT, ADD_RULE,GET_COMPONENT_RECT,
      INVOKE_METHOD};

這裡就可以回想我們在一中說到的Native Dom API的轉換過程中,提到的API就是在這裡的註冊的Dom相關的API,其註冊過程就在這裡,其就是以dom模組的形式註冊的。而我們自定義的模組,其註冊方式和這裡是一樣的,只不過是主動呼叫的,這裡我們後面再分析。

這裡最終執行的是一個Native的方法,呼叫registerModules方法告訴JSFramework我們需要註冊模組,function名稱即:registerModules。

步驟三:註冊元件,呼叫register()函式

1、使用BatchOperationHelper來攔截registerComponent操作,在最後呼叫flush進行統一執行,具體細節如下:

BatchOperationHelper batchHelper = new BatchOperationHelper(WXBridgeManager.getInstance());

這裡是將BridgeManager當做BactchExecutor介面傳入的,會給其設定攔截器:

public BatchOperationHelper(BactchExecutor executor){
    mExecutor = executor;
    executor.setInterceptor(this);//設定攔截器
    isCollecting = true;
}

設定了攔截器之後,這樣BridgeManager在呼叫post時,會進行攔截:

public void post(Runnable r) {
    if (mInterceptor != null && mInterceptor.take(r)) {
      //task is token by the interceptor
      return; //返回了,不進行post下一步操作
    }
    ...
}

並且Runnable被攔截器take了:

public boolean take(Runnable runnable) {
    if(isCollecting){
      //take下來,新增到task列表中
      sRegisterTasks.add(runnable);
      return true;
    }
    return false;
}

這樣只有在BatchOperationHelper呼叫flush時,才集中進行呼叫:

public void flush(){
    //設定isCollecting為false,這樣take會失敗,下面的任務就會真正得到執行
    isCollecting = false;
    //統一按順序執行
    mExecutor.post(new Runnable() {
      @Override
      public void run() {
        Iterator<Runnable> iterator = sRegisterTasks.iterator();
        while(iterator.hasNext()){
          Runnable item = iterator.next();
          item.run();
          iterator.remove();
        }
      }
    });
    //取消攔截設定
    mExecutor.setInterceptor(null);
}

下面我們來看真正的註冊Component操作是如何進行的,首先看registerComponent函式有好幾個簽名,但最終呼叫的都是:

WXComponentRegistry.registerComponent(name, holder, componentInfo);

交給了WXComponentRegistry來進行,我們來看一下引數,這裡以text元件的註冊為例:

registerComponent(
    new SimpleComponentHolder(
        WXText.class,
        new WXText.Creator()
    ),
    false,
    WXBasicComponentType.TEXT
);

(1)type:型別,這裡是“text”

(2)holder:Component holder,實現介面IFComponentHolder,預設實現是這裡的SimpleComponentHolder,我們後面再看

(3)componentInfo,我們看是如何建立的:

Map<String, Object> componentInfo = new HashMap<>();
if (appendTree) {
    componentInfo.put("append", "tree");
}
result  = result && WXComponentRegistry.registerComponent(name, holder, componentInfo);

這裡可以看到,如果appendToTree為false,則就是一個空的HashMap,否則加入一個屬性"append": "tree",我們下面深入到實現中:

WXBridgeManager.getInstance()
        .post(new Runnable() {
      @Override
      public void run() {
        try {
          Map<String, Object> registerInfo = componentInfo;
          if (registerInfo == null){
            registerInfo = new HashMap<>();
          }

          registerInfo.put("type",type);
          registerInfo.put("methods",holder.getMethods());
          //分別註冊Native與JS
          registerNativeComponent(type, holder);
          registerJSComponent(registerInfo);
          sComponentInfos.add(registerInfo);
        } catch (WXException e) {
          WXLogUtils.e("register component error:", e);
        }

      }
});

首先可以應該注意的是,註冊元件的執行是在Bridge Thread中的(也成為JS Thread),然後可以看到,在呼叫兩個register之前,關鍵還是registerInfo,也就是我們之前傳入的componentInfo,首先放入了type的值,然後就是methods,這個又與holder有關了,我們下面詳細來看一下holder:

IFComponentHolder介面的定義:(注意繼承關係)

public interface IFComponentHolder extends ComponentCreator,JavascriptInvokable

其實現類是SimpleComponentHolder,建構函式為:

public SimpleComponentHolder(Class<? extends WXComponent> clz,ComponentCreator customCreator) {
    this.mClz = clz;
    this.mCreator = customCreator;
}

還是以text為例,我們傳入了兩個引數:

(1)clz:WXText類

(2)customCreator:一個新建立的WXText的Creater物件,實現ComponentCreator介面,實際上就是一個建立例項的介面,實現方法:createInstance從而建立Component例項。

那這個使用的getMethods方法呢?如下:

public String[] getMethods() {
    if(mMethodInvokers == null){
      generate();
    }
    Set<String> keys = mMethodInvokers.keySet();
    return keys.toArray(new String[keys.size()]);
}

這裡可以發現兩點

  • 首先這個getMethods方法是定義在JavascriptInvokable介面中的,我們建立的元件可以有一些方法,在JS程式碼中呼叫,這裡Holder作為元件的快取,實現的就是這個介面,從介面名稱也可以看的出來,而methods就儲存了這些方法
  • 這裡呼叫了generate方法,生成mMethodInvokers物件,我們繼續看generate方法:
private synchronized void generate(){
    Pair<Map<String, Invoker>, Map<String, Invoker>> methodPair = getMethods(mClz);
    mPropertyInvokers = methodPair.first;
    mMethodInvokers = methodPair.second;
}

這裡我們可以看出來,使用getMethods方法將PropertyInvokers和MethodInvokers分別通過Map的形式獲取,其實分別對應於WXComponentProp和JSMethod註解(被JS呼叫的兩種形式的註解:屬性和方法)

然後我們進入getMethods方法中看一下,具體來說並不複雜:

static Pair<Map<String,Invoker>,Map<String,Invoker>> getMethods(Class clz){
    Map<String, Invoker> methods = new HashMap<>();
    Map<String, Invoker> mInvokers = new HashMap<>();

    Annotation[] annotations;
    Annotation anno;
    try {
      //遍歷class的方法
      for (Method method : clz.getMethods()) {
        try {
          //獲取方法註解
          annotations = method.getDeclaredAnnotations();
          for (int i = 0, annotationsCount = annotations.length;
               i < annotationsCount; ++i) {
            anno = annotations[i];
            if(anno == null){
              continue;
            }
            if (anno instanceof WXComponentProp) {
              //屬性方法註解
              String name = ((WXComponentProp) anno).name();
              methods.put(name, new MethodInvoker(method,true));
              break;
            }else if(anno instanceof JSMethod){
              //直接JS呼叫方法註解
              JSMethod methodAnno = (JSMethod)anno;
              String name = methodAnno.alias();
              if(JSMethod.NOT_SET.equals(name)){
                name = method.getName(); //如果沒有設定,就是方法名稱
              }
              mInvokers.put(name, new MethodInvoker(method,methodAnno.uiThread()));
              break;
            }
          }
        } catch (ArrayIndexOutOfBoundsException | IncompatibleClassChangeError e) {
          //ignore: getDeclaredAnnotations may throw this
        }
      }
    }catch (IndexOutOfBoundsException e){
      e.printStackTrace();
      //ignore: getMethods may throw this
    }
    return new Pair<>(methods,mInvokers);
}

可以看到,實際上就是遍歷class中的方法,根據其註解放入不同的HashMap中:

1、如果是WXComponentProp,放入methods中,並且根據method建立新的MethodInvoker,當然,這個方法肯定是執行在UI執行緒中的

2、如果是JSMethod,放入mInvokers中,同樣建立MethodInvoker,而這裡是否執行在UI執行緒中是通過註解進行設定的,name如果不用註解設定,就使用方法的名稱

所以這裡分析完了我們在回到剛才呼叫getMethods的地方:

registerInfo.put("methods",holder.getMethods());

這裡返回的,就是所有JSMethod註解的方法名稱陣列。

2、Native層面註冊元件,呼叫registerNativeComponent方法

private static boolean registerNativeComponent(String type, IFComponentHolder holder) throws WXException {
    try {
      holder.loadIfNonLazy();
      sTypeComponentMap.put(type, holder);
    }catch (ArrayStoreException e){
      e.printStackTrace();
      //ignore: ArrayStoreException: java.lang.String cannot be stored in an array of type java.util.HashMap$HashMapEntry[]
    }
    return true;
}

這裡的引數就是我們剛才說type以及holder,然後首先呼叫了holder的loadIfNonLazy方法,實際上仍是SimpleComponentHolder實現的:

public void loadIfNonLazy() {
    Annotation[] annotations = mClz.getDeclaredAnnotations();
    for (Annotation annotation :
      annotations) {
      if (annotation instanceof Component){
        if(!((Component) annotation).lazyload() && mMethodInvokers == null){
          generate();
        }
        return;
      }
    }
}

實際上做的事情非常簡單,如果沒有在使用Component註解是註明是懶載入情況的,就直接呼叫generate函式,而這個函式之前我們已經分析過了(這裡似乎程式碼中是有些問題的,因為實際上之前已經呼叫過了,但是並不影響)。

之後就把type和holder存入sTypeComponentMap中,這裡實際上後面我們可以看到,在我們之前說的Dom解析的過程中,就是使用這個列表來取出對應的Component的,想必之前也應該知道大概就是這麼一個過程,這個過程我們最後分析完了再來看。

3、向JS Framework註冊元件

這裡我們不必細說,因為最終還是使用execJS函式,呼叫JS的方法,這裡怎麼實現就是JS層面的事情了,目前還不我們關注的重點。

步驟四:註冊Module,呼叫registerModule函式

這裡這個步驟似乎我們已經分析過了,但實際上這裡和我們之前說的Dom模組的註冊並不一樣,可以說,Dom模組和我們其他註冊的模組是不一樣的地位(畢竟Dom模組主導著最主要的功能),這個具體我們可以先了解一下callNative這樣一個通訊呼叫中很關鍵的一段(在WXBridgeMananger中):

if (WXDomModule.WXDOM.equals(target)) {
    WXDomModule dom = getDomModule(instanceId);
    dom.callDomMethod(task, parseNanos);
} else {
    JSONObject optionObj = task.getJSONObject(OPTIONS);
    callModuleMethod(instanceId, (String) target,
        (String) task.get(METHOD), (JSONArray) task.get(ARGS), optionObj);
}

這裡的target和task都是來自於JS的,我們可以看到對於以dom為target和其他Module是不同的,DomModule是直接由WXModuleManager獲取DomModule例項,而其他模組則是有一個查詢的過程的,這個我們最後分析完註冊可以再分析一下查詢過程。

模組註冊過程,以modal為例:

registerModule("modal", WXModalUIModule.class, false);

這裡的引數分別是

(1)moduleName:模組名稱

(2)moduleClass:模組實現類

(3)global:是否是全域性模組(全域性模組整個全域性只有一個,而普通模組每個頁面Instance有一個)

這裡最終呼叫的是WXModuleManager的registerModule函式,並且在呼叫前,建立了一個TypeModuleFactory,傳入class,實際上就是一個工廠類,可以根據Module的class生成例項以及生成可呼叫的方法的列表,然後我們繼續看這個函式:

public static boolean registerModule(final String moduleName, final ModuleFactory factory, final boolean global) throws WXException {
    ...
    //dom的註冊不能使用此函式,即不能註冊一個名為dom的模組
    if (TextUtils.equals(moduleName,WXDomModule.WXDOM)) {
      WXLogUtils.e("Cannot registered module with name 'dom'.");
      return false;
    }
    //execute task in js thread to make sure register order is same as the order invoke register method.
    WXBridgeManager.getInstance()
        .post(new Runnable() {
      @Override
      public void run() {
        if (sModuleFactoryMap.containsKey(moduleName)) {
          WXLogUtils.w("WXComponentRegistry Duplicate the Module name: " + moduleName);
        }
        //如果是global的,提前建立例項
        if (global) {
          try {
            WXModule wxModule = factory.buildInstance();
            wxModule.setModuleName(moduleName);
            sGlobalModuleMap.put(moduleName, wxModule);
          } catch (Exception e) {
            WXLogUtils.e(moduleName + " class must have a default constructor without params. ", e);
          }
        }

        try {
          registerNativeModule(moduleName, factory);
        } catch (WXException e) {
          WXLogUtils.e("", e);
        }
        registerJSModule(moduleName, factory);
      }
    });
    return true;
}

1、如果是global的情況,通過工廠方法提前建立了一個WXModule例項並且根據moduleName放入sGlobalModuleMap中,受到ModuleManager管理

2、呼叫registerNativeModule,向Native註冊module,這一步實際上非常簡單:

static boolean registerNativeModule(String moduleName, ModuleFactory factory) throws WXException {
    ...//null檢查
    try {
      sModuleFactoryMap.put(moduleName, factory);
    }catch (ArrayStoreException e){
      e.printStackTrace();
      //ignore:
    }
    return true;
}

把moduleName和factory放入了sModuleFactoryMap即可。

3、呼叫registerJSModule,向JS註冊module,這裡省略,之前分析過了,最終實際上呼叫了execJS,執行了JS方法registerModules

步驟六:註冊DomObject

最後這一步就比較簡單了,註冊DomObject,用於Dom解析中,根據名稱獲取不同的DomObject物件,我們還是以text為例:

registerDomObject(WXBasicComponentType.TEXT, WXTextDomObject.class);

這裡傳入的引數很簡單:

(1)type:元件名稱

(2)clazz:實現元件的DomObject類

然後實際上又是交給了WXDomRegistry:

public static boolean registerDomObject(String type, Class<? extends WXDomObject> clazz) throws WXException {
    ...//null檢測
    if (sDom.containsKey(type)) {
      if (WXEnvironment.isApkDebugable()) {
        throw new WXException("WXDomRegistry had duplicate Dom:" + type);
      } else {
        WXLogUtils.e("WXDomRegistry had duplicate Dom: " + type);
        return false;
      }
    }
    sDom.put(type, clazz);
    return true;
}

實際上很簡單,放入了sDom的Map中,這樣在之前我們分析的parse中,就可以使用其getDomObjectClass來獲取對應的DomObject實現類。

三、使用

在第二階段的分析中,除了一系列載入,最主要的就是註冊過程,註冊了Component、Module以及DomObject,在這個註冊過程之後,我們最關心的還是一點:註冊了怎麼用?

下面我們就看看三種註冊是被如何使用的:

1、使用註冊的Component

相信看過(二)中的分析,對於Component的建立肯定不陌生,我們來回憶一下關鍵程式碼:

//遞迴生成Component樹,這段程式碼在CreateBodyAction中
WXComponent component = createComponent(context, domObject);
if (component == null) {
    return;
}

之後根據之前的分析,我們知道最關鍵的建立例項的程式碼是:

WXComponent component = WXComponentFactory.newInstance(context.getInstance(), dom, parent);

然後我們來看一下是如何根據這個工廠方法建立例項的:

public static WXComponent newInstance(WXSDKInstance instance, WXDomObject node, WXVContainer parent) {
    ...//null處理
    if(sComponentTypes.get(instance.getInstanceId())==null){
      Set<String> types=new HashSet<>();
      sComponentTypes.put(instance.getInstanceId(),types);
    }
    sComponentTypes.get(instance.getInstanceId()).add(node.getType());

    //獲取註冊時的Holder
    IFComponentHolder holder = WXComponentRegistry.getComponent(node.getType());
    if (holder == null) {
        ...//錯誤處理
    }

    try {
      return holder.createInstance(instance, node, parent);
    } catch (Exception e) {
      WXLogUtils.e("WXComponentFactory Exception type:[" + node.getType() + "] ", e);
    }

    return null;
}

這裡關鍵還是這一句程式碼,並且我們就很熟悉了(WXComponentRegistry):

IFComponentHolder holder = WXComponentRegistry.getComponent(node.getType());
//建立例項
try {
      return holder.createInstance(instance, node, parent);
    } catch (Exception e) {
      WXLogUtils.e("WXComponentFactory Exception type:[" + node.getType() + "] ", e);
}

我們看一下getComponent的實現:

//WXComponentRegistry中實現
public static IFComponentHolder getComponent(String type) {
    return sTypeComponentMap.get(type);
}

這裡一目瞭然,在註冊時我們就是把type以及Holder放入sTypeComponentMap中的,這裡我們只不過根據type取出而已,那最終還是呼叫了holder的createInstance函式,我們以text為例,看一下其實現(我們知道Holder的實現類是SimpleComponentHolder):

@Override
  public synchronized WXComponent createInstance(WXSDKInstance instance, WXDomObject node, WXVContainer parent) throws IllegalAccessException, InvocationTargetException, InstantiationException {
    WXComponent component = mCreator.createInstance(instance,node,parent);

    component.bindHolder(this);
    return component;
}

這裡的mCreator就是我們之前分析過的Creater,這樣一來整個流程我們就清晰了。

2、使用註冊過的Module

我們知道,Module的主要作用是擴充套件客戶端的能力,實際上就是一個通訊過程,JS使用Bridge來呼叫Android端的方法,所以我們要看過程,實際上還是從WXBridge開始,也就是之前我們一直提到的關鍵函式callNative,這個函式是JS ---> Android的通訊橋樑,會多次提及:

/**
   * JavaScript uses this methods to call Android code
   *
   * @param instanceId
   * @param tasks
   * @param callback
   */
  public int callNative(String instanceId, byte [] tasks, String callback) {
    try {
     return callNative(instanceId,(JSONArray)WXJsonUtils.parseWson(tasks),callback);
    } catch (Throwable e) {
      //catch everything during call native.
      // if(WXEnvironment.isApkDebugable()){
      WXLogUtils.e(TAG,"callNative throw exception:"+e.getMessage());
      // }
      return 0;
    }
}

然後我們可以繼續看到,這個任務最終還是交給了WXBridgeManager來做,實現在了callNative中:

//WXBridgeManager:callNative方法中
...//省略非關鍵部分      
      try {
        JSONObject task;
        for (int i = 0; i < size; ++i) {
          task = (JSONObject) array.get(i);
          if (task != null && WXSDKManager.getInstance().getSDKInstance(instanceId) != null) {
            Object target = task.get(MODULE);
            if (target != null) {
              //根據target來判斷,區分特殊的dom和普通模組
              if (WXDomModule.WXDOM.equals(target)) {
                WXDomModule dom = getDomModule(instanceId);
                dom.callDomMethod(task, parseNanos);
              } else {
                JSONObject optionObj = task.getJSONObject(OPTIONS);
                callModuleMethod(instanceId, (String) target,
                    (String) task.get(METHOD), (JSONArray) task.get(ARGS), optionObj);
              }
            } else if (task.get(COMPONENT) != null) {
              //call component
              WXDomModule dom = getDomModule(instanceId);
              dom.invokeMethod((String) task.get(REF), (String) task.get(METHOD), (JSONArray) task.get(ARGS));
            } else {
              throw new IllegalArgumentException("unknown callNative");
            }
          }
        }
      } catch (Exception e) {
        ...//錯誤處理
      }

在這裡我們就可以看到我們之前分析的模組和dom的區別,對於dom模組的方法呼叫是特殊處理的,這部分我們之前在(二)中分析過,這裡不再贅述。我們還是看到關鍵的callModuleMethod方法上,這裡呼叫之後實際上可以看到還是交給了WXModuleManager來執行:

return WXModuleManager.callModuleMethod(instanceId, moduleStr, methodStr, args);

這裡的引數非常清晰:

(1)instanceId:Weex例項的id

(2)moduleStr:module名稱

(3)methodStr:method名稱

(4)args:引數

我們看一下這個callModuleMethod函式按順序做了哪些事情:

1、獲取Module對應的Factory

ModuleFactory factory = sModuleFactoryMap.get(moduleStr);
if(factory == null){
    WXLogUtils.e("[WXModuleManager] module factory not found.");
    return null;
}

首先這個sModuleFactoryMap我們在註冊時以及見過了,註冊Module在Native層面其實就是建立了一個Factory,按照名稱放入了sModuleFactoryMap中,這裡按照名稱取了出來。

2、獲取Module例項

final WXModule wxModule = findModule(instanceId, moduleStr,factory);
if (wxModule == null) {
    return null;
}

這裡實際上是交給了findModule方法,我們接著看:

private static WXModule findModule(String instanceId, String moduleStr,ModuleFactory factory) {
    // find WXModule
    WXModule wxModule = sGlobalModuleMap.get(moduleStr);

    //not global module
    if (wxModule == null) {
      //獲取對應例項的module map(1)
      Map<String, WXModule> moduleMap = sInstanceModuleMap.get(instanceId);
      if (moduleMap == null) {
        moduleMap = new ConcurrentHashMap<>();
        sInstanceModuleMap.put(instanceId, moduleMap);
      }
      wxModule = moduleMap.get(moduleStr);
      //獲取不到,則對應新建立的情況(2)
      if (wxModule == null) {
        try {
          //使用factory建立例項
          wxModule = factory.buildInstance();
          wxModule.setModuleName(moduleStr);
        } catch (Exception e) {
          WXLogUtils.e(moduleStr + " module build instace failed.", e);
          return null;
        }
        moduleMap.put(moduleStr, wxModule);
      }
    }

    return wxModule;
}

這裡其實很直接,考慮到了一個直接涉及的點:global

1、如果是global的Module,是全域性唯一的,在註冊時就已經建立,這裡只是把它從sGlobalModuleMap中取出來而已;

2、如果不是global的情況,那麼還要注意一下這裡的判斷:

(1)即使不是global的module,對於同一個weex例項而言也是唯一的,在sInstanceModuleMap中,我們可以看出來一個weex例項對應的module列表是固定的,建立了的新的module就會放入這個表中。而在獲取module是,也會首先從這個列表中取

(2)取不到則是新建立的情況,則呼叫factory建立例項,這個Factory就是我們之前說的TypeModuleFactory,而buildInstance也很簡單:

public T buildInstance() throws IllegalAccessException, InstantiationException {
    return mClazz.newInstance();
}

3、獲取Weex例項,並設定給module例項

WXSDKInstance instance = WXSDKManager.getInstance().getSDKInstance(instanceId);
wxModule.mWXSDKInstance = instance;

4、獲取方法Invoker,dispatch方法呼叫

final Invoker invoker = factory.getMethodInvoker(methodStr);
...//省去效能分析和錯誤處理
return dispatchCallModuleMethod(instance,wxModule,args,invoker);

我們首先看一下是如何獲取方法的Invoker的(TypeModuleFactory中):

public Invoker getMethodInvoker(String name) {
    if (mMethodMap == null) {
      generateMethodMap();
    }
    return mMethodMap.get(name);
}

//generateMethodMap
private void generateMethodMap() {
    HashMap<String, Invoker> methodMap = new HashMap<>();
    try {
      for (Method method : mClazz.getMethods()) {
        // iterates all the annotations available in the method
        for (Annotation anno : method.getDeclaredAnnotations()) {
          if (anno != null) {
            if(anno instanceof JSMethod) {
              JSMethod methodAnnotation = (JSMethod) anno;
              String name = JSMethod.NOT_SET.equals(methodAnnotation.alias())? method.getName():methodAnnotation.alias();
              methodMap.put(name, new MethodInvoker(method, methodAnnotation.uiThread()));
              break;
            }else if(anno instanceof WXModuleAnno) {
              WXModuleAnno methodAnnotation = (WXModuleAnno)anno;
              methodMap.put(method.getName(), new MethodInvoker(method,methodAnnotation.runOnUIThread()));
              break;
            }
          }
        }
      }
    } catch (Throwable e) {
      WXLogUtils.e("[WXModuleManager] extractMethodNames:", e);
    }
    mMethodMap = methodMap;
}

這裡的做法很簡單明瞭,獲取module類的所有方法,如果是使用了JSMethod或者WXModuleAnno註解,就根據名字和對應的MethodInvoker放入mMethodMap中,實際上就是一個生成方法的方式。

所以說這裡的獲取方法Invoker即name對應的方法的Invoker。之後的步驟就是dispatch:

//in dispatchCallModuleMethod
instance.getNativeInvokeHelper().invoke(wxModule,invoker,args);

我們省去一些判斷,只看關鍵呼叫部分,這裡我們首先要知道,每個Weex例項都有自己的,在建立時初始化:

//init函式中,建構函式中呼叫
mNativeInvokeHelper = new NativeInvokeHelper(mInstanceId);

然後我們再看它是如何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(target + "Invoker " + invoker.toString() ,e);
          }
        }
      }, 0);
    } else {
      return invoker.invoke(target, params);
    }
    return null;
}

這裡很顯然,就是針對是否在UI執行緒中執行進行判斷,如果是UI執行緒中執行,就通過WXSDKManager去post,而如果不需要,直接在當前執行緒(Bridge Thread,也可以叫JS Thread)中呼叫即可。到這裡,我們已經真正看到了一個的Module方法是如何執行的了。

3、使用註冊過的DomObject

這裡就比較簡單了,和Component一樣,我們只要去看看DomObject樹的構建即可:

//CreateBodyAvtion的父類AbstractAddElementAction中
//addDomInternal
WXDomObject domObject = WXDomObject.parse(dom, instance, null);

而parse中的關鍵是根據dom的type建立DomObject例項:

WXDomObject domObject = WXDomObjectFactory.newInstance(type);

我們接著看:

public static @Nullable WXDomObject newInstance(String type) {
    ...//null處理
    Class<? extends WXDomObject> clazz = WXDomRegistry.getDomObjectClass(type);
    if (clazz == null) {
      ...//null處理
    }
    try {
      if (WXDomObject.class.isAssignableFrom(clazz)) {
        WXDomObject domObject = clazz.getConstructor()
            .newInstance();
        return domObject;
      }
    } catch (Exception e) {
      WXLogUtils.e("WXDomObjectFactory Exception type:[" + type + "] ", e);
    }
    return null;
}

果然,最終還是到了我們註冊時熟悉的類:WXDomRegistry,我們看一下如何獲取Class的:

public static Class<? extends WXDomObject> getDomObjectClass(String type) {
    ...//null處理
    Class<? extends WXDomObject> clazz = sDom.get(type);
    return clazz == null ? mDefaultClass : clazz;
}

果然還是從sDom根據type取出對應的WXDomObject子類,從而生成DomObject例項。

四、引用連結

用WEB技術棧開發NATIVE應用(一):WEEX SDK原理詳解

相關文章