React Native Android 源码框架浅析(主流程及 Java 与 JS 双边通信)
Posted 工匠若水
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了React Native Android 源码框架浅析(主流程及 Java 与 JS 双边通信)相关的知识,希望对你有一定的参考价值。
【工匠若水 http://blog.csdn.net/yanbober 未经允许严禁转载,请尊重作者劳动成果。私信联系我】
1 背景
有了前面《React Native Android 从学车到补胎和成功发车经历》和《React Native Android Gradle 编译流程浅析》两篇文章的学习我们 React Native 已经能够基本接入处理一些事情了,那接下来的事情就是渐渐理解 RN 框架的一些东西,以便裁剪和对 RN 有个更深入的认识,所以本篇总结了我这段时间阅读源码的一些感触,主要总结了 React Native 启动流程、JS 调用 Java 流程、Java 调用 JS 流程。
涉及到源码分析了,所以有必要先交代下相关源码版本,以便引来不必要疑惑,如下:
"dependencies": {
"react": "15.3.2",
"react-native": "0.37.0"
}
首先通过前面的踩坑经历和编译流程浅析(编译流程已经暴露很多细节)我们能意识到 React Native 的大致框架流程应该是如下这样的:
也就是说其实需要我们编写代码是 Java 端(少)和 JS 端(多),其他的基本不变的,作为桥梁的核心是 C/C++ 来处理的,同时 JS(JSX)代码又是通过 Virtual DOM 来进行虚拟适应的,所以才有了 React Native 官方放话的 Learn once, do anywhere. 之说。下面我们就来解析下这个神奇的 React Native android 框架吧。
【工匠若水 http://blog.csdn.net/yanbober 未经允许严禁转载,请尊重作者劳动成果。私信联系我】
2 RN 启动流程框架浅析
还记得我们在《React Native Android 从学车到补胎和成功发车经历》中是怎么集成的 RN 吗?集成 RN 无非就是通过继承 ReactActivity 或者自己通过 ReactRootView 进行处理,但是实质都是触发了 ReactRootView 的 startReactApplication 方法,所以我们整个启动流程的核心入口就是这玩意;下面为了一致,我们直接从 ReactActivityDelegate 类的 onCreate 方法进行启动分析(分析源码本来就比较枯燥,坚持看下去收获是巨大的),如下:
public class ReactActivityDelegate {
protected void onCreate(Bundle savedInstanceState) {
//权限弹窗判断
if (getReactNativeHost().getUseDeveloperSupport() && Build.VERSION.SDK_INT >= 23) {
......
}
//启动流程一定会执行的,mMainComponentName为我们设置的,与JS边保持一致
if (mMainComponentName != null) {
loadApp(mMainComponentName);
}
......
}
protected void loadApp(String appKey) {
if (mReactRootView != null) {
throw new IllegalStateException("Cannot loadApp while app is already running.");
}
//创建一个ReactRootView,实质是一个FrameLayout
mReactRootView = createRootView();
//重磅启动流程核心方法!!!!!!
mReactRootView.startReactApplication(
getReactNativeHost().getReactInstanceManager(),
appKey,
getLaunchOptions());
//把View设置进Activity
getPlainActivity().setContentView(mReactRootView);
}
}
可以看见,ReactActivityDelegate 只是一个抽出来的封装,上面的实质就是 new 了一个 ReactRootView(实质是 Android 的 FrameLayout),接着调用 ReactRootView 的 startReactApplication 方法,完事就像常规 Android 代码一样直接通过 Activity 的 setContentView 方法把 View 设置进去。所以可以看出来,RN 的神秘之处一定在于 ReactRootView 中,Activity 对于 RN 来说只是为了让 RN 依附符合 Android 的框架而已,所以说,说白了 RN 依旧是标准 Android,因此在我们集成开发中我们可以选择整个界面(包含多级跳转)都用 React Native 实现,或者一个 Android 现有界面中部分采用 React Native 实现,因为这货就是一个 View,爱咋咋地,具体如下所示:
既然明白了 RN 就是个 View,那就接着看看 ReactRootView 呗,如下:
/**
React Native 的 Root View,负责监听标准 Android 的 View 相关各种东东及事件分发和子 View 渲染等。
*/
public class ReactRootView extends SizeMonitoringFrameLayout implements RootView {
public void startReactApplication(ReactInstanceManager reactInstanceManager, String moduleName) {
startReactApplication(reactInstanceManager, moduleName, null);
}
public void startReactApplication(
ReactInstanceManager reactInstanceManager,
String moduleName,
@Nullable Bundle launchOptions) {
UiThreadUtil.assertOnUiThread();
......
//标记判断,初始化会走进来的
if (!mReactInstanceManager.hasStartedCreatingInitialContext()) {
mReactInstanceManager.createReactContextInBackground();
}
// We need to wait for the initial onMeasure, if this view has not yet been measured, we set which
// will make this view startReactApplication itself to instance manager once onMeasure is called.
if (mWasMeasured) {
attachToReactInstanceManager();
}
}
}
可以看见,ReactRootView 果然是个牛逼的类,我也不多解释了,大段的英文注释已经交代很清楚用途和地位了,我们直接看上面代码的 startReactApplication 方法吧,可以看见他又调用了一个三个参数的同名方法,具体这三个参数来历如下(也是我们自己集成 RN 时手动 builder 模式创建的):
1. reactInstanceManager: 大内总管接口类,提供一个构造者模式的初始化 Builder,实现类是 XReactInstanceManagerImpl,这类也是我们在集成 RN 时 new ReactRootView 的之前自己创建的。
2. moduleName: 与 JS 代码约定的 String 类型识别 name,JS 端通过 AppRegistry.registerComponent 方法设置这个 name,Java 端重写基类的 getMainComponentName 方法设置这个 name,这样两边入口就对上了。
3. launchOptions: 这里默认是 null 的,如果自己不继承 ReactActivity 而自己实现的话可以通过这个参数在 startActivity 时传入一些参数到 JS 代码,用来依据参数初始化 JS 端代码。
这些参数都初始化传递好了以后,可以看见接着调用了 mReactInstanceManager 的 createReactContextInBackground 方法,mReactInstanceManager 就是上面说的第一个参数,实质是通过一个构造者模式创建的,实现类是 XReactInstanceManagerImpl,所以我们直接跳到 XReactInstanceManagerImpl 的 createReactContextInBackground 方法看看,如下:
public void createReactContextInBackground() {
......
recreateReactContextInBackgroundInner();
}
private void recreateReactContextInBackgroundInner() {
UiThreadUtil.assertOnUiThread();
if (mUseDeveloperSupport && mJSMainModuleName != null) {
//如果是 dev 模式,BuildConfig.DEBUG=true就走这里,在线更新bundle,手机晃动出现调试菜单等等。
//这个路线属于RN调试流程原理,后面再写文章分析,这里我们抓住主线分析
......
return;
}
//非调试模式,即BuildConfig.DEBUG=false时执行
recreateReactContextInBackgroundFromBundleLoader();
}
private void recreateReactContextInBackgroundFromBundleLoader() {
//厉害了,word哥,在后台创建ReactContext,两个参数是重点。
//mJSCConfig.getConfigMap()默认是一个WritableNativeMap,在前面通过构造模式构造时通过Builder类的set方法设置。
//mJSBundleLoader是在前面通过构造模式构造时通过Builder类的多个setXXX方法均可设置的,
//最终在Builder中build方法进行判断处理,你可以自定义Loader,或者按照build方法规则即可,
//默认是JSBundleLoader.createAssetLoader静态方法返回的JSBundleLoader抽象类的实现类。
//自定义热更新时setJSBundleFile方法参数就是巧妙的利用这里是走JSBundleLoader.createAssetLoader还是JSBundleLoader.createFileLoader!!!!!!
recreateReactContextInBackground(
new JSCjavascriptExecutor.Factory(mJSCConfig.getConfigMap()),
mBundleLoader);
}
private void recreateReactContextInBackground(
JavaScriptExecutor.Factory jsExecutorFactory,
JSBundleLoader jsBundleLoader) {
UiThreadUtil.assertOnUiThread();
//封装一把,把两个参数封装成ReactContextInitParams对象
ReactContextInitParams initParams =
new ReactContextInitParams(jsExecutorFactory, jsBundleLoader);
if (mReactContextInitAsyncTask == null) {
//初始化进来一定会走啦,这货不就是创建一个AsyncTask,然后执行,同时传递封装的参数initParams给task。
// No background task to create react context is currently running, create and execute one.
mReactContextInitAsyncTask = new ReactContextInitAsyncTask();
mReactContextInitAsyncTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, initParams);
} else {
// Background task is currently running, queue up most recent init params to recreate context
// once task completes.
mPendingReactContextInitParams = initParams;
}
}
通过上面注释可以看见,我们《React Native Android 从学车到补胎和成功发车经历 #3-5 RN 集成后热更新核心思路》的热更新面纱也是从这个地方开始揭晓的,ReactInstanceManager 的 setJSBundleFile 如下:
/**
* Path to the JS bundle file to be loaded from the file system.
*
* Example: {@code "assets://index.android.js" or "/sdcard/main.jsbundle"}
*/
public Builder setJSBundleFile(String jsBundleFile) {
if (jsBundleFile.startsWith("assets://")) {
mJSBundleAssetUrl = jsBundleFile;
mJSBundleLoader = null;
return this;
}
return setJSBundleLoader(JSBundleLoader.createFileLoader(jsBundleFile));
}
我们先记住这个提示,后面再边分析主加载流程边插入介绍热更新的原理,所以我们还是把思路先回到 XReactInstanceManagerImpl 内部类的 ReactContextInitAsyncTask 上,如下:
private final class ReactContextInitAsyncTask extends
AsyncTask<ReactContextInitParams, Void, Result<ReactApplicationContext>> {
......
@Override
protected Result<ReactApplicationContext> doInBackground(ReactContextInitParams... params) {
......
try {
//异步执行的重量级核心方法createReactContext,创建ReactContext
JavaScriptExecutor jsExecutor = params[0].getJsExecutorFactory().create();
return Result.of(createReactContext(jsExecutor, params[0].getJsBundleLoader()));
} catch (Exception e) {
// Pass exception to onPostExecute() so it can be handled on the main thread
return Result.of(e);
}
}
@Override
protected void onPostExecute(Result<ReactApplicationContext> result) {
try {
//回到主线程执行的重量级核心方法setupReactContext,设置ReactContext相关
setupReactContext(result.get());
} catch (Exception e) {
mDevSupportManager.handleException(e);
} finally {
mReactContextInitAsyncTask = null;
}
......
}
......
}
可以看见,这就是典型的 AsyncTask 用法,我们先关注 doInBackground 方法,onPostExecute 方法等会回头再看;doInBackground 中首先把上面封装的 ReactContextInitParams 对象里 JavaScriptExecutor.Factory 工厂对象拿到,接着调用了工厂类的 create 方法创建 JavaScriptExecutor 抽象类的实现类 JSCJavaScriptExecutor 对象(因为上面分析 recreateReactContextInBackground 方法时第一个参数传入的是 new JSCJavaScriptExecutor.Factory(mJSCConfig.getConfigMap()))。接着往下执行了 createReactContext 方法,两个参数分别是前面封装的 ReactContextInitParams 对象中的 JSCJavaScriptExecutor 实例和 JSBundleLoader.createAssetLoader 静态方法创建的匿名内部类 JSBundleLoader 对象(热更新的话可能是另一个 Loader,参见前面分析);createReactContext 方法如下(有点长,但是句句核心啊):
private ReactApplicationContext createReactContext(
JavaScriptExecutor jsExecutor,
JSBundleLoader jsBundleLoader) {
......
//这货默认不就是上面刚刚分析的"assets://" + bundleAssetName么
mSourceUrl = jsBundleLoader.getSourceUrl();
List<ModuleSpec> moduleSpecs = new ArrayList<>();
Map<Class, ReactModuleInfo> reactModuleInfoMap = new HashMap<>();
//!!!Js层模块注册表,通过它把所有的JavaScriptModule注册到CatalystInstance。我们自定义的继承JavaScriptModule接口的Java端也是通过他来管理。
JavaScriptModuleRegistry.Builder jsModulesBuilder = new JavaScriptModuleRegistry.Builder();
//ContextWrapper封装类,其实就是getApplicationContext的封装,用在ReactContext中
final ReactApplicationContext reactContext = new ReactApplicationContext(mApplicationContext);
//如果是开发模式下ReactApplicationContext中有崩溃就捕获后交给mDevSupportManager处理(出错时弹个红框啥玩意的都是这货捕获的功劳)
if (mUseDeveloperSupport) {
//mDevSupportManager实例对象来源于XReactInstanceManagerImpl构造方法中一个工厂方法,实质由useDeveloperSupport决定DevSupportManager是哪个实例。
//非开发模式情况下mDevSupportManager为DisabledDevSupportManager实例,开发模式下为DevSupportManagerImpl实例。
reactContext.setNativeModuleCallExceptionHandler(mDevSupportManager);
}
......
try {
//创建CoreModulesPackage(ReactPackage),RN framework的核心Module Package,主要通过createNativeModules、createJSModules和createViewManagers等方法创建本地模块,JS模块及视图组件等。
//CoreModulesPackage封装了通信、调试等核心类。
CoreModulesPackage coreModulesPackage =
new CoreModulesPackage(this, mBackBtnHandler, mUIImplementationProvider);
//当我们设置mLazyNativeModulesEnabled=true(默认false)后启动可以得到延迟加载,感觉没啥卵用,没整明白有何用意。
//拼装来自coreModulesPackage的各种module了,JS的直接add进了jsModulesBuilder映射表、Native的直接保存在了moduleSpecs、reactModuleInfoMap中。
processPackage(
coreModulesPackage,
reactContext,
moduleSpecs,
reactModuleInfoMap,
jsModulesBuilder);
} finally {
Systrace.endSection(TRACE_TAG_REACT_JAVA_BRIDGE);
}
//加载我们自定义的ReactPackage,譬如自己封装的和MainReactPackage等,mPackages就来源于我们自己定义的;整个过程同上CoreModulesPackage,进行各种拼装module。
// TODO(6818138): Solve use-case of native/js modules overriding
for (ReactPackage reactPackage : mPackages) {
Systrace.beginSection(
TRACE_TAG_REACT_JAVA_BRIDGE,
"createAndProcessCustomReactPackage");
try {
processPackage(
reactPackage,
reactContext,
moduleSpecs,
reactModuleInfoMap,
jsModulesBuilder);
} finally {
Systrace.endSection(TRACE_TAG_REACT_JAVA_BRIDGE);
}
}
......
//!!!Java层模块注册表,通过它把所有的NativeModule注册到CatalystInstance。我们自定义的继承NativeModule接口的Java端也是通过他来管理。
NativeModuleRegistry nativeModuleRegistry;
try {
//new一个NativeModuleRegistry,其管理了NativeModule和OnBatchCompleteListener列表(JS调用Java结束时的回掉管理)。
nativeModuleRegistry = new NativeModuleRegistry(moduleSpecs, reactModuleInfoMap);
} finally {
Systrace.endSection(TRACE_TAG_REACT_JAVA_BRIDGE);
ReactMarker.logMarker(BUILD_NATIVE_MODULE_REGISTRY_END);
}
//依据外面是否设置mNativeModuleCallExceptionHandler异常捕获实现来决定exceptionHandler是使用外面的还是DevSupportManager。
NativeModuleCallExceptionHandler exceptionHandler = mNativeModuleCallExceptionHandler != null
? mNativeModuleCallExceptionHandler
: mDevSupportManager;
//!!!重点创建CatalystInstance的CatalystInstanceImpl实现实例
CatalystInstanceImpl.Builder catalystInstanceBuilder = new CatalystInstanceImpl.Builder()
.setReactQueueConfigurationSpec(ReactQueueConfigurationSpec.createDefault())
.setJSExecutor(jsExecutor)
.setRegistry(nativeModuleRegistry)
.setJSModuleRegistry(jsModulesBuilder.build())
.setJSBundleLoader(jsBundleLoader)
.setNativeModuleCallExceptionHandler(exceptionHandler);
......
final CatalystInstance catalystInstance;
try {
catalystInstance = catalystInstanceBuilder.build();
} finally {
Systrace.endSection(TRACE_TAG_REACT_JAVA_BRIDGE);
ReactMarker.logMarker(CREATE_CATALYST_INSTANCE_END);
}
if (mBridgeIdleDebugListener != null) {
catalystInstance.addBridgeIdleDebugListener(mBridgeIdleDebugListener);
}
//关联reactContext与catalystInstance
reactContext.initializeWithInstance(catalystInstance);
//通过catalystInstance加载js bundle文件
catalystInstance.runJSBundle();
return reactContext;
}
可以发现,上面这段代码做的事情真特么多,不过总的来说 createReactContext() 方法做的都是一些取数据组表放表的过程,核心就是通过 ReactPackage 实现类的 createNativeModules()、createJSModules() 等方法把所有 NativeModule 包装后放入 NativeModuleRegistry 及 JavaScriptModule 包装后放入 JavaScriptModuleRegistry,然后把这两张映射表交给 CatalystInstanceImpl,同时包装创建 ReactContext 对象,然后通过 CatalystInstanceImpl 的 runJSBundle() 方法把 JS bundle 文件的 JS 代码加载进来等待 Task 结束以后调用 JS 入口进行渲染 RN。既然这样就去看看 CatalystInstanceImpl 的 build 方法中调用的 CatalystInstanceImpl 构造方法到底干了哪些鸟事,如下:
public class CatalystInstanceImpl implements CatalystInstance {
......
// C++ parts
private final HybridData mHybridData;
private native static HybridData initHybrid();
private CatalystInstanceImpl(
final ReactQueueConfigurationSpec ReactQueueConfigurationSpec,
final JavaScriptExecutor jsExecutor,
final NativeModuleRegistry registry,
final JavaScriptModuleRegistry jsModuleRegistry,
final JSBundleLoader jsBundleLoader,
NativeModuleCallExceptionHandler nativeModuleCallExceptionHandler) {
FLog.d(ReactConstants.TAG, "Initializing React Xplat Bridge.");
//native C++方法,用来初始化JNI相关状态然后返回mHybridData。
mHybridData = initHybrid();
//创建ReactNative的三个线程nativeModulesThread和jsThread、uiThread,都是通过Handler来管理的。
mReactQueueConfiguration = ReactQueueConfigurationImpl.create(
ReactQueueConfigurationSpec,
new NativeExceptionHandler());
mBridgeIdleListeners = new CopyOnWriteArrayList<>();
mJavaRegistry = registry;
mJSModuleRegistry = jsModuleRegistry;
mJSBundleLoader = jsBundleLoader;
mNativeModuleCallExceptionHandler = nativeModuleCallExceptionHandler;
mTraceListener = new JSProfilerTraceListener(this);
//native C++方法,用来初始化Bridge。
initializeBridge(
new BridgeCallback(this),
jsExecutor,
mReactQueueConfiguration.getJSQueueThread(),
mReactQueueConfiguration.getNativeModulesQueueThread(),
mJavaRegistry.getModuleRegistryHolder(this));
mMainExecutorToken = getMainExecutorToken();
}
private native void initializeBridge(ReactCallback callback,
JavaScriptExecutor jsExecutor,
MessageQueueThread jsQueue,
MessageQueueThread moduleQueue,
ModuleRegistryHolder registryHolder);
......
}
刚刚分析 createReactContext() 方法的总结没错,CatalystInstanceImpl 这货就是个封装总管,负责了 Java 层代码到 JNI 封装初始化的任务和 Java 与 JS 调用的 Java 端控制中心。所以我们先看看调用 native initializeBridge 方法时传入的 5 个参数吧,分别如下:
1. callback参数: CatalystInstanceImpl 的内部静态实现类 BridgeCallback,负责相关接口回调回传。
2. jsExecutor参数: 前面分析的 XReactInstanceManagerImpl 中赋值为 JSCJavaScriptExecutor 实例,JSCJavaScriptExecutor 中也有自己的 native initHybrid 的 C++ 方法被初始化时调用,具体在 OnLoad.cpp 的 JSCJavaScriptExecutorHolder 类中。
3. jsQueue参数: 来自于 mReactQueueConfiguration.getJSQueueThread(),mReactQueueConfiguration就是 CatalystInstanceImpl 中创建的 ReactQueueConfigurationImpl.create(
ReactQueueConfigurationSpec,
new NativeExceptionHandler()); 第一个参数来自于 XReactInstanceManagerImpl 中 CatalystInstanceImpl 的建造者,实质为包装相关线程名字、类型等,然后通过 ReactQueueConfigurationImpl 的 create 创建对应线程的 Handler,这里就是名字为 js 的后台线程 Handler,第二个参数为异常捕获回调实现。
4. moduleQueue参数: 来自于 mReactQueueConfiguration.getNativeModulesQueueThread(),mReactQueueConfiguration就是 CatalystInstanceImpl 中创建的 ReactQueueConfigurationImpl.create(
ReactQueueConfigurationSpec,
new NativeExceptionHandler()); 第一个参数来自于 XReactInstanceManagerImpl 中 CatalystInstanceImpl 的建造者,实质为包装相关线程名字、类型等,然后通过 ReactQueueConfigurationImpl 的 create 创建对应线程的 Handler,这里就是名字为 native_modules 的后台线程 Handler,第二个参数为异常捕获回调实现。
5. registryHolder参数: mJavaRegistry 对象来自于 XReactInstanceManagerImpl 中 CatalystInstanceImpl 的建造者,通过 mJavaRegistry.getModuleRegistryHolder(this) 传递一个 Java 层的 ModuleRegistryHolder 实例到同名的 C++ 中,具体在 mJavaRegistry.getModuleRegistryHolder(this) 的返回值处为 return new ModuleRegistryHolder(catalystInstanceImpl, javaModules, cxxModules); 而 ModuleRegistryHolder 的构造方法中调用了 C++ 的 initHybrid(catalystInstanceImpl, javaModules, cxxModules); 方法。
CatalystInstanceImpl 这货会玩,自己在 Java 层直接把持住了 JavaScriptModuleRegistry 映射表,把 NativeModuleRegistry 映射表、BridgeCallback 回调、JSCJavaScriptExecutor、js 队列 MessageQueueThread、native 队列 MessageQueueThread 都通过 JNI 嫁接到了 C++ 中。那我们现在先把目光转移到 CatalystInstanceImpl.cpp 的 initializeBridge 方法上(关于 JNI 的 OnLoad 中初始化注册模块等等就不介绍了),如下:
void CatalystInstanceImpl::initializeBridge(
jni::alias_ref<ReactCallback::javaobject> callback,
// This executor is actually a factory holder.
JavaScriptExecutorHolder* jseh,
jni::alias_ref<JavaMessageQueueThread::javaobject> jsQueue,
jni::alias_ref<JavaMessageQueueThread::javaobject> moduleQueue,
ModuleRegistryHolder* mrh) {
......
// Java CatalystInstanceImpl -> C++ CatalystInstanceImpl -> Bridge -> Bridge::Callback
// --weak--> ReactCallback -> Java CatalystInstanceImpl
......
//instance_为ReactCommon目录下 Instance.h 中类的实例;JNI封装规则就不介绍了,之前写过文章的。
//第一个参数为JInstanceCallback实现类,父类在cxxreact/Instance.h中。
//第二个参数为JavaScriptExecutorHolder,实质对应java中JavaScriptExecutor,也就是上面分析java的initializeBridge方法第二个参数JSCJavaScriptExecutor。
//第三第四个参数都是java线程透传到C++,纯C++的JMessageQueueThread。
//第五个参数为C++的ModuleRegistryHolder的getModuleRegistry()方法。
instance_->initializeBridge(folly::make_unique<JInstanceCallback>(callback),
jseh->getExecutorFactory(),
folly::make_unique<JMessageQueueThread>(jsQueue),
folly::make_unique<JMessageQueueThread>(moduleQueue),
mrh->getModuleRegistry());
}
到此 CatalystInstance 的实例 CatalystInstanceImpl 对象也就初始化 OK 了,同时通过 initializeBridge 建立了 Bridge 连接。关于这个 Bridge 在RN 中是通过 libjsc.so 中 JSObjectRef.h 的 JSObjectSetProperty(m_context, globalObject, jsPropertyName, valueToInject, 0, NULL); 来关联的,这样就可以在 Native 设置 JS 执行,反之同理。
由于这一小节我们只讨论 RN 的加载启动流程,所以 initializeBridge 的具体实现我们下面分析互相通信交互时再仔细分析,故我们先把思路还是回到 XReactInstanceManagerImpl 中 createReactContext 方法的 reactContext.initializeWithInstance(catalystInstance); 一行,可以看见,这行代码意思就是将刚刚初始化的 catalystInstance 传递给全局唯一的 reactContext 对象,同时在 reactContext 中通过 catalystInstance 拿到 JS、Native、UI 几个 Thread 的引用,方便快速访问使用这些对象。接着调用了 catalystInstance.runJSBundle(); 方法,这个方法实现如下:
@Override
public void runJSBundle() {
......
mJSBundleHasLoaded = true;
//mJSBundleLoader就是前面分析的依据不同设置决定是JSBundleLoader的createAssetLoader还是createFileLoader等静态方法的匿名实现类。
// incrementPendingJSCalls();
mJSBundleLoader.loadScript(CatalystInstanceImpl.this);
......
}
通过注释我们假设 Loader 是默认的,也即 JSBundleLoader 类的如下方法:
public static JSBundleLoader createAssetLoader(
final Context context,
final String assetUrl) {
return new JSBundleLoader() {
@Override
public void loadScript(CatalystInstanceImpl instance) {
instance.loadScriptFromAssets(context.getAssets(), assetUrl);
}
@Override
public String getSourceUrl() {
return assetUrl;
}
};
}
可以看见,它实质又调用了 CatalystInstanceImpl 的 loadScriptFromAssets 方法,我们继续跟踪 CatalystInstanceImpl 的这个方法吧,如下:
native void loadScriptFromAssets(AssetManager assetManager, String assetURL);
loadScriptFromAssets 既然是一个 native 方法,我们去 CatalystInstanceImpl.cpp 看下这个方法的实现,如下:
void CatalystInstanceImpl::loadScriptFromAssets(jobject assetManager,
const std::string& assetURL) {
const int kAssetsLength = 9; // strlen("assets://");
//获取source路径名,不计前缀,这里默认就是index.android.bundle
auto sourceURL = assetURL.substr(kAssetsLength);
//assetManager是Java传递的AssetManager。
//extractAssetManager是JSLoader.cpp中通过系统动态链接库android/asset_manager_jni.h的AAssetManager_fromJava方法来获取AAssetManager对象的。
auto manager = react::extractAssetManager(assetManager);
//通过JSLoader对象的loadScriptFromAssets方法读文件,得到大字符串script(即index.android.bundle文件的JS内容)。
auto script = react::loadScriptFromAssets(manager, sourceURL);
//判断是不是Unbundle,这里不是Unbundle,因为打包命令我们用了react.gradle的默认bundle,没用unbundle命令(感兴趣的自己分析这条路线)。
if (JniJSModulesUnbundle::isUnbundle(manager, sourceURL)) {
instance_->loadUnbundle(
folly::make_unique<JniJSModulesUnbundle>(manager, sourceURL),
std::move(script),
sourceURL);
return;
} else {
//bundle命令打包的,所以走这里。
//instance_为ReactCommon目录下 Instance.h 中类的实例,前面分析过了。
instance_->loadScriptFromString(std::move(script), sourceURL);
}
}
看来还没到头,这货又走到了 ReactCommon 目录下 Instance 实例的 loadScriptFromString 方法去了(由此可以看出来前面 ReactNativeAndroid 目录下的 jni 代码都是 Android 平台特有的封装,直到 ReactCommon 才是通用的),如下:
//string为index.android.bundle内容。
//sourceURL在这里默认为index.android.bundle。
void Instance::loadScriptFromString(std::unique_ptr<const JSBigString> string,
std::string sourceURL) {
//callback_就是initializeBridge传进来的,实质实现是CatalystInstanceImpl的BridgeCallback。
//说白了就是回传一个状态,要开始搞loadScriptFromString了
callback_->incrementPendingJSCalls();
SystraceSection s("reactbridge_xplat_loadScriptFromString",
"sourceURL", sourceURL);
//厉害了,Word哥,年度大戏啊!
//nativeToJsBridge_也是Instance::initializeBridge方法里初始化的,实现在Common的NativeToJsBridge类里。
nativeToJsBridge_->loadApplication(nullptr, std::move(string), std::move(sourceURL));
}
妈的,没完没了了,继续跟吧,到 Common 的 NativeToJsBridge.cpp 看看 loadApplication 方法吧,如下:
//unbundle传入的是个空指针。
//startupScript为bundle文件内容。
//startupScript为bundle文件名。
void NativeToJsBridge::loadApplication(
std::unique_ptr<JSModulesUnbundle> unbundle,
std::unique_ptr<const JSBigString> startupScript,
std::string startupScriptSourceURL) {
//runOnExecutorQueue实质就是获取一个MessageQueueThread,然后在其线程中执行一个task。
runOnExecutorQueue(
m_mainExecutorToken,
[unbundleWrap=folly::makeMoveWrapper(std::move(unbundle)),
startupScript=folly::makeMoveWrapper(std::move(startupScript)),
startupScriptSourceURL=std::move(startupScriptSourceURL)]
(JSExecutor* executor) mutable {
auto unbundle = unbundleWrap.move();
if (unbundle) {
executor->setJSModulesUnbundle(std::move(unbundle));
}
//因为我们是bundle命令打包的,所以走这里继续执行!!!
executor->loadApplicationScript(std::move(*startupScript),
std::move(startupScriptSourceURL));
});
}
靠靠靠,还不到头,又特么绕到 JSExecutor 的 loadApplicationScript 方法里面去了,继续跟吧(这个 executor 是 runOnExecutorQueue 方法中回传的一个 map 中取的,实质是 OnLoad 中 JSCJavaScriptExecutorHolder 对应,也即 java 中 JSCJavaScriptExecutor,所以 JSExecutor 实例为 JSCExecutor.cpp 中实现),如下:
//script为bundle文件内容,sourceURL为bundle文件名
void JSCExecutor::loadApplicationScript(std::unique_ptr<const JSBigString> script, std::string sourceURL) throw(JSException) {
SystraceSection s("JSCExecutor::loadApplicationScript",
"sourceURL", sourceURL);
......
//把bundle文件和文件名等内容转换成js可以识别的String
String jsScript = jsStringFromBigString(*script);
String jsSourceURL(sourceURL.c_str());
//使用webkit JSC去真正解释执行Javascript了!
evaluateScript(m_context, jsScript, jsSourceURL);
//绑定桥,核心是通过getGlobalObject将JS与C++通过webkit JSC bind
bindBridge();
flush();
......
}
去他大爷的,没完没了了,继续看看 bindBridge() 方法和 flush() 方法,如下:
void JSCExecutor::bindBridge() throw(JSException) {
......
auto global = Object::getGlobalObject(m_context);
auto batchedBridgeValue = global.getProperty("__fbBatchedBridge");
......
auto batchedBridge = batchedBridgeValue.asObject();
m_callFunctionReturnFlushedQueueJS = batchedBridge.getProperty("callFunctionReturnFlushedQueue").asObject();
m_invokeCallbackAndReturnFlushedQueueJS = batchedBridge.getProperty("invokeCallbackAndReturnFlushedQueue").asObject();
//通过webkit JSC获取MessageQueue.js的flushedQueue
m_flushedQueueJS = batchedBridge.getProperty("flushedQueue").asObject();
m_callFunctionReturnResultAndFlushedQueueJS = batchedBridge.getProperty("callFunctionReturnResultAndFlushedQueue").asObject();
}
void JSCExecutor::flush() {
SystraceSection s("JSCExecutor::flush");
//m_flushedQueueJS->callAsFunction({})即调用MessageQueue.js的flushedQueue方法。
//即把JS端相关通信交互数据通过flushedQueue返回传给callNativeModules。
callNativeModules(m_flushedQueueJS->callAsFunction({}));
}
void JSCExecutor::callNativeModules(Value&& value) {
SystraceSection s("JSCExecutor::callNativeModules");
try {
//把JS端相关通信数据转为JSON格式字符串数据
auto calls = value.toJSONString();
//m_delegate实质为Executor.h中ExecutorDelegate类的实现类JsToNativeBridge对象。
//故callNativeModules为JsToNativeBridge.cpp中实现的方法,把calls json字符串pase成格式结构。
m_delegate->callNativeModules(*this, folly::parseJson(calls), true);
} catch (...) {
......
}
}
卧槽!又绕回到了 JsToNativeBridge.cpp 的 callNativeModules 方法,那就看下吧,如下:
//executor即为前面的JSCExecutor。
//calls为被解析OK的JS端JSON通信参数结构。
//isEndOfBatch通知是否一个批次处理OK了,这里传递了true进来,说明JS文件Loader OK了。
void callNativeModules(
JSExecutor& executor, folly::dynamic&& calls, bool isEndOfBatch) override {
//拿到token
ExecutorToken token = m_nativeToJs->getTokenForExecutor(executor);
//扔到nativeQueue的线程队列去等待执行
m_nativeQueue->runOnQueue([this, token, calls=std::move(calls), isEndOfBatch] () mutable {
// An exception anywhere in here stops processing of the batch. This
// was the behavior of the Android bridge, and since exception handling
// terminates the whole bridge, there's not much point in continuing.
for (auto& call : react::parseMethodCalls(std::move(calls))) {
//调用Native registry表中的java NativeMethod方法。
m_registry->callNativeMethod(
token, call.moduleId, call.methodId, std::move(call.arguments), call.callId);
}
//一些类似数据库事务操作的机制,用来告诉OK了
if (isEndOfBatch) {
m_callback->onBatchComplete();
m_callback->decrementPendingJSCalls();
}
});
}
终于尼玛明朗了,上面这段调用不就是前面分析的那个回调么,说白了就是 CatalystInstanceImpl.java 中 CatalystInstanceImpl 构造方法中调用 C++ 的 initializeBridge 方法时传入的第一个参数 BridgeCallback 么,也就是说 JS bundle 文件被加载完成以后 JS 端调用 Java 端时会触发 Callback 的 onBatchComplete 方法,这货最终又会触发 OnBatchCompleteListener 接口的 onBatchComplete 方法,这不就把 JS Bundle 文件加载完成以后回调 Java 通知 OK 了么,原来主要的流程是这么回事。为了接下来不迷糊,赶紧先来一把小梳理总结,用图说话,如下:
上面这幅图已经囊括了我们上面那些枯燥的启动流程的部分流程分析了,好了,从上图可以知道我们前面贴出来的 AsyncTask 的 onPostExecute 方法还没分析,所以我们把目光再回到 XReactInstanceManagerImpl 的那个 ReactContextInitAsyncTask 中,doInBackground 方法执行完成后返回了 Result 包装的 reactContext,所以我们看下 onPostExecute 方法中调用的核心方法 setupReactContext,如下:
private void setupReactContext(ReactApplicationContext reactContext) {
......
CatalystInstance catalystInstance =
Assertions.assertNotNull(reactContext.getCatalystInstance());
//执行Native Java Module 的 initialize
catalystInstance.initialize();
//重置DevSupportManager实现类的reactContext相关
mDevSupportManager.onNewReactContextCreated(reactContext);
//内存状态回调设置
mMemoryPressureRouter.addMemoryPressureListener(catalystInstance);
//置位生命周期
moveReactContextToCurrentLifecycleState();
//核心方法!!!!
for (ReactRootView rootView : mAttachedRootViews) {
attachMeasuredRootViewToInstance(rootView, catalystInstance);
}
......
}
到此我们再追一下 mAttachedRootViews 这个列表赋值的地方吧,依旧是这个类的 attachMeasuredRootView(ReactRootView rootView) 方法,然而这个方法唯一被调用的地方在 ReactRootView 的 attachToReactInstanceManager() 中,再次发现 attachToReactInstanceManager 又是在 ReactRootView 已经 measure 的情况下才会触发,所以也就是说 mAttachedRootViews 中保存的都是 ReactRootView。那我们继续回到 XReactInstanceManagerImpl 中 setupReactContext 方法的 attachMeasuredRootViewToInstance(rootView, catalystInstance); 里看看,如下:
private void attachMeasuredRootViewToInstance(
ReactRootView rootView,
CatalystInstance catalystInstance) {
......
//彻底reset ReactRootView中的UI
// Reset view content as it's going to be populated by the application content from JS
rootView.removeAllViews();
rootView.setId(View.NO_ID);
//通过UIManagerModule设置根布局为ReactRootView
UIManagerModule uiManagerModule = catalystInstance.getNativeModule(UIManagerModule.class);
int rootTag = uiManagerModule.addMeasuredRootView(rootView);
//设置相关tag
rootView.setRootViewTag(rootTag);
//把Java端启动传递的launchOptions包装成JS用的类型
@Nullable Bundle launchOptions = rootView.getLaunchOptions();
WritableMap initialProps = Arguments.makeNativeMap(launchOptions);
//获取我们startReactApplication设置的JS端入口name,继承ReactActivity的话值为getMainComponentName()设置的
String jsAppModuleName = rootView.getJSModuleName();
//包装相关参数,rootTag告知JS端Native端的ReactRootView是哪个
WritableNativeMap appParams = new WritableNativeMap();
appParams.putDouble("rootTag", rootTag);
appParams.putMap("initialProps", initialProps);
//核心大招!!!!!React Native真正的启动流程入口是被Java端在这里拉起来的!!!!!!!!
catalystInstance.getJSModule(AppRegistry.class).runApplication(jsAppModuleName, appParams);
......
}
坚持一下,分析源码就是个苦逼的过程,坚持下来就好了,马上看到希望了;我们知道 AppRegistry.class 是 JS 端暴露给 Java 端的接口方法,所以 catalystInstance.getJSModule(AppRegistry.class) 实质就桥接到 JS 端代码去了,那就去看看 AppRegistry.js 的代码吧,如下:
//JS端对应代码,注意这个变量上面的英文已经交代很详细啦
var AppRegistry = {
......
//我们JS端自己在index.android.js文件中调用的入口就是:
//AppRegistry.registerComponent('TestRN', () => TestRN);
registerComponent: function(appKey: string, getComponentFunc: ComponentProvider): string {
runnables[appKey] = {
run: (appParameters) =>
renderApplication(getComponentFunc(), appParameters.initialProps, appParameters.rootTag)
};
return appKey;
},
......
//上面java端 AppRegistry 调用的 JS 端就是这个方法,索引到我们设置的appkey=TestRN字符串的JS入口
runApplication: function(appKey: string, appParameters: any): void {
......
runnables[appKey].run(appParameters);
},
......
};
真他妈不容易啊,总算到头了,原来 React Native 是这么被启动起来的。现在回过头来看发现其实主启动流程也就那么回事,还以为很神秘嘻嘻的,现在总算被揭开了。总结一下吧,如下图所示即为整个 React Native 加载主流成的主要情况:
到这里 React Native 的启动流程就分析完了,不过,我猜你看到这里的时候一定会骂我,因为我知道上面的主流程中你会有很多疑惑,这也是我写这篇阅读 RN 源码总结最纠结的地方,因为想尽可能的将主加载流程和通信方式分开来分析,以便做到模块化理解,但是后来发现关联性又很强,揉一起分析更乱套,所以就有了这么一篇很长的文章,前面就当是主流程综述概要分析,细节在下面通信方式分析时会继续提及浅析,所以建议带着上面的疑惑继续向下看完这篇文章再回到 Part 2 RN 启动流程框架浅析 这一部分来看一遍,这样你的疑惑就全部揭开了。
【工匠若水 http://blog.csdn.net/yanbober 未经允许严禁转载,请尊重作者劳动成果。私信联系我】
3 RN Java 调用 JS 端框架浅析
这是一个悲伤的故事,看源码没有伴,RN 接入也在自己一个人搞,所以搞起来总是挺慢,好在一直在坚持,源码也断断续续在工作之余看了一个多星期,这篇文章也占用了我一个美好的周末时光,有种说不出来的感觉,唉,不扯了,我们现在来看看 RN 中 Java 是如何调用 JS 代码的。
首先,通过上面加载流程或者以前我们自定义 Java & JS 交互模块的经历我们知道 JS 端代码模块对应的 Java 端都是继承 JavaScriptModule 来实现的(可以看上面 reactPackage.createJSModules() 方法,返回的是 JS 端给 Java 端约定好的 JS 模块 Java 实现);要说 Java 端如何调用 JS 端代码就得有个例子,我们就拿上面启动流程中最后 CatalystInstanceImpl.getJSModule(AppRegistry.class).runApplication(jsAppModuleName, appParams); 拉起 JS 端 index.android.js 的 JS 组件入口来分析,这就是一个典型的 Java 端调用 JS 端代码的例子,首先我们可以知道 AppRegistry.java 是继承 JavaScriptModule 的,如下:
public interface AppRegistry extends JavaScriptModule {
void runApplication(String appKey, WritableMap appParameters);
void unmountApplicationComponentAtRootTag(int rootNodeTag);
void startHeadlessTask(int taskId, String taskKey, WritableMap data);
}
然后 AppRegistry.java 是在 CoreModulesPackage 的 createJSModules() 方法中被添加入列表的,CoreModulesPackage 又是在主启动流程的 processPackage() 方法中被包装后加入 JavaScriptModule
以上是关于React Native Android 源码框架浅析(主流程及 Java 与 JS 双边通信)的主要内容,如果未能解决你的问题,请参考以下文章
React Native Android入门实战及深入源码分析系列——React Native源码编译