首页 前端知识 React Native Android 源码框架浅析(主流程及 Java 与 JS 双边通信)

React Native Android 源码框架浅析(主流程及 Java 与 JS 双边通信)

2024-05-14 23:05:33 前端知识 前端哥 878 736 我要收藏

}

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_refReactCallback::javaobject callback,

// This executor is actually a factory holder.

JavaScriptExecutorHolder* jseh,

jni::alias_refJavaMessageQueueThread::javaobject jsQueue,

jni::alias_refJavaMessageQueueThread::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(callback),

jseh->getExecutorFactory(),

folly::make_unique(jsQueue),

folly::make_unique(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(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 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 unbundle,

std::unique_ptr 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 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() 方法中被包装后加入 JavaScriptModuleRegistry 映射表的,JavaScriptModuleRegistry 映射表又被 Java 层的 CatalystInstanceImpl 接管。所以 Java 调用 JS 方法都是通过 CatalystInstanceImpl.getJSModule(class).methodXXX() 来执行的(我们自己模块调用的话是通过 ReactContext.getJSModule(),因为 ReactContext 在主启动流程中持有了 CatalystInstanceImpl 实例,所以 CatalystInstanceImpl 是不直接对外的),那我们就沿着这条线去观摩一把,如下 CatalystInstanceImpl.java 的 getJSModule 方法:

@Override

public T getJSModule(Class jsInterface) {

//mMainExecutorToken来自于native C++代码

return getJSModule(mMainExecutorToken, jsInterface);

}

@Override

public T getJSModule(ExecutorToken executorToken, Class jsInterface) {

//mJSModuleRegistry就是启动流程中processPackage()方法加进去交给CatalystInstanceImpl托管的JS代码映射表

return Assertions.assertNotNull(mJSModuleRegistry)

.getJavaScriptModule(this, executorToken, jsInterface);

}

接着去 JavaScriptModuleRegistry 映射表中看看 getJavaScriptModule() 方法,如下:

public synchronized T getJavaScriptModule(

CatalystInstance instance,

ExecutorToken executorToken,

Class moduleInterface) {

//module加载的缓存,加载过一次且缓存存在就直接从缓存取

//获取JavaScriptModule模块的方式,以AppRegistry模块获取为例,略叼,动态代理生成获取JS Module

JavaScriptModuleRegistration registration =

Assertions.assertNotNull(

mModuleRegistrations.get(moduleInterface),

“JS module " + moduleInterface.getSimpleName() + " hasn’t been registered!”);

JavaScriptModule interfaceProxy = (JavaScriptModule) Proxy.newProxyInstance(

moduleInterface.getClassLoader(),

new Class[]{moduleInterface},

new JavaScriptModuleInvocationHandler(executorToken, instance, registration));

instancesForContext.put(moduleInterface, interfaceProxy);

return (T) interfaceProxy;

}

从上面这段代码我们可以看见,getJSModule 获取 JsModule 的实质是通过 Java 的动态代理来实现的,同时 JavaScriptModuleRegistration 对 JavaScriptModule 的包装是为了检查实现 JavaScriptModule 接口的类不能存在重载,因为与 JS 端对应,JS 不支持。那我们不妨把视线转移到 JavaScriptModuleInvocationHandler 的 invoke 方法,可以发现实质是调用了 mCatalystInstance.callFunction(executorToken, mModuleRegistration.getName(), method.getName(), jsArgs); 语句,继续跟了一下发现调用了 CatalystInstanceImpl.java 的 native callJSFunction() 方法把相关参数传递到了 C++ 层,额,前面我们知道 CatalystInstanceImpl.cpp 只是 JNI 对于 Android 层适配的特有封装,实质对应了 Common 里 Instance.cpp,而这里的 native callJSFunction() 实质是通过 Instance::callJSFunction() 调用了 NativeToJsBridge::callFunction() 方法,进而放在了 JSCExecutor 的线程队列中触发了 JSCExecutor::callFunction() 方法,我们重点关注下 JSCExecutor::callFunction() 方法,如下:

void JSCExecutor::callFunction(const std::string& moduleId, const std::string& methodId, const folly::dynamic& arguments) {

auto result = [&] {

try {

//m_callFunctionReturnFlushedQueueJS来自于JSCExecutor::bindBridge()方法中初始化,JSCExecutor::bindBridge()是在前面分析启动流程时被调用的,实质是负责通过 Webkit JSC 拿到 JS 端代码的相关对象和方法引用,譬如拿到 JS 端 BatchedBridge.js 的 __fbBatchedBridge 属性与 MessageQueue.js 的 callFunctionReturnFlushedQueue 方法引用。此处实质为调用 MessageQueue.js 的 callFunctionReturnFlushedQueue 方法。

return m_callFunctionReturnFlushedQueueJS->callAsFunction({

Value(m_context, String::createExpectingAscii(moduleId)),

Value(m_context, String::createExpectingAscii(methodId)),

Value::fromDynamic(m_context, std::move(arguments))

});

} catch (…) {

std::throw_with_nested(

std::runtime_error("Error calling function: " + moduleId + “:” + methodId));

}

}();

//调用 native 模块,暂时忽略,下一小节解释,这里重点关注 Java 调用 JS 通信

callNativeModules(std::move(result));

}

既然都说了 m_callFunctionReturnFlushedQueueJS 是 JSCExecutor::bindBridge() 方法中初始化的,实质依赖 Webkit JSC 架起了 JS 代码与 C++ 的桥梁,那我们就去 JS 端看看 MessageQueue.js 的 callFunctionReturnFlushedQueue() 方法,如下:

callFunctionReturnFlushedQueue(module: string, method: string, args: Array) {

guard(() => {

this.__callFunction(module, method, args);

this.__callImmediates();

});

return this.flushedQueue();

}

继续跟下 this.__callFunction(module, method, args),如下:

__callFunction(module: string, method: string, args: Array) {

//_callableModules属性是通过registerCallableModule()方法添加的,而我们以AppRegistry.js为例,可以看见AppRegistry.js中有调用BatchedBridge.registerCallableModule(‘AppRegistry’, AppRegistry);把自己注册进去,BatchedBridge.js是与MessageQueue.js绑死的。

//说白了,这里就是在 JS 端的 Modules 中查映射表找到 AppRegistry.js

const moduleMethods = this._callableModules[module];

//拿到Java端调用的对应JS端指定Module的方法,譬如AppRegistry.js的runApplication()方法

const result = moduleMethods[method].apply(moduleMethods, args);

Systrace.endEvent();

return result;

}

握草,React Native Java 层调用 JS 层通信原来原来就这么回事;实质就是 Java 与 JS 端都准备好一个 Module 映射表,然后当 Java 端调用 JS 代码时 Java 端通过查表动态代理创建一个与 JS 对应的 Module 对象,当调用这个 Module 的方法时 Java 端通过动态代理的 invoke 方法触发 C++ 层,层层调用后通过 JSCExecutor 执行 JS 端队列中的映射查表找到 JS 端方法进行调用;同时会发现在 JSCExecutor 中每次 Java 调用 JS 之后会进行 Java 端的一个回调(从 JS 层的 MessageQueue.js 中获得累积的 JS Call)。为了不迷糊,我们对这一阶段总结如下图:

这里写图片描述

不多说了,一切都在上面图里,所以这时候如果你再回头去想第一部分 RN 启动流程源码浅析的疑惑点就完全明白了,也就串起来了。

【工匠若水 http://blog.csdn.net/yanbober 未经允许严禁转载,请尊重作者劳动成果。私信联系我】

4 RN JS 调用 Java 端框架浅析

=========================

上面已经总结了 React Native 的启动流程与 Java 端代码调用 JS 端代码的通信流程,下面我们再来分析 JS 端代码调用 Java 端代码的通信流程,这样整个 React Native 核心框架的三大问题都得到浅析了,方便日后裁剪 React Native。既然要分析 JS 调用 Java 端通信框架了,我们一样有必要先找到一个例子来做突破口,那我们就以 RN 官方的《Native Modules 》为例来说明,因为这是我们经常要封装且最熟悉的东西,可以看见文档中举了一个封装 ToastAndroid 的例子,那就拿它下手吧。我们在 JS 端使用 Android 端封装的 Java 模块时是如下这样用的:

//通过NativeModules拿到ToastAndroid

import { NativeModules } from ‘react-native’;

module.exports = NativeModules.ToastAndroid;

//使用的地方在JS中相关逻辑处调用,官方文档标准

import ToastAndroid from ‘./ToastAndroid’;

ToastAndroid.show(‘Awesome’, ToastAndroid.SHORT);

可以看见,JS 调用 Java 的第一步就是通过 JS 端的 NativeModules 拿到 相关的 Java 映射 Module,那我们就去 JS 的 NativeModules 里看看吧,如下:

//暂时只关注NativeModules.js中精彩的

//JSC全局唯一global添加一个属性,赋值为NativeModules.js中方法引用

global.__fbGenNativeModule = genModule;

let NativeModules : {[moduleName: string]: Object} = {};

//依据是否定义global.nativeModuleProxy属性来决定怎么获取。

//nativeModuleProxy属性实质是在主加载流程中JSCExecutor::JSCExecutor()构造时通过installGlobalProxy(m_context, “nativeModuleProxy”, exceptionWrapMethod<&JSCExecutor::getNativeModule>());创建的,所以当JS调用NativeModules时实质为JSCExecutor::getNativeModule()方法

if (global.nativeModuleProxy) {

NativeModules = global.nativeModuleProxy;

} else {

//只关注精彩的主线,DEV等模式的咱们不管

}

module.exports = NativeModules;

我们看下 JSCExecutor::getNativeModule() 方法,如下:

JSValueRef JSCExecutor::getNativeModule(JSObjectRef object, JSStringRef propertyName) {

//m_nativeModules来源于JsToNativeBridge的getModuleRegistry()方法,实质被转换为了JSCNativeModules.cpp

return m_nativeModules.getModule(m_context, propertyName);

}

继续跟踪上面 JSCNativeModules.cpp 的 getModule(m_context, propertyName) 可以发现其核心就是调用 JSCNativeModules::createModule(const std::string& name, JSContextRef context) 方法,而这个方法里实质是通过 JSC 获取全局设置的 JS 属性,然后通过 JNI 查找 Java 端映射表再触发 JS 端相关方法:

folly::Optional JSCNativeModules::createModule(const std::string& name, JSContextRef context) {

//JSC获取NativeModules.js中的global.__fbGenNativeModule = genModule;属性

m_genNativeModuleJS = global.getProperty(“__fbGenNativeModule”).asObject();

m_genNativeModuleJS->makeProtected();

}

//调用folly::Optional ModuleRegistry::getConfig(const std::string& name)获取Native的配置表

auto result = m_moduleRegistry->getConfig(name);

if (!result.hasValue()) {

return nullptr;

}

//JS端调用m_genNativeModuleJS对应方法

Value moduleInfo = m_genNativeModuleJS->callAsFunction({

Value::fromDynamic(context, result->config),

JSValueMakeNumber(context, result->index)

});

return moduleInfo.asObject().getProperty(“module”).asObject();

}

上面主要分两步,通过 C++ 获取 Java 层映射表、通过 JSC 调用 JS 端方法,我们先看下 ModuleRegistry::getConfig(const std::string& name) 源码,如下:

folly::Optional ModuleRegistry::getConfig(const std::string& name) {

//临界值判断

//modules_列表实质来源自上面加载流程中分析的CatalystInstanceImpl::initializeBridge();

//实质就是Java端在CatalystInstanceImpl中传递到C++的ModuleRegistryHolder::getModuleRegistry()方法;

//module实质就是ModuleRegistryHolder.cpp构造中把Java端传递过来的Module包装成CxxNativeModule、JavaNativeModule,这两实质是NativeModule C++子类

NativeModule* module = modules_[it->second].get();

// string name, object constants, array methodNames (methodId is index), [array promiseMethodIds], [array syncMethodIds]

//准备创建一个动态config对象

folly::dynamic config = folly::dynamic::array(name);

{

//module->getConstants()实际通过反射调用了Java层JavaModuleWrapper的getConstants方法

config.push_back(module->getConstants());

}

{

//module->getMethods()实际反射调用了Java层JavaModuleWrapper的getMethods方法,也就是BaseJavaModule.java的getMethods方法,这里会通过Java的findMethods()方法过滤出继承BaseJavaModule实现类中有@ReactMethod注解的方法!!!!!!!!(符合官方文档)

std::vector methods = module->getMethods();

folly::dynamic methodNames = folly::dynamic::array;

folly::dynamic promiseMethodIds = folly::dynamic::array;

folly::dynamic syncMethodIds = folly::dynamic::array;

for (auto& descriptor : methods) {

// TODO: #10487027 compare tags instead of doing string comparison?

methodNames.push_back(std::move(descriptor.name));

if (descriptor.type == “promise”) {

promiseMethodIds.push_back(methodNames.size() - 1);

} else if (descriptor.type == “sync”) {

syncMethodIds.push_back(methodNames.size() - 1);

}

}

if (!methodNames.empty()) {

config.push_back(std::move(methodNames));

if (!promiseMethodIds.empty() || !syncMethodIds.empty()) {

config.push_back(std::move(promiseMethodIds));

if (!syncMethodIds.empty()) {

config.push_back(std::move(syncMethodIds));

}

}

}

}

return ModuleConfig({it->second, config});

}

真相渐渐浮出水面了,我们继续回到前面 JSCNativeModules::createModule(const std::string& name, JSContextRef context) 中看看最后调用的 NativeModules.js 中的 global.__fbGenNativeModule = genModule 方法,如下:

function genModule(config: ?ModuleConfig, moduleID: number): ?{name: string, module?: Object} {

//通过JSC拿到C++中从Java端获取的Java的Module映射表包装配置类

const [moduleName, constants, methods, promiseMethods, syncMethods] = config;

const module = {};

//遍历构建module的属性方法

methods && methods.forEach((methodName, methodID) => {

const isPromise = promiseMethods && arrayContains(promiseMethods, methodID);

const isSync = syncMethods && arrayContains(syncMethods, methodID);

invariant(!isPromise || !isSync, ‘Cannot have a method that is both async and a sync hook’);

const methodType = isPromise ? ‘promise’ : isSync ? ‘sync’ : ‘async’;

//生成Module的函数方法

module[methodName] = genMethod(moduleID, methodID, methodType);

});

Object.assign(module, constants);

//返回一个

return { name: moduleName, module };

}

到此 JS 调用 Java 的准备工作已经就绪了(即 JS 是如何拿到 Native 映射表与方法的),JSC会准备好一个 JS 使用的 NativeModule 对象。

有了 JS 端的 Module 映射对象,访问就变得明朗了许多;还记得刚刚 NativeModules.js 中 genMethod() 方法生成的 JS Module 的方法属性吗?当我们 JS 真正调用 ToastAndroid.show(‘Awesome’, ToastAndroid.SHORT); 时实质就是调用 genMethod() 设置的方法;那我们就仔细看看这个 genMethod() 方法,可以发现它是依据调用的 JS 方法是不是 promise、sync 等走不同逻辑,但是主线核心都是调用了 BatchedBridge.enqueueNativeCall() 方法,那我们看看 MessageQueue.js 的 enqueueNativeCall 方法,如下:

enqueueNativeCall(moduleID: number, methodID: number, params: Array, onFail: ?Function, onSucc: ?Function) {

this._callID++;

//_queue是个队列,用于存放调用的模块、方法、参数信息

学习福利

【Android 详细知识点思维脑图(技能树)】

其实Android开发的知识点就那么多,面试问来问去还是那么点东西。所以面试没有其他的诀窍,只看你对这些知识点准备的充分程度。so,出去面试时先看看自己复习到了哪个阶段就好。

虽然 Android 没有前几年火热了,已经过去了会四大组件就能找到高薪职位的时代了。这只能说明 Android 中级以下的岗位饱和了,现在高级工程师还是比较缺少的,很多高级职位给的薪资真的特别高(钱多也不一定能找到合适的),所以努力让自己成为高级工程师才是最重要的。

这里附上上述的面试题相关的几十套字节跳动,京东,小米,腾讯、头条、阿里、美团等公司19年的面试题。把技术点整理成了视频和PDF(实际上比预期多花了不少精力),包含知识脉络 + 诸多细节。

由于篇幅有限,这里以图片的形式给大家展示一小部分。

网上学习 Android的资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。希望这份系统化的技术体系对大家有一个方向参考。
《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》点击传送门,即可获取!
methodName] = genMethod(moduleID, methodID, methodType);

});

Object.assign(module, constants);

//返回一个

return { name: moduleName, module };

}

到此 JS 调用 Java 的准备工作已经就绪了(即 JS 是如何拿到 Native 映射表与方法的),JSC会准备好一个 JS 使用的 NativeModule 对象。

有了 JS 端的 Module 映射对象,访问就变得明朗了许多;还记得刚刚 NativeModules.js 中 genMethod() 方法生成的 JS Module 的方法属性吗?当我们 JS 真正调用 ToastAndroid.show(‘Awesome’, ToastAndroid.SHORT); 时实质就是调用 genMethod() 设置的方法;那我们就仔细看看这个 genMethod() 方法,可以发现它是依据调用的 JS 方法是不是 promise、sync 等走不同逻辑,但是主线核心都是调用了 BatchedBridge.enqueueNativeCall() 方法,那我们看看 MessageQueue.js 的 enqueueNativeCall 方法,如下:

enqueueNativeCall(moduleID: number, methodID: number, params: Array, onFail: ?Function, onSucc: ?Function) {

this._callID++;

//_queue是个队列,用于存放调用的模块、方法、参数信息

学习福利

【Android 详细知识点思维脑图(技能树)】

[外链图片转存中…(img-8gSd1CJv-1715136549929)]

其实Android开发的知识点就那么多,面试问来问去还是那么点东西。所以面试没有其他的诀窍,只看你对这些知识点准备的充分程度。so,出去面试时先看看自己复习到了哪个阶段就好。

虽然 Android 没有前几年火热了,已经过去了会四大组件就能找到高薪职位的时代了。这只能说明 Android 中级以下的岗位饱和了,现在高级工程师还是比较缺少的,很多高级职位给的薪资真的特别高(钱多也不一定能找到合适的),所以努力让自己成为高级工程师才是最重要的。

这里附上上述的面试题相关的几十套字节跳动,京东,小米,腾讯、头条、阿里、美团等公司19年的面试题。把技术点整理成了视频和PDF(实际上比预期多花了不少精力),包含知识脉络 + 诸多细节。

由于篇幅有限,这里以图片的形式给大家展示一小部分。

[外链图片转存中…(img-s8ufmNI1-1715136549929)]

网上学习 Android的资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。希望这份系统化的技术体系对大家有一个方向参考。
《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》点击传送门,即可获取!

转载请注明出处或者链接地址:https://www.qianduange.cn//article/8669.html
评论
发布的文章

video 自定义视频播放控件

2024-05-26 01:05:25

HTML5 画布绘制海报

2024-05-26 01:05:13

HTML5学习(三)

2024-05-26 01:05:43

大家推荐的文章
会员中心 联系我 留言建议 回顶部
复制成功!