这是之前在android版刚出来不久时在内部发的一篇浅析,现在同步上来,这之间,版本由于快速变动或多或少有些许出入,但大体思路暂时未变。

react native的android版于上周发布,然后花了点时间试了下,从几个demo的体验来看都挺流畅,具体环境搭建与基本介绍,可以看看官方的文档说明。
之后周末又花了点时间看了下内部的实现,这里主要来简单介绍下java层是如何与js绑定的。

整体架构上,可以分为java层<->c++层<->js层,其中java层作为入口,主要是对整体流程的控制,模块的配置。c++层是对JavaScriptCore接口的封装(每个应用自己背一个js解释器),直接执行js脚本并获取返回值。js层则是界面的布局与事件的响应处理。

启动

MoviesActivity.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class MoviesActivity extends Activity implements DefaultHardwareBackBtnHandler {
private ReactInstanceManager mReactInstanceManager;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mReactInstanceManager = ReactInstanceManager.builder()
.setApplication(getApplication())
.setBundleAssetName("MoviesApp.android.bundle")
.setJSMainModuleName("Examples/Movies/MoviesApp.android")
.addPackage(new MainReactPackage())
.setUseDeveloperSupport(true)
.setInitialLifecycleState(LifecycleState.RESUMED)
.build();
((ReactRootView) findViewById(R.id.react_root_view))
.startReactApplication(mReactInstanceManager, "MoviesApp", null);
}

入口上,主要是在相应使用React的Activity中加入一个ReactRootView,在Activity的onCreate中,通过ReactRootView启动React。这里的addPackage是配置应用所需的java模块与js模块,这个后面会细说。然后React的启动中主要会构造React的context,加载模块配置表,初始化CatalystInstance(java与js调用的高层接口) 与ReactBridge(jni接口封装),最后通过AppRegistry调用到js层的入口完成启动。

ReactInstanceManager.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
private void attachMeasuredRootViewToInstance(
ReactRootView rootView,
CatalystInstance catalystInstance) {
UiThreadUtil.assertOnUiThread();
// Reset view content as it's going to be populated by the application content from JS
rootView.removeAllViews();
rootView.setId(View.NO_ID);
UIManagerModule uiManagerModule = catalystInstance.getNativeModule(UIManagerModule.class);
int rootTag = uiManagerModule.addMeasuredRootView(rootView);
@Nullable Bundle launchOptions = rootView.getLaunchOptions();
WritableMap initialProps = launchOptions != null
? Arguments.fromBundle(launchOptions)
: Arguments.createMap();
String jsAppModuleName = rootView.getJSModuleName();
WritableNativeMap appParams = new WritableNativeMap();
appParams.putDouble("rootTag", rootTag);
appParams.putMap("initialProps", initialProps);
catalystInstance.getJSModule(AppRegistry.class).runApplication(jsAppModuleName, appParams);
}

模块注册

React Native中,需要用户自己定义需要的配置ReactPackage,显式的声明哪些java类或js脚本是api,是两边都需要的。的然后在启动中注册后,会同时在java端与js端共用一份模块配置表,主要以{moduleID, methodID}定位到某个java模块或js模块的方法。

CoreModulesPackage.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
/* package */ class CoreModulesPackage implements ReactPackage {
private final ReactInstanceManager mReactInstanceManager;
private final DefaultHardwareBackBtnHandler mHardwareBackBtnHandler;
CoreModulesPackage(
ReactInstanceManager reactInstanceManager,
DefaultHardwareBackBtnHandler hardwareBackBtnHandler) {
mReactInstanceManager = reactInstanceManager;
mHardwareBackBtnHandler = hardwareBackBtnHandler;
}
@Override
public List<NativeModule> createNativeModules(
ReactApplicationContext catalystApplicationContext) {
return Arrays.<NativeModule>asList(
new AnimationsDebugModule(
catalystApplicationContext,
mReactInstanceManager.getDevSupportManager().getDevSettings()),
new AndroidInfoModule(),
new DeviceEventManagerModule(catalystApplicationContext, mHardwareBackBtnHandler),
new ExceptionsManagerModule(mReactInstanceManager.getDevSupportManager()),
new Timing(catalystApplicationContext),
...
}
@Override
public List<Class<? extends JavaScriptModule>> createJSModules() {
return Arrays.asList(
DeviceEventManagerModule.RCTDeviceEventEmitter.class,
JSTimersExecution.class,
RCTEventEmitter.class,
AppRegistry.class,
ReactNative.class,
DebugComponentOwnershipModule.RCTDebugComponentOwnership.class);
}
...

模块配置

NativeModuleRegistry.java
1
2
3
4
5
6
7
public class NativeModuleRegistry {
private final ArrayList<ModuleDefinition> mModuleTable;
private final Map<Class<NativeModule>, NativeModule> mModuleInstances;
private final String mModuleDescriptions;
private final ArrayList<OnBatchCompleteListener> mBatchCompleteListenerModules;
...

java模块注册表

JavaScriptModuleRegistry.java
1
2
3
4
5
/*package*/ class JavaScriptModuleRegistry {
private final HashMap<Class<? extends JavaScriptModule>, JavaScriptModule> mModuleInstances;
...
}

js模块注册表

ToastModule.java
1
2
3
4
5
6
7
8
9
10
11
12
13
public class ToastModule extends ReactContextBaseJavaModule {
public ToastModule(ReactApplicationContext reactContext) {
super(reactContext);
}
...
@ReactMethod
public void show(String message, int duration) {
Toast.makeText(getReactApplicationContext(), message, duration).show();
}
}

java模块主要是通过@ReactMethod注解表明该方法为可以导出到js的方法

AppRegistry.java
1
2
3
4
5
6
/**
* JS module interface - main entry point for launching react application for a given key.
*/
public interface AppRegistry extends JavaScriptModule {
void runApplication(String appKey, WritableMap appParameters);
}
JavaScriptModuleRegistry.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
private static class JavaScriptModuleInvocationHandler implements InvocationHandler {
private final CatalystInstance mCatalystInstance;
private final JavaScriptModuleRegistration mModuleRegistration;
public JavaScriptModuleInvocationHandler(
CatalystInstance catalystInstance,
JavaScriptModuleRegistration moduleRegistration) {
mCatalystInstance = catalystInstance;
mModuleRegistration = moduleRegistration;
}
@Override
public @Nullable Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
String tracingName = mModuleRegistration.getTracingName(method);
mCatalystInstance.callFunction(
mModuleRegistration.getModuleId(),
mModuleRegistration.getMethodId(method),
Arguments.fromJavaArgs(args),
tracingName);
return null;
}
}

js模块是继承JavaScriptModule的interface,与之对应的是在js侧有个同名的js脚本,运行时通过动态代理技术,生成实现,在模块配置表中查找到该js,然后发起调用。

CatalystInstance.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
private void initializeBridge(
JavaScriptExecutor jsExecutor,
NativeModuleRegistry registry,
JavaScriptModulesConfig jsModulesConfig,
JSBundleLoader jsBundleLoader) {
mCatalystQueueConfiguration.getJSQueueThread().assertIsOnThread();
Assertions.assertCondition(mBridge == null, "initializeBridge should be called once");
mBridge = new ReactBridge(
jsExecutor,
new NativeModulesReactCallback(),
mCatalystQueueConfiguration.getNativeModulesQueueThread());
mBridge.setGlobalVariable(
"__fbBatchedBridgeConfig",
buildModulesConfigJSONProperty(registry, jsModulesConfig));
jsBundleLoader.loadScript(mBridge);
}

将模块配置表作为全局变量导入到js层,

调用

java与js之前的调用,两边都是以{moduleID, methodID}的形式作为一个call,然后一端在之前的模块配置表里查找注册的模块与方法。

java调用js

是直接通过c++层对JavaScriptCore封装的接口调用到相应的脚本。

ReactBridge.java
1
2
3
4
5
6
public native void loadScriptFromAssets(AssetManager assetManager, String assetName);
public native void loadScriptFromNetworkCached(String sourceURL, @Nullable String tempFileName);
public native void callFunction(int moduleId, int methodId, NativeArray arguments);
public native void invokeCallback(int callbackID, NativeArray arguments);
public native void setGlobalVariable(String propertyName, String jsonEncodedArgument);
...

OnLoad.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
static void callFunction(JNIEnv* env, jobject obj, jint moduleId, jint methodId,
NativeArray::jhybridobject args) {
auto bridge = extractRefPtr<Bridge>(env, obj);
auto arguments = cthis(wrap_alias(args));
std::vector<folly::dynamic> call{
(double) moduleId,
(double) methodId,
std::move(arguments->array),
};
try {
bridge->executeJSCall("BatchedBridge", "callFunctionReturnFlushedQueue", std::move(call));
} catch (...) {
translatePendingCppExceptionToJavaException();
}
}

c++层,直接调用BatchedBridge.jsMessageQueue.js中的callFunctionReturnFlushedQueue,最终会加载js对应的模块并调用到相应方法。

MessageQueue.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
callFunctionReturnFlushedQueue(module, method, args) {
guard(() => this.__callFunction(module, method, args));
return this.flushedQueue();
}
...
__callFunction(module, method, args) {
BridgeProfiling.profile(() => `${module}.${method}(${stringifySafe(args)})`);
if (isFinite(module)) {
method = this._methodTable[module][method];
module = this._moduleTable[module];
}
if (__DEV__ && SPY_MODE) {
console.log('N->JS : ' + module + '.' + method + '(' + JSON.stringify(args) + ')');
}
module = this._require(module);
module[method].apply(module, args);
BridgeProfiling.profileEnd();
}

js调用java

React Native的设计是js不会直接调用java,而是将调用的call插入到一个js层的一个MessageQueue中,java过来调用。

MessageQueue.js
1
2
3
4
5
6
7
8
9
10
11
12
class MessageQueue {
constructor(remoteModules, localModules, customRequire) {
this.RemoteModules = {};
this._require = customRequire || require;
this._queue = [[],[],[]];
this._moduleTable = {};
this._methodTable = {};
this._callbacks = [];
this._callbackID = 0;
...

MessageQueue.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
__nativeCall(module, method, params, onFail, onSucc) {
if (onFail || onSucc) {
// eventually delete old debug info
(this._callbackID > (1 << 5)) &&
(this._debugInfo[this._callbackID >> 5] = null);
this._debugInfo[this._callbackID >> 1] = [module, method];
onFail && params.push(this._callbackID);
this._callbacks[this._callbackID++] = onFail;
onSucc && params.push(this._callbackID);
this._callbacks[this._callbackID++] = onSucc;
}
this._queue[MODULE_IDS].push(module);
this._queue[METHOD_IDS].push(method);
this._queue[PARAMS].push(params);
if (__DEV__ && SPY_MODE && isFinite(module)) {
console.log('JS->N : ' + this._remoteModuleTable[module] + '.' +
this._remoteMethodTable[module][method] + '(' + JSON.stringify(params) + ')');
}
}

具体是在java层主动调用或各种事件到来时(触摸,刷新,timer…),都会调用上文说的java层的callFunction,从而最终调用上文说到的callFunctionReturnFlushedQueue,注意该方法会在执行相应js调用后调用flushedQueue并返回MessageQueue.

MessageQueue.js
1
2
3
4
5
6
7
8
flushedQueue() {
BridgeProfiling.profile('JSTimersExecution.callImmediates()');
guard(() => JSTimersExecution.callImmediates());
BridgeProfiling.profileEnd();
let queue = this._queue;
this._queue = [[],[],[]];
return queue[0].length ? queue : null;
}

返回的queue格式化为json经由c++返回给java

Bridge.cpp
1
2
3
4
5
6
7
void executeJSCall(
const std::string& moduleName,
const std::string& methodName,
const std::vector<folly::dynamic>& arguments) {
auto returnedJSON = m_jsExecutor->executeJSCall(moduleName, methodName, arguments);
m_callback(parseMethodCalls(returnedJSON));
}

OnLoad.cpp
1
2
3
4
5
6
7
8
9
10
11
static void create(JNIEnv* env, jobject obj, jobject executor, jobject callback,
jobject callbackQueueThread) {
auto weakCallback = createNew<WeakReference>(callback);
auto weakCallbackQueueThread = createNew<WeakReference>(callbackQueueThread);
auto bridgeCallback = [weakCallback, weakCallbackQueueThread] (std::vector<MethodCall> calls) {
dispatchCallbacksToJava(weakCallback, weakCallbackQueueThread, std::move(calls));
};
auto nativeExecutorFactory = extractRefPtr<JSExecutorFactory>(env, executor);
auto bridge = createNew<Bridge>(nativeExecutorFactory, bridgeCallback);
setCountableForJava(env, obj, std::move(bridge));
}
CatalysInstance.java
1
2
3
4
5
6
7
8
9
10
11
12
13
private class NativeModulesReactCallback implements ReactCallback {
@Override
public void call(int moduleId, int methodId, ReadableNativeArray parameters) {
mCatalystQueueConfiguration.getNativeModulesQueueThread().assertIsOnThread();
// Suppress any callbacks if destroyed - will only lead to sadness.
if (mDestroyed) {
return;
}
mJavaRegistry.call(CatalystInstance.this, moduleId, methodId, parameters);
}

最终java层依旧是查表调用对应的方法。

线程

线程方面 主要分为了三种线程

  • UIQueueThread android本身的ui线程,最终绘制肯定还得在这里
  • NativeModulesQueueThread js调过来的native module的方法执行的线程
  • JSQueueThread js解释器线程
    CatalystQueueConfiguration.java
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    /**
    * Specifies which {@link MessageQueueThread}s must be used to run the various contexts of
    * execution within catalyst (Main UI thread, native modules, and JS). Some of these queues *may* be
    * the same but should be coded against as if they are different.
    *
    * UI Queue Thread: The standard Android main UI thread and Looper. Not configurable.
    * Native Modules Queue Thread: The thread and Looper that native modules are invoked on.
    * JS Queue Thread: The thread and Looper that JS is executed on.
    */
    public class CatalystQueueConfiguration {
    private final MessageQueueThread mUIQueueThread;
    private final MessageQueueThread mNativeModulesQueueThread;
    private final MessageQueueThread mJSQueueThread;

总结

以上就是React Native Android版java和js间相互调用的主要实现,从demo的体验感觉整体上比较流畅,起码简单的页面感知上和native区分不太开。而且只要reload一遍js bundle,不用重启不用切换,页面立即原地更新,这个应该是一个很大的卖点。

代码上整体分层挺清晰,集合了包括fresco/okhttp/botls/boost/folly等不少开源库,java层用了动态代理,c++层的代码是用c++11封装的,js中也是使用了不少es6的新特性,还有jsx,css layout什么的,模式上也是状态驱动UI,整体实现感觉很是前卫,但是要完全掌握可能要java/c++/js三修,对现有的开发模式是一个新的挑战。而且由于依赖库太多,导致安装包略肿大,光so就有4.5MB,这给集成到现有项目带来了不少问题。

另外,由于监听了Choreographer的事件,使用了些高版本的api,导致4.1以下不能使用React。

而且实际使用中,由于react本身其实主要还是帮助做绑定,导致稍微复杂一点的界面和逻辑,还是少不了native的大量参与,除非导出的组件已经足够用或者界面足够简单。

总的来说,虽然可能刚出来还存在不少问题,包大小与开发方式也需要进一步的探索,但react native的整体设计与实现还是很值得学习的,或许今后会带来app开发新的思路吧。

Comments