ReactNative For Android 框架启动核心路径剖析

版权声明:本文由王少鸣原创文章,转载请注明出处: 
文章原文链接:https://www.qcloud.com/community/article/144

来源:腾云阁 https://www.qcloud.com/community

前面给大家分析过 ReactNative For Android (RN4A) 的通信机制,这次我们从源码出发,分析下RN4A的启动过程。启动过程基于通信机制,涉及通信机制原理大家可以查看前一篇文章,本篇不赘述。

上面是2016 React.js Conf FB 工程师分享的RN启动时序图,整个过程比较清晰,先启动终端运行时,随后由终端上下文去启动JS的运行时,进而布局,最后再由终端进行渲染,最后将View添加到RootView上。那接下来,我们先理解几个概念,方便后续我们对整个启动过程的理解。

模块:

模块即暴露给调用方的API集合,在RN4A存在两种模块。

一种是Native层暴露给Js层的API集合模块,即NativeModule,如ToastModule,DialogModule,或是创建View的UIManagerModule。业务方可以通过实现NativeModule自定义模块,通过重写getName将模块名暴露给Js层,通过注解的方式将API暴露给Js层调用。

另一种是Js层暴露给Java层的API集合模块,即JavascriptModule,如DeviceEventEmitter,AppRegistry等。业务方可以通过继承JavaScriptModule接口自定义接口模块,申明与Js层相应的方法即可。

无论是NativeModule还是JavascriptModule,在Js层存在与之相互映射同名的Module,Js层通过require引用Module。

模块注册表:

各模块信息统一收集到模块注册表。同样,在RN4A中存在两种模块注册表,一是由集合所有Java层模块接口信息的NativeModuleRegistry,另一种是集合所有Js层模块接口信息的JavascriptModuleRegistry。在启动RN4A后,终端将注册表信息存入与前端互通的全局变量__fbBatchedBridgeConfig 中,使得Js层与Java层存在同样的模块注册表。

正如上面FB攻城狮提出的时序图,从终端启动,入口是ReactRootView.startReactApplication,在构造JavaScriptExecutor&JSBundleLoader后,进而通过ReactContextInitAsycnTask去创建ReactContext,这部分主要创建了NativeModules,JavaScriptModule及其对的注册表,负责Js与Java通信的高层接口CatalystInstance等。在创建完ReactContext后,通过CatalystInstance获取AppRegistry并调用其runApplication启动Js Application。整体流程如下:

接下来进入正题,从源码来分析RN4A的启动(为阅读方便,源码适当裁剪)

ReactInstanceManager createReactContextInBackground,通过AysncTask初始化ReactNative上下文。mJSModuleName是与前端约定好所要启动的JS Application Name。mLauncahOptions是终端启动前端Application可选的传入的参数。

1/** 2 * ReactRootView.java 3 */ 4public void startReactApplication( 5 ReactInstanceManager reactInstanceManager, 6 String moduleName, 7 @Nullable Bundle launchOptions) { 8 UiThreadUtil.assertOnUiThread(); 9 10 mReactInstanceManager = reactInstanceManager; 11 mJSModuleName = moduleName; 12 mLaunchOptions = launchOptions; 13 14 if (!mReactInstanceManager.hasStartedCreatingInitialContext()) { 15 mReactInstanceManager.createReactContextInBackground(); 16 } 17 18 if (mWasMeasured && mIsAttachedToWindow) { 19 mReactInstanceManager.attachMeasuredRootView(this); 20 mIsAttachedToInstance = true; 21 getViewTreeObserver().addOnGlobalLayoutListener(getKeyboardListener()); 22 } else { 23 mAttachScheduled = true; 24 } 25} 26`

createReactContextInBackground最终调用到recreateReactContextInBackgroundFromBundleFile。这里会创建两个Key Obj : JSCJavaScriptExecutor&JSBundleLoader。
JSCJavaScriptExecutor继承自JavaScriptExecutor,在JSCJavaScriptExecutor.class加载会加载ReactNative的SO,并且,在初始JSCJavaScriptExecutor时会调用initialze去初始C++层ReactNative与JSC的通信框架等。
JSBundleLoader缓存了JsBundle的信息,封装了上层加载JsBundle相关接口,CatalystInstance通过其间接调用ReactBridge去加载文件。

1/** 2 * ReactInstanceManagerImpl.java 3 */ 4private void recreateReactContextInBackgroundFromBundleFile() { 5 recreateReactContextInBackground( 6 new JSCJavaScriptExecutor.Factory(), 7 JSBundleLoader.createFileLoader(mApplicationContext, mJSBundleFile)); 8}

创建完JSCJavaScriptExecutor&JSBundleLoader后,execute ReactContextInitAsyncTask继续初始化ReactContext。

1/** 2 * ReactInstanceManagerImpl.java 3 */ 4private void recreateReactContextInBackground( 5 JavaScriptExecutor.Factory jsExecutorFactory, 6 JSBundleLoader jsBundleLoader) { 7 8 ReactContextInitParams initParams = 9 new ReactContextInitParams(jsExecutorFactory, jsBundleLoader); 10 if (!mIsContextInitAsyncTaskRunning) { 11 12 ReactContextInitAsyncTask initTask = new ReactContextInitAsyncTask(); 13 initTask.execute(initParams); 14 mIsContextInitAsyncTaskRunning = true; 15 } else { 16 mPendingReactContextInitParams = initParams; 17 } 18}

ReactContextInitAsyncTask为创建ReactContext的核心类,在执行初始化前会销毁先前的上下文,保证只存在一个上下文。随后,调用createReactContext进一步创建ReactContext。在创建完React Context后会调用setUpReactContext,进而通知DevSupportManager更新上下文,更新生命周期,将ReactRootView做为Root View传递给UIManagerModule,调用AppRegistry的runApplication去启动Js Application等。

1/** 2 * ReactInstanceManagerImpl$ReactContextInitAsynTask.java 3 */ 4private final class ReactContextInitAsyncTask extends 5 AsyncTask<ReactContextInitParams, Void, Result<ReactApplicationContext>> { 6 @Override 7 protected void onPreExecute() { 8 if (mCurrentReactContext != null) { 9 tearDownReactContext(mCurrentReactContext); 10 mCurrentReactContext = null; 11 } 12 } 13 14 @Override 15 protected Result<ReactApplicationContext> doInBackground(ReactContextInitParams... params) { 16 Assertions.assertCondition(params != null && params.length > 0 && params[0] != null); 17 try { 18 JavaScriptExecutor jsExecutor = params[0].getJsExecutorFactory().create(); 19 return Result.of(createReactContext(jsExecutor, params[0].getJsBundleLoader())); 20 } catch (Exception e) { 21 // Pass exception to onPostExecute() so it can be handled on the main thread 22 return Result.of(e); 23 } 24 } 25 26 @Override 27 protected void onPostExecute(Result<ReactApplicationContext> result) { 28 try { 29 setupReactContext(result.get()); 30 } catch (Exception e) { 31 mDevSupportManager.handleException(e); 32 } finally { 33 mIsContextInitAsyncTaskRunning = false; 34 } 35 36 // Handle enqueued request to re-initialize react context. 37 if (mPendingReactContextInitParams != null) { 38 recreateReactContextInBackground( 39 mPendingReactContextInitParams.getJsExecutorFactory(), 40 mPendingReactContextInitParams.getJsBundleLoader()); 41 mPendingReactContextInitParams = null; 42 } 43 } 44}

在CreateReactContext中,主要有以下5个key path:

  1. 通过Builder构建上文概念讲过的NativeModuleRegistry及JavaScriptModuleConfig;

  2. 创建ReactApplicationContext。ReactApplicationContext继承自ContextWrapper,主要缓存了Application Context,Activity Context,ReactNative处理消息的三个thread(下篇讲述),还有就是全局控制JS调用导致Native Module Crash的NativeModuleCallExceptionHandler,在初始化ReactInstanceManager的时候传入,并且要关闭DeveloperSupport后才可以启用,假如不传,则默认交由DevSupportManger去处理;

  3. 创建ReactPackage。ReactPackage主要通过createNativeModules、createJSModules和createViewManagers等API去创建本地模块,JS模块及视图组件等。ReactPackage分为framework的CoreModulesPackage和业务方可选的基础MainReactPackage,CoreModulesPackage封装了大部分通信,调试核心类,如UIManagerModule,这个负责控制Js层Dom到Native View的核心类;

  4. 创建CatalystInstance。CatalystInstance并不直接面向开发者,开发者通ReactInstanceManger间接操作CatalystInstance。CatalystInstance持有对ReactBridge的引用,主要通过ReactBridge这个JNI类去实现Java层与Js层的通信,ReactBridge由CatalystInstance的Constructor创建。同时初始化的时候调用了ReactQueueConfigurationSpec.createDefault创建了ReactNative通信的两个线程 JsQueueThread&NativeModulesQueueThread;

  5. 调用reactContext.initializeWithInstance进一步将创建完的CatalystInstance及线程等缓存在ReactContext中;

  6. 调用catalystInstance.runJSBundle加载解析Jsbundle;

    /**

    • ReactInstanceManagerImpl.java
    • @return instance of {@link ReactContext} configured a {@link CatalystInstance} set */ private ReactApplicationContext createReactContext( JavaScriptExecutor jsExecutor, JSBundleLoader jsBundleLoader) {

    mSourceUrl = jsBundleLoader.getSourceUrl(); NativeModuleRegistry.Builder nativeRegistryBuilder = new NativeModuleRegistry.Builder(); JavaScriptModulesConfig.Builder jsModulesBuilder = new JavaScriptModulesConfig.Builder();

    ReactApplicationContext reactContext = new ReactApplicationContext(mApplicationContext); if (mUseDeveloperSupport) { reactContext.setNativeModuleCallExceptionHandler(mDevSupportManager); }

    CoreModulesPackage coreModulesPackage = new CoreModulesPackage(this, mBackBtnHandler, mUIImplementationProvider); processPackage(coreModulesPackage, reactContext, nativeRegistryBuilder, jsModulesBuilder);

    for (ReactPackage reactPackage : mPackages) { processPackage(reactPackage, reactContext, nativeRegistryBuilder, jsModulesBuilder); }

    nativeModuleRegistry = nativeRegistryBuilder.build(); javaScriptModulesConfig = jsModulesBuilder.build();

    NativeModuleCallExceptionHandler exceptionHandler = mNativeModuleCallExceptionHandler != null ? mNativeModuleCallExceptionHandler : mDevSupportManager; CatalystInstanceImpl.Builder catalystInstanceBuilder = new CatalystInstanceImpl.Builder() .setReactQueueConfigurationSpec(ReactQueueConfigurationSpec.createDefault()) .setJSExecutor(jsExecutor) .setRegistry(nativeModuleRegistry) .setJSModulesConfig(javaScriptModulesConfig) .setJSBundleLoader(jsBundleLoader) .setNativeModuleCallExceptionHandler(exceptionHandler);

    CatalystInstance catalystInstance= catalystInstanceBuilder.build(); if (mBridgeIdleDebugListener != null) { catalystInstance.addBridgeIdleDebugListener(mBridgeIdleDebugListener); } reactContext.initializeWithInstance(catalystInstance); catalystInstance.runJSBundle();

    return reactContext; }

ReactBridge由CatalystInstance的Constructor创建。

1/** 2 * CatalystInstanceImpl.java 3 */ 4private CatalystInstanceImpl( 5 final ReactQueueConfigurationSpec ReactQueueConfigurationSpec, 6 final JavaScriptExecutor jsExecutor, 7 final NativeModuleRegistry registry, 8 final JavaScriptModulesConfig jsModulesConfig, 9 final JSBundleLoader jsBundleLoader, 10 NativeModuleCallExceptionHandler nativeModuleCallExceptionHandler) { 11 mReactQueueConfiguration = ReactQueueConfigurationImpl.create( 12 ReactQueueConfigurationSpec, 13 new NativeExceptionHandler()); 14 mBridgeIdleListeners = new CopyOnWriteArrayList<>(); 15 mJavaRegistry = registry; 16 mJSModuleRegistry = new JavaScriptModuleRegistry(CatalystInstanceImpl.this, jsModulesConfig); 17 mJSBundleLoader = jsBundleLoader; 18 mNativeModuleCallExceptionHandler = nativeModuleCallExceptionHandler; 19 mTraceListener = new JSProfilerTraceListener(); 20 21 try { 22 mBridge = mReactQueueConfiguration.getJSQueueThread().callOnQueue( 23 new Callable<ReactBridge>() { 24 @Override 25 public ReactBridge call() throws Exception { 26 Systrace.beginSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE, "initializeBridge"); 27 try { 28 return initializeBridge(jsExecutor, jsModulesConfig); 29 } finally { 30 Systrace.endSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE); 31 } 32 } 33 }).get(); 34 } catch (Exception t) { 35 throw new RuntimeException("Failed to initialize bridge", t); 36 } 37}

ReactBridge将注册表信息存入与前端互通的全局变量 __fbBatchedBridgeConfig 中,使得Js层与Java层存在同样的模块注册表。

1/** 2 * CatalystInstanceImpl.java 3 */ 4private ReactBridge initializeBridge( 5 JavaScriptExecutor jsExecutor, 6 JavaScriptModulesConfig jsModulesConfig) { 7 8 ReactBridge bridge = new ReactBridge(jsExecutor, new NativeModulesReactCallback(), 9 mReactQueueConfiguration.getNativeModulesQueueThread()); 10 11 Systrace.beginSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE, "setBatchedBridgeConfig"); 12 bridge.setGlobalVariable( 13 "__fbBatchedBridgeConfig", 14 buildModulesConfigJSONProperty(mJavaRegistry, jsModulesConfig)); 15 bridge.setGlobalVariable( 16 "__RCTProfileIsProfiling", 17 18 return bridge; 19}

调用catalystInstance.runJSBundle加载解析Jsbundle。假如在解析过程中出现Exception,统一交给NativeModuleCallExceptionHandler处理,建议开发者设置自己的NativeModuleCallExceptionHandler,可以归避部分Crash(SyntaxError: Unexpected token ‘<‘ 或 SyntaxError: Unexpected end of script)。

1/** 2 * CatalystInstanceImpl.java 3 */ 4public void runJSBundle() { 5 try { 6 mJSBundleHasLoaded = mReactQueueConfiguration.getJSQueueThread().callOnQueue( 7 new Callable<Boolean>() { 8 @Override 9 public Boolean call() throws Exception { 10 incrementPendingJSCalls(); 11 try { 12 mJSBundleLoader.loadScript(mBridge); 13 Systrace.registerListener(mTraceListener); 14 } catch (JSExecutionException e) { 15 mNativeModuleCallExceptionHandler.handleException(e); 16 } finally { 17 Systrace.endSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE); 18 } 19 return true; 20 } 21 }).get(); 22 } catch (Exception t) { 23 throw new RuntimeException(t); 24 } 25}

在创建完React Context后会执行ReactContextInitAsyncTask的onPostExecute,从而调用setUpReactContext,会将ReactRootView做为Root View传递给UIManagerModule,此后Js通过UIManager创建的View都会add到该View上。

1/** 2 * ReactInstanceManagerImpl.java 3 */ 4@Override 5public void attachMeasuredRootView(ReactRootView rootView) { 6 UiThreadUtil.assertOnUiThread(); 7 if(mIsNeedDetachView){ 8 Log.d(ReactConstants.QZONE_REACT_SRC_TAG,"attachMeasuredRootView do add"); 9 mAttachedRootViews.add(rootView); 10 // If react context is being created in the background, JS application will be started 11 // automatically when creation completes, as root view is part of the attached root view list. 12 if (!mIsContextInitAsyncTaskRunning && mCurrentReactContext != null) { 13 attachMeasuredRootViewToInstance(rootView, mCurrentReactContext.getCatalystInstance()); 14 } 15 }else{ 16 Log.d(ReactConstants.QZONE_REACT_SRC_TAG,"attachMeasuredRootView do nothing"); 17 } 18 19}

在绑定完RootView后,通过CatalystInstance获取AppRegistry这个JSModule后,进一步调用runApplication启动Js Application。

1/** 2 * ReactInstanceManagerImpl.java 3 */ 4private void attachMeasuredRootViewToInstance( 5 ReactRootView rootView, 6 CatalystInstance catalystInstance) { 7 8 rootView.removeAllViews(); 9 rootView.setId(View.NO_ID); 10 11 UIManagerModule uiManagerModule = catalystInstance.getNativeModule(UIManagerModule.class); 12 int rootTag = uiManagerModule.addMeasuredRootView(rootView); 13 @Nullable Bundle launchOptions = rootView.getLaunchOptions(); 14 WritableMap initialProps = launchOptions != null 15 ? Arguments.fromBundle(launchOptions) 16 : Arguments.createMap(); 17 String jsAppModuleName = rootView.getJSModuleName(); 18 19 WritableNativeMap appParams = new WritableNativeMap(); 20 appParams.putDouble("rootTag", rootTag); 21 appParams.putMap("initialProps", initialProps); 22 catalystInstance.getJSModule(AppRegistry.class).runApplication(jsAppModuleName, appParams); 23}

ReactNative中Java与Js通信不再赘述。至此,启动Js层AppRegistry的runApplication启动Js Application。

1/** 2* AppRegistry.js 3*/ 4runApplication: function(appKey: string, appParameters: any): void { 5 console.log( 6 'Running application "' + appKey + '" with appParams: ' + 7 JSON.stringify(appParameters) + '. ' + 8 '__DEV__ === ' + String(__DEV__) + 9 ', development-level warning are ' + (__DEV__ ? 'ON' : 'OFF') + 10 ', performance optimizations are ' + (__DEV__ ? 'OFF' : 'ON') 11 ); 12 invariant( 13 runnables[appKey] && runnables[appKey].run, 14 'Application ' + appKey + ' has not been registered. This ' + 15 'is either due to a require() error during initialization ' + 16 'or failure to call AppRegistry.registerComponent.' 17 ); 18 runnables[appKey].run(appParameters); 19},
点赞
收藏

评论区

加载中...

相关推荐

MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1

文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s

Oracle 分组与拼接字符串同时使用

SELECTT.,ROWNUMIDFROM(SELECTT.EMPLID,T.NAME,T.BU,T.REALDEPART,T.FORMATDATE,SUM(T.S0)S0,MAX(UPDATETIME)CREATETIME,LISTAGG(TOCHAR(

MySQL部分从库上面因为大量的临时表tmp_table造成慢查询

背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_

手写Java HashMap源码

HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程22

2020年前端实用代码段,为你的工作保驾护航

有空的时候,自己总结了几个代码段,在开发中也经常使用,谢谢。1、使用解构获取json数据let jsonData  id: 1,status: "OK",data: 'a', 'b';let  id, status, data: number   jsonData;console.log(id, status, number )

Android So动态加载 优雅实现与原理分析

背景:漫品Android客户端集成适配转换功能(基于目标识别(So库35M)和人脸识别库(5M)),导致apk体积50M左右,为优化客户端体验,决定实现So文件动态加载.!(https://oscimg.oschina.net/oscnet/00d1ff90e4b34869664fef59e3ec3fdd20b.png)点击上方“蓝字”关注我