(源码版本:0.34,新版本(0.48)基本流程是不变的,建议跟着源码看看,哪个版本的倒影响不大)
这篇简单刨析一下React Native是怎么在Android上跑起来的,会从下面几个方面说说。

  • 启动流程
  • 通信机制
  • 事件驱动
  • 渲染原理
  • 脚本执行

启动流程

React NativeAndroid上启动是从ReactRootView.startReactApplication触发的,而ReactRootView是继承FrameLayout的,所以React NativeAndroid的操作都是在这个View中进行的。

1
startReactApplication(ReactInstanceManager reactInstanceManager, String moduleName, @Nullable Bundle launchOptions)

这个方法参数第一个ReactInstanceManager,实现是XReactInstanceManagerImpl,可以理解在应用层对RN的配置都是对这个类操作实现的。moduleName是要启动的RNComponentname,是在jsAppRegistry.registerComponent('xxx', () => App);定义的。最后的launchOptions是传过去的参数,可以在jsComponentprops中获取。

下一步到了mReactInstanceManager.createReactContextInBackground();是在后台线程中创建RNReactContext上下文对象,然后到

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
//JavaScriptExecutor 默认就是jsc,如果的debug在chrome上时候,就是v8。
//JSBundleLoader 有AssetLoader FileLoader CachedBundleFromNetworkLoader RemoteDebuggerBundleLoader 从不同的地方加载bundle
private void recreateReactContextInBackground(
JavaScriptExecutor.Factory jsExecutorFactory,
JSBundleLoader jsBundleLoader) {
UiThreadUtil.assertOnUiThread();
ReactContextInitParams initParams =
new ReactContextInitParams(jsExecutorFactory, jsBundleLoader);
if (mReactContextInitAsyncTask == null) {
// 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;
}
}

主要的创建工作就转移到了ReactContextInitAsyncTask这个AsyncTask里面,

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
@Override
protected Result<ReactApplicationContext> doInBackground(ReactContextInitParams... params) {
....
return Result.of(createReactContext(jsExecutor, params[0].getJsBundleLoader()));
....
}
private ReactApplicationContext createReactContext(
JavaScriptExecutor jsExecutor,
JSBundleLoader jsBundleLoader) {
...
NativeModuleRegistry.Builder nativeRegistryBuilder = new NativeModuleRegistry.Builder();//NativeModule的注册表
JavaScriptModuleRegistry.Builder jsModulesBuilder = new JavaScriptModuleRegistry.Builder();//jsModules的注册表
...打包定义的各种modules到上面的注册表...
//创建关键的CatalystInstanceImpl
CatalystInstanceImpl.Builder catalystInstanceBuilder = new CatalystInstanceImpl.Builder()
...
catalystInstance = catalystInstanceBuilder.build();
....
//扔到js线程中加载js脚本
catalystInstance.getReactQueueConfiguration().getJSQueueThread().callOnQueue(
new Callable<Void>() {
@Override
public Void call() throws Exception {
//让reactContext持有catalystInstance
reactContext.initializeWithInstance(catalystInstance);
...
catalystInstance.runJSBundle();
return null;
}
}).get();
}

CatalystInstanceImpl的构造函数中有

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
...
//native C++方法,用来初始化JNI相关状态然后返回mHybridData。具体在 OnLoad.cpp 的 JSCJavaScriptExecutorHolder 类中
mHybridData = initHybrid();
...
//初始化线程环境,包括和主线程绑定,JS线程,Native线程创建。
mReactQueueConfiguration = ReactQueueConfigurationImpl.create(
ReactQueueConfigurationSpec,
new NativeExceptionHandler());
...
initializeBridge(
new BridgeCallback(this),//CatalystInstanceImpl内部类,用于native对java的一些回调
jsExecutor,//jsc
mReactQueueConfiguration.getJSQueueThread(),//js线程队列
mReactQueueConfiguration.getNativeModulesQueueThread(),//native线程队列
mJavaRegistry.getModuleRegistryHolder(this));//nativemodules注册表
mMainExecutorToken = getMainExecutorToken();//貌似是用于切换jsExecutor的标记,后面版本删掉了。

然后就进入到了cpp层的CatalystInstanceImpl.cppinitializeBridge方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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) {
instance_->initializeBridge(folly::make_unique<JInstanceCallback>(callback),
jseh->getExecutorFactory(),
folly::make_unique<JMessageQueueThread>(jsQueue),
folly::make_unique<JMessageQueueThread>(moduleQueue),
mrh->getModuleRegistry());
}

然后有委托给了Instance.cppinitializeBridge方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
void Instance::initializeBridge(
std::unique_ptr<InstanceCallback> callback,
std::shared_ptr<JSExecutorFactory> jsef,
std::shared_ptr<MessageQueueThread> jsQueue,
std::unique_ptr<MessageQueueThread> nativeQueue,
std::shared_ptr<ModuleRegistry> moduleRegistry) {
callback_ = std::move(callback);
//在js线程中包装nativeQueue和创建nativeToJsBridge_,后者在双向bridge起作用,不要仅仅看名字,内部还有一个JsToNativeBridge
jsQueue->runOnQueueSync(
[this, &jsef, moduleRegistry, jsQueue,
nativeQueue=folly::makeMoveWrapper(std::move(nativeQueue))] () mutable {
nativeToJsBridge_ = folly::make_unique<NativeToJsBridge>(
jsef.get(), moduleRegistry, jsQueue, nativeQueue.move(), callback_);
});
CHECK(nativeToJsBridge_);
}

到这就看没了,再回到上面的catalystInstance.runJSBundle();FileLoader为例,最终走到native void loadScriptFromFile(String fileName, String sourceURL);进入CatalystInstanceImpl.cpp进而委托给Instance.cpp。预警。。下面是一大片的cpp代码

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
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
void Instance::loadScriptFromFile(const std::string& filename,
const std::string& sourceURL) {
...检测文件合法性等...
loadScriptFromString(std::move(buf), sourceURL);
}
void Instance::loadScriptFromString(std::unique_ptr<const JSBigString> string,
std::string sourceURL) {
callback_->incrementPendingJSCalls();//这个callback就是java层的CatalystInstanceImpl的BridgeCallback这个内部类。
...
nativeToJsBridge_->loadApplicationScript(std::move(string), std::move(sourceURL));
}
void NativeToJsBridge::loadApplicationScript(std::unique_ptr<const JSBigString> script,
std::string sourceURL) {
m_mainExecutor->loadApplicationScript(std::move(script), std::move(sourceURL));
}
void JSCExecutor::loadApplicationScript(std::unique_ptr<const JSBigString> script, std::string sourceURL) throw(JSException) {
...
//使用webkit JSC去真正解释执行Javascript了!
evaluateScript(m_context, jsScript, jsSourceURL);
//绑定桥,核心是通过getGlobalObject将JS与C++通过webkit JSC bind
bindBridge();
flush();//这里算是通知java,加载完js脚本
}
void JSCExecutor::bindBridge() throw(JSException) {
...下面都是通过jsc 获取js的一下属性,方法等...
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();
//这个比较重要 获取MessageQueue.js的flushedQueue 下面就用到
m_flushedQueueJS = batchedBridge.getProperty("flushedQueue").asObject();
}
//这个下面js->native的时候还会提到
void JSCExecutor::flush() {
...真的烦,绕来绕去 m_flushedQueueJS看上面
callNativeModules(m_flushedQueueJS->callAsFunction({}));
}
void JSCExecutor::callNativeModules(Value&& value) {
...
try {
auto calls = value.toJSONString();
//class JsToNativeBridge : public react::ExecutorDelegate
m_delegate->callNativeModules(*this, std::move(calls), true);
} catch (...) {
...
}
}
void callNativeModules(
JSExecutor& executor, std::string callJSON, bool isEndOfBatch) override {
ExecutorToken token = m_nativeToJs->getTokenForExecutor(executor);
m_nativeQueue->runOnQueue([this, token, callJSON=std::move(callJSON), isEndOfBatch] {
for (auto& call : react::parseMethodCalls(callJSON)) {
//快完了 这个是ModuleRegistry.cpp 是在initializeBridge间接创建包装nativemodule的
m_registry->callNativeMethod(
token, call.moduleId, call.methodId, std::move(call.arguments), call.callId);
}
if (isEndOfBatch) {
//又见到了这个callback
m_callback->onBatchComplete();
m_callback->decrementPendingJSCalls();
}
});
}
void ModuleRegistry::callNativeMethod(ExecutorToken token, unsigned int moduleId, unsigned int methodId,
folly::dynamic&& params, int callId) {
...
modules_[moduleId]->invoke(token, methodId, std::move(params));
}

看到最后一句就是要去调用nativeModule里面的方法了,具体在ModuleRegistryHolder.cppJavaNativeModule类和NewJavaNativeModule类,对应JavaJavaModuleWrapper.java,就是jni调用。

说到这里,现在只完成了bridge环境的初步搭建,把jsbundle扔到jsc里面,还没真正拉起React Native应用。还是回到上面那个AsyncTaskonPostExecute方法。看看执行完这么一大堆准备代码之后,是怎么拉起来整个应用的。

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
@Override
protected void onPostExecute(Result<ReactApplicationContext> result) {
....
setupReactContext(result.get());
}
private void setupReactContext(ReactApplicationContext reactContext) {
...各种listener回调,通知birdge就绪,reactContext创建完成
for (ReactRootView rootView : mAttachedRootViews) {
attachMeasuredRootViewToInstance(rootView, catalystInstance);
}
...各种listener回调,通知birdge就绪,reactContext创建完成
}
private void attachMeasuredRootViewToInstance(ReactRootView rootView,CatalystInstance catalystInstance) {
....
UIManagerModule uiManagerModule = catalystInstance.getNativeModule(UIManagerModule.class);
int rootTag = uiManagerModule.addMeasuredRootView(rootView);
rootView.setRootViewTag(rootTag);
@Nullable Bundle launchOptions = rootView.getLaunchOptions();
WritableMap initialProps = Arguments.makeNativeMap(launchOptions);
String jsAppModuleName = rootView.getJSModuleName();
WritableNativeMap appParams = new WritableNativeMap();
appParams.putDouble("rootTag", rootTag);
appParams.putMap("initialProps", initialProps);
//真正拉起react native 的地方
catalystInstance.getJSModule(AppRegistry.class).runApplication(jsAppModuleName, appParams);
}

再来详细说一下最后一句,(大量代码预警)

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
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
@Override
public <T extends JavaScriptModule> T getJSModule(ExecutorToken executorToken, Class<T> jsInterface) {
//进入JSModuleRegistry中
return Assertions.assertNotNull(mJSModuleRegistry)
.getJavaScriptModule(this, executorToken, jsInterface);
}
//JavaScriptModuleRegistry.java
public synchronized <T extends JavaScriptModule> T getJavaScriptModule(
CatalystInstance instance,
ExecutorToken executorToken,
Class<T> moduleInterface) {
HashMap<Class<? extends JavaScriptModule>, JavaScriptModule> instancesForContext =
mModuleInstances.get(executorToken);
if (instancesForContext == null) {
instancesForContext = new HashMap<>();
//缓存一下 方便后面再使用
mModuleInstances.put(executorToken, instancesForContext);
}
JavaScriptModule module = instancesForContext.get(moduleInterface);
if (module != null) {
//命中缓存 直接返回
return (T) module;
}
JavaScriptModuleRegistration registration =
...
//很明显 动态代理 重点关注JavaScriptModuleInvocationHandler的invoke方法
JavaScriptModule interfaceProxy = (JavaScriptModule) Proxy.newProxyInstance(
moduleInterface.getClassLoader(),
new Class[]{moduleInterface},
new JavaScriptModuleInvocationHandler(executorToken, instance, registration));
instancesForContext.put(moduleInterface, interfaceProxy);
return (T) interfaceProxy;
}
@Override
public @Nullable Object invoke(Object proxy, Method method, @Nullable Object[] args) throws Throwable {
....
//又跑到了CatalystInstanceImpl.java中。。。然后又桥接到了CatalystInstanceImpl.cpp中,同样会调用instance的对应方法,直接看吧
mCatalystInstance.callFunction(
executorToken,
mModuleRegistration.getName(),
method.getName(),
jsArgs
);
return null;
}
void Instance::callJSFunction(ExecutorToken token, std::string&& module, std::string&& method,
folly::dynamic&& params) {
callback_->incrementPendingJSCalls();//这个回调不多说
//....接着跟吧
nativeToJsBridge_->callFunction(token, std::move(module), std::move(method), std::move(params));
}
//又会进入executor->callFunction(module, method, arguments);
void JSCExecutor::callFunction(const std::string& moduleId, const std::string& methodId, const folly::dynamic& arguments) {
....
auto result = [&] {
try {
//被桥接到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的 这个会在后面再说一下
callNativeModules(std::move(result));
}
callFunctionReturnFlushedQueue(module, method, args) {
guard(() => {
//执行js的function
this.__callFunction(module, method, args);
this.__callImmediates();
});
//取出积攒在queue中的action返回给上面的,最终在java中执行
return this.flushedQueue();
}
__callFunction(module: string, method: string, args: any) {
...
//根据module名,方法名和参数执行js方法
const result = moduleMethods[method].apply(moduleMethods, args);
return result;
}
//那什么时候把js的module注册到moduleMethods中呢
//AppRegistry.js
BatchedBridge.registerCallableModule(
'AppRegistry',
AppRegistry
);
//BatchedBridge是啥?
const BatchedBridge = new MessageQueue(
() => global.__fbBatchedBridgeConfig,
serializeNativeParams
);
registerCallableModule(name, methods) {
this._callableModules[name] = methods;
}

这里就执行了AppRegistry.js的的runApplication方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
runApplication: function(appKey: string, appParameters: any): void {
...
runnables[appKey].run(appParameters);
},
//而runnables是在什么时候被添加的??下面
registerComponent: function(appKey: string, getComponentFunc: ComponentProvider): string {
runnables[appKey] = {
run: (appParameters) =>
renderApplication(getComponentFunc(), appParameters.initialProps, appParameters.rootTag)
};
return appKey;
},
//而registerComponent什么时候被调用的就不用说了吧

到此真正执行到了js脚本,开始执行Component的逻辑渲染,最终映射到NativeView上。后面会再详细说渲染的原理。同时会发现在 JSCExecutor 中每次 Java 调用 JS 之后会进行 Java 端的一个回调(从 JS 层的 MessageQueue.js 中获得累积的 JS Call)。

通信机制

上面关于java->js已经体现的差不多了,实质就是 JavaJS 端都准备好一个 Module 映射表,然后当 Java 端调用 JS 代码时 Java 端通过查表动态代理创建一个与 JS 对应的 Module 对象,当调用这个 Module 的方法时 Java 端通过动态代理的 invoke 方法触发 C++ 层,层层调用后通过 JSCExecutor 执行 JS 端队列中的映射查表找到 JS 端方法进行调用;js->java的调用会在渲染原理里面提到。

简单画了个图
init

btw:看完js->java原理之后,会发现其实所谓的双向通信,其实基本上(除了那个直接调用的java之外)都是java调用。这也体现了消息机制的两种实现方式,推和拉。推方式就是直接调用对应的方法,也可以理解为经典的观察者模式。而RN通信更像拉这种模式。双边的通信都是靠java端去拉队列中的action。拉比推有一个优势,就是可以解决背压的问题,不必去解决或者协调生产者生产事件的速度,而是根据消费者的速度去消费事件,而且这个消费的过程还是可以优化的,例如去压缩合并一些事件,一次执行。

渲染原理

现在以一个Image如何渲染到Native为例,说一下简单的流程。
当执行js的脚本时候,是不知道nativeModule的注册表的,因为nativeModule的注册表只保存在javacpp端,并没有直接传递到js端。所有当执行到

1
2
3
import {
Image,
} from 'react-native';

这时候js并不知道Image是什么,然后看一下代码

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
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
const ReactNative = {
...
get Image() { return require('Image'); },
...
}
...
module.exports = ReactNative;
//Image.android.js
var NativeModules = require('NativeModules');
//NativeModules.js
const NativeModules = {};
Object.keys(RemoteModules).forEach((moduleName) => {
Object.defineProperty(NativeModules, moduleName, {
configurable: true,
enumerable: true,
get: () => {
let module = RemoteModules[moduleName];
if (module && typeof module.moduleID === 'number' && global.nativeRequireModuleConfig) {
//nativeRequireModuleConfig映射到JSCExecutor.cpp
const config = global.nativeRequireModuleConfig(moduleName);
module = config && BatchedBridge.processModuleConfig(config, module.moduleID);
RemoteModules[moduleName] = module;
}
Object.defineProperty(NativeModules, moduleName, {
configurable: true,
enumerable: true,
value: module,
});
return module;
},
});
});
module.exports = NativeModules;
//cpp
JSCExecutor::nativeRequireModuleConfig->JsToNativeBridge::getModuleConfig->ModuleRegistry::getConfig
folly::dynamic ModuleRegistry::getConfig(const std::string& name) {
...
NativeModule* module = modules_[it->second].get();
...
//最终反射调用JavaModuleWrapper.java的getConstants
folly::dynamic constants = module->getConstants();
...
//最终反射调用JavaModuleWrapper.java的getMethods
//返回对应module中所有@ReactMethod注解的方法
std::vector<MethodDescriptor> methods = module->getMethods();
//modules_在哪赋值?
//ModuleRegistryHolder.cpp构造函数,这个类上面有提到,回去看看
//registry_ = std::make_shared<ModuleRegistry>(std::move(modules));
}

然后返回到NativeModules.js中,BatchedBridge.processModuleConfig
->_genModule->_genMethod。进一步处理一下。所以到现在,js获取到了Image这个module中所有方法和属性。

然后当调用Image中相关方法时候,其实就是调用上面_genMethod中的方法,在这个方法中,分promisesync其他调用类型,最终都是调用了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
__nativeCall(module, method, params, onFail, onSucc) {
...
this._queue[MODULE_IDS].push(module);
this._queue[METHOD_IDS].push(method);
this._queue[PARAMS].push(preparedParams);
...
//如果5ms内有多个方法调用就先待在队列里防止过高频率,否则调用C++的nativeFlushQueueImmediate方法
if (global.nativeFlushQueueImmediate &&
now - this._lastFlush >= MIN_TIME_BETWEEN_FLUSHES_MS) {
global.nativeFlushQueueImmediate(this._queue);
this._queue = [[], [], [], this._callID];
this._lastFlush = now;
}
}

上面把MODULE_IDSMETHOD_IDSPARAMS放到queue中,等待java的调用,至于什么时候会触发java的调用和为什么要这么设计,会在下面的事件驱动解释。调用JSCExecutor::flush()。还有就是直接调用cppnativeFlushQueueImmediate,最终这两种方式都是调用了callNativeModules,这个上面也说了,不再赘述啦。

下面再说一下Nativeview创建过程,这个过程中Viewtag起标记View的作用,从java拉起React NativeattachMeasuredRootViewToInstance方法中可以看到

1
2
appParams.putDouble("rootTag", rootTag);
appParams.putMap("initialProps", initialProps);

rootTag通过bridge带到了js端,js执行React逻辑后,要创建一个NativeView,同时也把这个rootTag带到java层,让java层知道,创建完一个View要添加到哪个根布局上。

这个rootTag的生成是有规则的,在UIManagerModule.addMeasuredRootView的时候会生成RootViewTag

1
2
final int tag = mNextRootViewTag;//默认是1
mNextRootViewTag += ROOT_VIEW_TAG_INCREMENT;//10

也就是默认的rootTag是1,后面每多创建一个+10,也就是类似1,11,21这样都是根布局的tag

再通过这个rootTagjs的传递简单说一下React.js的创建组件逻辑。从前面可以知道,拉起js后执行AppRegistry.js ::runApplication,进而执行到了renderApplication(getComponentFunc(), appParameters.initialProps, appParameters.rootTag)这个方法。这里可以看到从java传过来的两个参数,其中一个就是rootTag,这里默认就一个根布局,这里的rootTag==1,进而到了renderApplication.js

1
2
3
4
5
6
7
8
9
ReactNative.render(
<AppContainer>
<RootComponent
{...initialProps}
rootTag={rootTag}
/>
</AppContainer>,
rootTag
);

这里的AppContainer也是一个组件,是包裹在根布局的外面,用于debug的红盒等工具布局。再到了

1
2
3
4
//ReactNative.js
var render = function (element, mountInto, callback) {
return ReactNativeMount.renderComponent(element, mountInto, callback);
};

这里面的逻辑快到React的一些处理,这里不多赘述,其实还有很多关于React Native的处理,暂时忽略,分支太多太繁琐。简单说一下React Native组件可以分为两种

元组件:框架内置的,可以直接使用的组件。例如:View、Image等。它在React Native中用ReactNativeBaseComponent来描述。
复合组件:用户封装的组件,一般可以通过React.createClass()来构建,提供render()方法来返回渲染目标。它在React Native中用ReactCompositeComponent来描述。

具体组合的逻辑基本都在上面连个类里面。下面来到ReactNativeBaseComponent.jsmountComponent,根据上面的提示是可以跟到这里的。只挑简单的看,看这个方法里面的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
var tag = ReactNativeTagHandles.allocateTag();//给每个view生成一个唯一的tag
...
UIManager.createView(tag, this.viewConfig.uiViewClassName, nativeTopRootTag, updatePayload);
//ReactNativeTagHandles.js
allocateTag: function () {
//排除已经给分配给rootTag的 类似1,11,21
//下面的就是简单的自增,初始化是1
while (this.reactTagIsNativeTopRootID(ReactNativeTagHandles.tagCount)) {
ReactNativeTagHandles.tagCount++;
}
var tag = ReactNativeTagHandles.tagCount;
ReactNativeTagHandles.tagCount++;
return tag;
},

看名字也知道这里就到了创建View的地方,还有另外两个方法和这个差不多的,用来操作View,分别的updateViewmanageChildrenUIManager通过bridge可以映射到javaUIManagerModule.java,可以在duiyiing这个类里面找到对应的用@ReactMethod注解的方法,这个注解是干啥的,看上面有提到。这里只看createView

1
2
3
4
5
@ReactMethod
//创建view的tag,对应native的组件类名,要加入的根布局tag,创建view需要的参数
public void createView(int tag, String className, int rootViewTag, ReadableMap props) {
mUIImplementation.createView(tag, className, rootViewTag, props);
}

UIImplementation.java中把要创建的view包装成CSSNode,用于后面的在CssLayout中布局。然后会包装成一个CreateViewOperation加入到UIViewOperationQueue.javaArrayDeque<UIOperation> mNonBatchedOperations这个队列中。最后还是通过GuardedChoreographerFrameCallback这个垂直同步的回调中出队,执行。关于事件驱动还是看下面。还有 updateview setchilderen就不说了,很复杂。

事件驱动

在说React Native的事件驱动之前,先看一下这几篇
Android图形显示系统(一)
React Native 分析(二)事件驱动
Android中的SurfaceFlinger和Choreographer
了解一下垂直同步和在Android上的Choreographer,正因为React Native使用了Choreographer这个类,而这个类是在4.1加入的,所以RN-Android的最低兼容是4.1,而weex是最低兼容到4.0,是在4.0使用了handler延时来模拟垂直同步的效果。当然这也是老版本Android的做法。这也是为啥总是吐槽Android显得很卡,当然在5.0又引入了renderThread就更上了一个台阶,还有Android的属性动画也是靠这个驱动的。

下面简单贴一下Choreographer的注释,看看为啥跨平台的框架都会用到这个类

However, there are a few cases where you might want to use the functions of thechoreographer directly in your application. Here are some examples.

  • If your application does its rendering in a different thread, possibly using GL,or does not use the animation framework or view hierarchy at all and you want to ensure that it is appropriately synchronized with the display, then use
    {@link Choreographer#postFrameCallback}.

  • … and that’s about it.

  • Each {@link Looper} thread has its own choreographer. Other threads can post callbacks to run on the choreographer but they will run on the {@link Looper}to which the choreographer belongs.

    再看一下postFrameCallback注释

    Posts a frame callback to run on the next frame.The callback runs once then is automatically removed.

    React Native的使用主要在EventDispatcher的内部类private class ScheduleDispatchFrameCallback implements Choreographer.FrameCallbackReactChoreographer与它的内部类private class ReactChoreographerDispatcher implements Choreographer.FrameCallback,还有用于view或者动画的就不说了。

    现在举个例子,点击一下view,这个事件是怎么传递的,点击事件肯定发生在java端。在ReactRootViewdispatchJSTouchEvent方法

    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
    ...
    EventDispatcher eventDispatcher = reactContext.getNativeModule(UIManagerModule.class)
    .getEventDispatcher();
    mJSTouchDispatcher.handleTouchEvent(event, eventDispatcher);
    //JSTouchDispatcher.java
    public void handleTouchEvent(MotionEvent ev, EventDispatcher eventDispatcher) {
    //这里面分为down,up move 等事件类别
    mTargetTag = TouchTargetHelper.findTargetTagAndCoordinatesForTouch(
    ev.getX(),
    ev.getY(),
    mRootViewGroup,
    mTargetCoordinates,
    null);
    eventDispatcher.dispatchEvent(
    TouchEvent.obtain(
    mTargetTag,
    TouchEventType.START,
    ev,
    mGestureStartTime,
    mTargetCoordinates[0],
    mTargetCoordinates[1],
    mTouchEventCoalescingKeyHelper));
    }

    最终包装成一个TouchEvent调用eventDispatcher.dispatchEvent,这里面主要是

    1
    mEventStaging.add(event);//ArrayList<Event>

    把事件添加到一个待发送的列表里面。那什么是去处发送?是在ScheduleDispatchFrameCallback.doFrame

    1
    2
    3
    4
    5
    6
    7
    8
    @Override
    public void doFrame(long frameTimeNanos) {
    ....
    moveStagedEventsToDispatchQueue();
    ...
    mReactContext.runOnJSQueueThread(mDispatchEventsRunnable);
    }

    调用moveStagedEventsToDispatchQueue在这个方法里面会对event再做一些处理,例如压缩,合并事件等,然后又把处理完的事件放到Event[] mEventsToDispatch = new Event[16];中。而在DispatchEventsRunnablerun方法中

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    @Override
    public void run() {
    for (int eventIdx = 0; eventIdx < mEventsToDispatchSize; eventIdx++) {
    Event event = mEventsToDispatch[eventIdx];
    ....
    event.dispatch(mRCTEventEmitter);
    event.dispose();
    ...
    }
    }
    ->TouchEvent.dispatch->TouchesHelper.sendTouchEvent->rctEventEmitter.receiveTouches(
    type.getJSEventName(),
    pointers,
    changedIndices);

    RCTEventEmitter extends JavaScriptModule这个就是走上面的java->js的路子,动态代理->cpp->flush()->….

    简单点就是getJSModule后对js的方法调用都会触发上面MessageQueue.js的出队

    eventloop

    脚本执行

    这里简单说说React Nativejs引擎选择,都是webkitJSC,在iOS上是内置的,在Android上则是引入了一个完整的JSC,这也是为什么AndroidRN会大这么多的很重要的原因,至于为什么要引入一个完整的JSC而不是使用内置的js引擎,Android 4.4之前的android系统浏览器内核是WebKitAndroid4.4系统浏览器切换到了Chromium(内核是Webkit的分支Blink)。在Android平台已经启用V8作为JS引擎,Android 4.0以后只用到了JavaScriptCore中的WTF(Web Template Library)部分代码。

    至于为啥不都使用V8,这个都是iOS的锅,看看chromeiOS上就是个WebView套个壳。。。

    还有其他的跨平台框架,例如weex,在Android上使用的是V8。现在网上也有对RNAndroid上移植的V8版本。
    onesubone/react-native-android-v8
    React Native Android V8接入
    这个是基于0.46的版本,还是可以跑起来的,但是RN的速度瓶颈貌似并不在js引擎。。

    还有一点要吐槽就是每个jsbundle都包含了框架代码和业务代码,导致文件的有效利用率很低,看看隔壁的weex就做的很好,框架js代码直接包含在sdk中,只下发业务代码,这才是正常的做法。在RN这只能自己hook了。还有就是每个RN页面都是要重新初始化Bridge,但是这些bridge其实都是基本一样的,只需要把不同的jsbundle扔给JSC就好了,我测试了一下大概会快20%,但是因为缓存,也会造成一些内存泄露的问题(业务代码和具体页面过于绑定)。但是有时候甚至比重新初始化还慢,不知道为啥。。

    最后再贴一下简单画的思维导图吧
    思维导图在线地址
    React Native启动

    参考:

    facebook/react-native
    React Native Android 源码框架浅析(主流程及 Java 与 JS 双边通信)
    ReactNative源码篇
    吟游雪人

    ps:

    因为本人能力实在有限,上面很多都是连蒙带猜,算是个笔记性质的流水账,有用就看看,没用就算了,欢迎指出错误。

    pps

    这篇本该在两星期之前完成的工作,一直拖到了现在。(一是因为懒),二是因为不知道该怎么更好的表述出来,因为一直贴代码体验实在是不好。(虽然现在还是这样的,但是源码分析的不贴代码怎么写)。但是感觉再不写点出来,过段时间又忘了,索性写出来算了,也不管效果了。。。凑合看吧。