Weex Android源碼解析(三)—— 進入正題
版本:0.18.0
自己對於Weex源碼學習的一些記錄,幫助理解,如有錯誤請指出,部分圖片以及描述來源於官方的源碼解析文章,文末會附上鏈接。
前面我們大體了解了Weex三個模塊中的前兩個:Bridge和Dom,但是我們還不太清楚一個非常重要的內容:渲染過程,也就是第三個模塊:Render。另外,我們知道了三個模塊的主要作用還完全不足以讓我們清晰地了解整個Weex的工作流程,所以在了解Render之前,我們首先要從整體上對Weex的工作原理有一定的了解,這樣幫助我們更好的理解之後的渲染流程。
一、Native SDK之外,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中@Overridepublic 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);}//WXDOMpublic static final String WXDOM = "dom";//METHODSpublic 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);}//generateMethodMapprivate 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 dispatchCallModuleMethodinstance.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中//addDomInternalWXDomObject 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原理詳解
推薦閱讀: