React Native 的项目跑起来之后,最让人好奇的一件事是,你在 index.js 里就写了一行 AppRegistry.registerComponent,Android 那边一个 MainActivity 里就写了个返回字符串的方法,这两头是怎么对上的?中间隔着 Java、C++、JavaScript 三层,bundle 从哪儿读、JS 引擎什么时候创建、第一帧是谁触发的,光看业务代码是一点线索都没有。
这篇跟着源码把 Android 侧的冷启动完整走一遍。从 MainApplication 出发,一层层往下到 C++ 的 JSCExecutor,再从 JS 侧的 AppRegistry.js 折返回来完成首帧渲染。读完你应该能在脑子里画出这条完整的调用链,看到启动慢或者白屏的时候知道该往哪一段查。
在本篇文章中,我们将从浅入深,和大家一起学习以下知识:
JS侧的入口AppRegistry.registerComponent,那个字符串名字是干什么用的MainApplication和MainActivity各自负责什么,SoLoader在加载谁ReactActivity为什么把所有活都委托给ReactActivityDelegateReactRootView.startReactApplication的三个参数分别是什么ReactInstanceManager怎么在后台线程里创建ReactContextCatalystInstance构造时的initializeBridge到底桥接了什么JSBundleLoader在Debug和Release下分别从哪里读bundle- 下到
C++层,Instance.cpp到JSCExecutor.cpp这一段发生了什么 - 回到
Java侧,setupReactContext和runApplication怎么触发首帧 - 这条链路在新架构下变成了什么样
# 一、先给一张地图
具体的类名很多,先把主干记住,后面看细节才不会迷路。
整条链路可以概括成一句话,Java 侧准备好环境和模块表,交给 C++ 侧把 JS bundle 喂进引擎执行,JS 侧执行完注册好组件,Java 侧再回头喊一声「跑吧」,JS 才开始渲染。
按调用顺序排一遍是这样。
MainApplication (SoLoader 加载 C++ 库)
→ MainActivity → ReactActivity → ReactActivityDelegate.onCreate
→ ReactRootView.startReactApplication
→ ReactInstanceManager.createReactContextInBackground
→ createReactContext (建两张模块注册表 + CatalystInstance)
→ CatalystInstanceImpl.initializeBridge (JNI 下到 C++)
→ JSBundleLoader.loadScript → CatalystInstanceImpl.cpp
→ Instance.cpp → NativeToJsBridge.cpp → JSCExecutor.cpp (真正执行 JS)
→ 回到 Java:setupReactContext → rootView.runApplication
→ AppRegistry.js runApplication → 开始渲染
注意中间有一次「下到 C++ 再折回 Java」的往返,这是最容易看晕的地方,遇到的时候回来看这张图。
# 二、JS 侧的入口
先从最熟悉的那一端看起。JS 程序的入口做的事只有一件,把当前 App 的根组件注册到 AppRegistry 这个 JS 模块里。
import { AppRegistry } from 'react-native'
// ...省略代码
AppRegistry.registerComponent('demo', () => Index)
registerComponent 的第一个参数是组件名,第二个是一个返回根组件的工厂函数。注意它只是「注册」,登记在一张表里,并没有渲染任何东西。真正的渲染要等原生侧准备好之后回过头来调 runApplication,那时候才会按这个名字去表里找组件。
那个字符串 'demo' 是两端约定的暗号,记住它,第三节马上会再见到。
# 三、MainApplication 和 MainActivity
新建一个 RN 项目,原生代码里会生成 MainActivity 和 MainApplication 两个 Java 类。顾名思义,MainActivity 就是原生侧的入口。
先看 MainApplication 做了哪些事。
public class MainApplication extends Application implements ReactApplication {
//ReactNativeHost:持有ReactInstanceManager实例,做一些初始化操作。
private final ReactNativeHost mReactNativeHost = new ReactNativeHost(this) {
@Override
public boolean getUseDeveloperSupport() {
return BuildConfig.DEBUG;
}
@Override
protected List<ReactPackage> getPackages() {
return Arrays.<ReactPackage>asList(
new MainReactPackage()
);
}
};
@Override
public ReactNativeHost getReactNativeHost() {
return mReactNativeHost;
}
@Override
public void onCreate() {
super.onCreate();
//SoLoader:加载C++底层库,准备解析JS。
SoLoader.init(this, /* native exopackage */ false);
}
}
这个类里有三处值得留意。
ReactNativeHost 是一个持有 ReactInstanceManager 实例的容器,做一些初始化配置。getUseDeveloperSupport 返回 BuildConfig.DEBUG,这一个布尔值决定了后面一系列分支,包括 bundle 从 Metro 服务器拉还是从 assets 读、红屏错误提示要不要显示、开发者菜单能不能调出来。所以 Debug 包和 Release 包在启动路径上是真的不一样,测性能必须用 Release 包,这一点在React Native 真机调试完整流程里也提到过。
getPackages 返回的是这个应用要加载的所有 ReactPackage。装一个原生库要在这里加一行,说的就是这个位置(后来有了自动链接就不用手动加了)。每个 Package 里声明了它提供哪些 NativeModule 和 ViewManager,第六节创建注册表的时候会遍历这个列表。
SoLoader.init 是在加载 C++ 底层库,为后面解析 JS 做准备。这一步失败的话,启动会直接崩在最开始,报的是找不到 so 库。
再看 MainActivity。
public class MainActivity extends ReactActivity {
@Override
protected String getMainComponentName() {
return "demo";
}
}
它继承了 ReactActivity,只重写了 getMainComponentName 方法。看这个返回值,"demo",和第二节 AppRegistry.registerComponent 的第一个参数是同一个字符串。
这就是那个暗号。原生侧最后会拿这个名字去 JS 侧的注册表里查组件,两边不一致就查不到,启动时会报「Application demo has not been registered」。这个错误信息在第十一节还会再见到一次。
# 四、ReactActivity 与 ReactActivityDelegate
看 ReactActivity 的 onCreate。
public abstract class ReactActivity extends Activity
implements DefaultHardwareBackBtnHandler, PermissionAwareActivity {
private final ReactActivityDelegate mDelegate;
...省略代码
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mDelegate.onCreate(savedInstanceState);
}
}
一行实际逻辑都没有,全权委托给了 ReactActivityDelegate。
这个设计不是多此一举。Activity 的继承链只有一条,如果 RN 的逻辑全写在 ReactActivity 里,那你的 Activity 就必须继承它,没法再继承公司内部的 BaseActivity。把逻辑抽到 Delegate 里之后,任何 Activity 都可以自己 new 一个 ReactActivityDelegate 持有着,在各个生命周期回调里转发一下就行。混合开发的项目基本都是这么接的。
接着看 Delegate 里干了什么。