前端进阶之旅前端进阶之旅
基础篇
进阶篇
高频篇
精选篇
手写篇
面经篇
AI 篇
原理篇
每日一题
小程序题库
知识卡片
  • 场景篇按分类整理的大前端场景考点
  • 历年面经按年份追踪真实考点
  • 算法题库NEW在线编码即时判题
  • 专项自测100 题快速查漏
  • 前端基础
    • HTTP从报文一路讲到 HTTPS
    • 浏览器渲染、事件循环、进程
    • 计算机基础Linux、网络、操作系统
  • 进阶专项
    • 设计模式23 种模式怎么用
    • 前端系统进阶学习大型项目工程化
    • 前端综合文章长期沉淀的实践文
  • 工程与工具
    • Node学习指南从环境搭建到服务端
    • NPM工作流script、依赖与发布
    • Docker容器化部署上手
    • Canvas图形与动画实战
  • 路线与导图
    • 思维导图知识点全景图
    • 学习路线按图索骥不跑偏
  • 动态
    • 公众号动态公众号历史文章
    • 博客动态站长的技术博客
    • 开发者导航常用工具与文档站
  • AI 助手随时提问,即时解析
  • AI 模拟面试模拟真实面试 + 报告
  • AI 知识地图串起全站知识点
  • AI 定制路线按你的简历现排
AI 热点
旧版
基础篇
进阶篇
高频篇
精选篇
手写篇
面经篇
AI 篇
原理篇
每日一题
小程序题库
知识卡片
  • 场景篇按分类整理的大前端场景考点
  • 历年面经按年份追踪真实考点
  • 算法题库NEW在线编码即时判题
  • 专项自测100 题快速查漏
  • 前端基础
    • HTTP从报文一路讲到 HTTPS
    • 浏览器渲染、事件循环、进程
    • 计算机基础Linux、网络、操作系统
  • 进阶专项
    • 设计模式23 种模式怎么用
    • 前端系统进阶学习大型项目工程化
    • 前端综合文章长期沉淀的实践文
  • 工程与工具
    • Node学习指南从环境搭建到服务端
    • NPM工作流script、依赖与发布
    • Docker容器化部署上手
    • Canvas图形与动画实战
  • 路线与导图
    • 思维导图知识点全景图
    • 学习路线按图索骥不跑偏
  • 动态
    • 公众号动态公众号历史文章
    • 博客动态站长的技术博客
    • 开发者导航常用工具与文档站
  • AI 助手随时提问,即时解析
  • AI 模拟面试模拟真实面试 + 报告
  • AI 知识地图串起全站知识点
  • AI 定制路线按你的简历现排
AI 热点
旧版

React Native启动流程源码拆解从MainActivity到第一帧

首页2019-10-02 15:40:12Front-End
RNReact源码

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 为什么把所有活都委托给 ReactActivityDelegate
  • ReactRootView.startReactApplication 的三个参数分别是什么
  • ReactInstanceManager 怎么在后台线程里创建 ReactContext
  • CatalystInstance 构造时的 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 里干了什么。

fe
  • 一、先给一张地图
  • 二、JS 侧的入口
  • 三、MainApplication 和 MainActivity
  • 四、ReactActivity 与 ReactActivityDelegate
  • 五、ReactRootView 与 startReactApplication
  • 六、ReactInstanceManager 创建 ReactContext
  • 七、CatalystInstance 与 initializeBridge
  • 八、JSBundleLoader 从哪里读 bundle
  • 九、下到 C++ 层
    • 9.1 CatalystInstanceImpl.cpp
    • 9.2 Instance.cpp
    • 9.3 NativeToJsBridge.cpp
    • 9.4 JSCExecutor.cpp
  • 十、回到 Java 侧完成首帧
  • 十一、AppRegistry.js 收尾
  • 十二、两张全景图
  • 十三、这条链路在新架构下变了什么
  • 总结
  • 参考

← React Native原理浅析从JavaScriptCore到BridgeReact Native图片宽高与字体的平台适配实战 →