React Native for Android 原理分析与实践:实现原理,深入剖析

  • JavascriptModuleRegistry:JavascriptModuleRegistry是JS Module映射表,NativeModuleRegistry是Java Module映射表

以上便是整套框架中关键的角色,值得一提的是,当页面真正渲染出来以后,它实际上还是Native代码,React Native的作用就是把JavaScript代码映射成Native代码以及实现两端 的通信,所以我们在React Native基础框架搭建的过程中,指导思路之一就是弱化Native与RN的边界与区别,让业务开发组感受不到两者的区别,从而简化开发流程。

好,有了对React Native框架的整体理解,我们来继续分析一个RN页面是如何启动并渲染出来的,这也是我们关心的主要问题。后续的基础框架的搭建、JS Bundle分包加载、渲染性能优化 等都会围绕着着一块做文章。

二 启动流程

我们知道RN的页面也是依托Activity,React Native框架里有一个ReactActivity,它就是我们RN页面的容器。ReactActivity里有个ReactRootView,正如它的名字那样,它就是 ReactActivity的root View,最终渲染出来的view都会添加到这个ReactRootView上。ReactRootView调用自己的startReactApplication()方法启动了整个RN页面,在启动的过程 中先去创建页面上下文ReactContext,然后再去加载、执行并将JavaScript映射成Native Widget,最终一个RN页面就显示在了用户面前。

整个RN页面的启动流程图如下所示:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

这个流程看起来有点长,但实际上重要的东西并不多,我们当前只需要重点关注四个问题:

  1. ReactInstanceManager是如何被创建的,它在创建的时候都初始化了哪些对象?🤔
  2. RN页面上下文ReactContext在创建的过程中都做了什么,都初始化了哪些对象?🤔
  3. JS Bundle是如何被加载的?🤔
  4. JS入口页面是如何被渲染出来的?🤔

2.1 创建ReactInstanceManager

我们先来看第一个问题,我们都知道要使用RN页面,就需要先初始化一个ReactNativeHost,它是一个抽象类,ReactInstanceManager就是在这个类里被创建的,如下所示:

public abstract class ReactNativeHost {
protected ReactInstanceManager createReactInstanceManager() {

ReactInstanceManagerBuilder builder = ReactInstanceManager.builder()
//应用上下文
.setApplication(mApplication)
//JSMainModuleP相当于应用首页的js Bundle,可以传递url从服务器拉取js Bundle
//当然这个只在dev模式下可以使用
.setJSMainModulePath(getJSMainModuleName())
//是否开启dev模式
.setUseDeveloperSupport(getUseDeveloperSupport())
//红盒的回调
.setRedBoxHandler(getRedBoxHandler())
//JS执行器
.setJavaScriptExecutorFactory(getJavaScriptExecutorFactory())
//自定义UI实现机制,这个我们一般用不到
.setUIImplementationProvider(getUIImplementationProvider())
.setInitialLifecycleState(LifecycleState.BEFORE_CREATE);

//添加我们外面设置的Package
for (ReactPackage reactPackage : getPackages()) {
builder.addPackage(reactPackage);
}

//获取js Bundle的加载路径
String jsBundleFile = getJSBundleFile();
if (jsBundleFile != null) {
builder.setJSBundleFile(jsBundleFile);
} else {
builder.setBundleAssetName(Assertions.assertNotNull(getBundleAssetName()));
}
return builder.build();
}
}

2.2 创建ReactContext

我们再来看第二个问题,ReactContext创建流程序列图如下所示:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

可以发现,最终创建ReactContext是createReactContext()方法,我们来看看它的实现。

public class ReactInstanceManager {

private ReactApplicationContext createReactContext(
JavaScriptExecutor jsExecutor,
JSBundleLoader jsBundleLoader) {
Log.d(ReactConstants.TAG, “ReactInstanceManager.createReactContext()”);
ReactMarker.logMarker(CREATE_REACT_CONTEXT_START);
//ReactApplicationContext是ReactContext的包装类。
final ReactApplicationContext reactContext = new ReactApplicationContext(mApplicationContext);

//debug模式里开启异常处理器,就是我们开发中见到的调试工具(红色错误框等)
if (mUseDeveloperSupport) {
reactContext.setNativeModuleCallExceptionHandler(mDevSupportManager);
}

//创建JavaModule注册表
NativeModuleRegistry nativeModuleRegistry = processPackages(reactContext, mPackages, false);

NativeModuleCallExceptionHandler exceptionHandler = mNativeModuleCallExceptionHandler != null
? mNativeModuleCallExceptionHandler
: mDevSupportManager;

//创建CatalystInstanceImpl的Builder,它是三端通信的管理类
CatalystInstanceImpl.Builder catalystInstanceBuilder = new CatalystInstanceImpl.Builder()
.setReactQueueConfigurationSpec(ReactQueueConfigurationSpec.createDefault())
//JS执行器
.setJSExecutor(jsExecutor)
//Java Module注册表
.setRegistry(nativeModuleRegistry)
//JS Bundle加载器
.setJSBundleLoader(jsBundleLoader)
//Java Exception处理器
.setNativeModuleCallExceptionHandler(exceptionHandler);

ReactMarker.logMarker(CREATE_CATALYST_INSTANCE_START);
// CREATE_CATALYST_INSTANCE_END is in JSCExecutor.cpp
Systrace.beginSection(TRACE_TAG_REACT_JAVA_BRIDGE, “createCatalystInstance”);
final CatalystInstance 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);
}
if (Systrace.isTracing(TRACE_TAG_REACT_APPS | TRACE_TAG_REACT_JS_VM_CALLS)) {
catalystInstance.setGlobalVariable(“__RCTProfileIsProfiling”, “true”);
}
ReactMarker.logMarker(ReactMarkerConstants.PRE_RUN_JS_BUNDLE_START);
//开启加载执行JS Bundle
catalystInstance.runJSBundle();
//关联catalystInstance与reactContext
reactContext.initializeWithInstance(catalystInstance);

return reactContext;
}
}

在这个方法里完成了RN页面上下文ReactContext的创建,我们先来看看这个方法的两个入参:

  • JSCJavaScriptExecutor jsExecutor:JSCJavaScriptExecutor继承于JavaScriptExecutor,当该类被加载时,它会自动去加载"reactnativejnifb.so"库,并会调用Native方 法initHybrid()初始化C++层RN与JSC通信的框架。
  • JSBundleLoader jsBundleLoader:缓存了JSBundle的信息,封装了上层加载JSBundle的相关接口,CatalystInstance通过其简介调用ReactBridge去加载JS文件,不同的场景会创建 不同的加载器,具体可以查看类JSBundleLoader。

可以看到在ReactContext创建的过程中,主要做了以下几件事情:

  1. 构建ReactApplicationContext对象,ReactApplicationContext是ReactContext的包装类。
  2. 利用jsExecutor、nativeModuleRegistry、jsBundleLoader、exceptionHandler等参数构建CatalystInstance实例,作为以为三端通信的中枢。
  3. 调用CatalystInstance的runJSBundle()开始加载执行JS。

另一个重要的角色CatalystInstance出现了,前面我们也说过它是三端通信的中枢。关于通信的具体实现我们会在接下来的通信机制小节来讲述,我们先来接着看JS的加载过程。

2.3 加载JS Bundle

在分析JS Bundle的加载流程之前,我们先来看一下上面提到CatalystInstance,它是一个接口,其实现类是CatalystInstanceImpl,我们来看看它的构造方法。

public class CatalystInstanceImpl implements CatalystInstance {

private CatalystInstanceImpl(
final ReactQueueConfigurationSpec reactQueueConfigurationSpec,
final JavaScriptExecutor jsExecutor,
final NativeModuleRegistry nativeModuleRegistry,
final JSBundleLoader jsBundleLoader,
NativeModuleCallExceptionHandler nativeModuleCallExceptionHandler) {
Log.d(ReactConstants.TAG, “Initializing React Xplat Bridge.”);
mHybridData = initHybrid();

//创建三大线程:UI线程、Native线程与JS线程
mReactQueueConfiguration = ReactQueueConfigurationImpl.create(
reactQueueConfigurationSpec,
new NativeExceptionHandler());
mBridgeIdleListeners = new CopyOnWriteArrayList<>();
mNativeModuleRegistry = nativeModuleRegistry;
//创建JS Module注册表实例,这个在以前的代码版本中是在上面的createReactContext()方法中创建的
mJSModuleRegistry = new JavaScriptModuleRegistry();
mJSBundleLoader = jsBundleLoader;
mNativeModuleCallExceptionHandler = nativeModuleCallExceptionHandler;
mNativeModulesQueueThread = mReactQueueConfiguration.getNativeModulesQueueThread();
mTraceListener = new JSProfilerTraceListener(this);

Log.d(ReactConstants.TAG, “Initializing React Xplat Bridge before initializeBridge”);
//在C++层初始化通信桥
initializeBridge(
new BridgeCallback(this),
jsExecutor,
mReactQueueConfiguration.getJSQueueThread(),
mNativeModulesQueueThread,
mNativeModuleRegistry.getJavaModules(this),
mNativeModuleRegistry.getCxxModules());
Log.d(ReactConstants.TAG, “Initializing React Xplat Bridge after initializeBridge”);

mJavaScriptContextHolder = new JavaScriptContextHolder(getJavaScriptContext());
}
}

这个函数的入参大部分我们都已经很熟悉了,我们单独说说这个ReactQueueConfigurationSpec,它用来创建ReactQueueConfiguration的实例,ReactQueueConfiguration 同样是个接口,它的实现类是ReactQueueConfigurationImpl。

ReactQueueConfiguration的定义如下:

public interface ReactQueueConfiguration {
//UI线程
MessageQueueThread getUIQueueThread();
//Native线程
MessageQueueThread getNativeModulesQueueThread();
//JS线程
MessageQueueThread getJSQueueThread();
void destroy();
}

可以看着这个接口的作用就是创建三个带消息队列的线程:

  • UI线程:Android的UI线程,处理和UI相关的事情。
  • Native线程:主要是完成通信的工作。
  • JS线程:主要完成JS的执行和渲染工作。

可以看到CatalystInstance对象在构建的时候,主要做了两件事情:

  1. 创建三大线程:UI线程、Native线程与JS线程。
  2. 在C++层初始化通信桥。

我们接着来看JS Bundle的加载流程,JS Bundle的加载实际上是指C++层完成的,我们看一下序列图。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

注:JS Bundle有三种加载方式:

  • setSourceURLs(String deviceURL, String remoteURL) :从远程服务器加载。
  • loadScriptFromAssets(AssetManager assetManager, String assetURL, boolean loadSynchronously):从Assets文件夹加载。
  • loadScriptFromFile(String fileName, String sourceURL, boolean loadSynchronously):从文件路径加载。

从这个序列图上我们可以看出,真正加载执行JS的地方就是JSCExector.cpp的loadApplicationScript()方法。

void JSCExecutor::loadApplicationScript(std::unique_ptr script, std::string sourceURL) {
SystraceSection s(“JSCExecutor::loadApplicationScript”,
“sourceURL”, sourceURL);

switch (jsStatus) {
case JSLoadSourceIsCompiled:
if (!bcSourceCode) {
throw std::runtime_error(“Unexpected error opening compiled bundle”);
}
//调用JavaScriptCore里的方法验证JS是否有效,并解释执行
evaluateSourceCode(m_context, bcSourceCode, jsSourceURL);

flush();

ReactMarker::logMarker(ReactMarker::CREATE_REACT_CONTEXT_STOP);
ReactMarker::logTaggedMarker(ReactMarker::RUN_JS_BUNDLE_STOP, scriptName.c_str());
return;

case JSLoadSourceErrorVersionMismatch:
throw RecoverableError(explainLoadSourceStatus(jsStatus));

case JSLoadSourceErrorOnRead:
case JSLoadSourceIsNotCompiled:
// Not bytecode, fall through.
break;
}
}

可以看到这个方法主要是调用JavaScriptCore里的evaluateSourceCode()方法验证JS是否有效,并解释执行。然后在调用flush()方法层调用JS层的里 方法执行JS Bundle。

void JSCExecutor::flush() {
SystraceSection s(“JSCExecutor::flush”);

if (m_flushedQueueJS) {
//调用MessageQueue.js的flushedQueue()方法
callNativeModules(m_flushedQueueJS->callAsFunction({}));
return;
}

// When a native module is called from JS, BatchedBridge.enqueueNativeCall()
// is invoked. For that to work, require(‘BatchedBridge’) has to be called,
// and when that happens, __fbBatchedBridge is set as a side effect.
auto global = Object::getGlobalObject(m_context);
auto batchedBridgeValue = global.getProperty(“__fbBatchedBridge”);
// So here, if __fbBatchedBridge doesn’t exist, then we know no native calls
// have happened, and we were able to determine this without forcing
// BatchedBridge to be loaded as a side effect.
if (!batchedBridgeValue.isUndefined()) {
// If calls were made, we bind to the JS bridge methods, and use them to
// get the pending queue of native calls.
bindBridge();
callNativeModules(m_flushedQueueJS->callAsFunction({}));
} else if (m_delegate) {
// If we have a delegate, we need to call it; we pass a null list to
// callNativeModules, since we know there are no native calls, without
// calling into JS again. If no calls were made and there’s no delegate,
// nothing happens, which is correct.
callNativeModules(Value::makeNull(m_context));
}
}

上面提到flush()方法调用MessageQueue.js的flushedQueue()方法,这是如何做到的呢?🤔。

事实上这是由bindBridge()方法来完成的,bindBridge()从__fbBatchedBridge(__fbBatchedBridge也是被MessageQueue.js设置为全局变量,供C++层读取)对象中取出MessageQueue.js里的四个方法:

  • callFunctionReturnFlushedQueue()
  • invokeCallbackAndReturnFlushedQueue()
  • flushedQueue()
  • callFunctionReturnResultAndFlushedQueue()

并分别存在三个C++变量中:

  • m_callFunctionReturnFlushedQueueJS
  • m_invokeCallbackAndReturnFlushedQueueJS
  • m_flushedQueueJS
  • m_callFunctionReturnResultAndFlushedQueueJS

这样C++就可以调用这四个JS方法。

void JSCExecutor::bindBridge() throw(JSException) {
SystraceSection s(“JSCExecutor::bindBridge”);
std::call_once(m_bindFlag, [this] {
auto global = Object::getGlobalObject(m_context);
auto batchedBridgeValue = global.getProperty(“__fbBatchedBridge”);
if (batchedBridgeValue.isUndefined()) {
auto requireBatchedBridge = global.getProperty(“__fbRequireBatchedBridge”);
if (!requireBatchedBridge.isUndefined()) {
batchedBridgeValue = requireBatchedBridge.asObject().callAsFunction({});
}
if (batchedBridgeValue.isUndefined()) {
throw JSException(“Could not get BatchedBridge, make sure your bundle is packaged correctly”);
}
}

auto batchedBridge = batchedBridgeValue.asObject();
m_callFunctionReturnFlushedQueueJS = batchedBridge.getProperty(“callFunctionReturnFlushedQueue”).asObject();
m_invokeCallbackAndReturnFlushedQueueJS = batchedBridge.getProperty(“invokeCallbackAndReturnFlushedQueue”).asObject();
m_flushedQueueJS = batchedBridge.getProperty(“flushedQueue”).asObject();
m_callFunctionReturnResultAndFlushedQueueJS = batchedBridge.getProperty(“callFunctionReturnResultAndFlushedQueue”).asObject();
});
}

至此,JS Bundle已经加载解析完成,进入MessageQueue.js开始执行。

2.4 绑定ReactContext与ReactRootView

JS Bundle加载完成以后,前面说的createReactContext()就执行完成了,然后开始执行setupReacContext()方法,绑定ReactContext与ReactRootView。 我们来看一下它的实现。

public class ReactInstanceManager {

private void setupReactContext(final ReactApplicationContext reactContext) {
//…

//执行Java Module的初始化
catalystInstance.initialize();
//通知ReactContext已经被创建爱女
mDevSupportManager.onNewReactContextCreated(reactContext);
//内存状态回调设置
mMemoryPressureRouter.addMemoryPressureListener(catalystInstance);
//复位生命周期
moveReactContextToCurrentLifecycleState();

ReactMarker.logMarker(ATTACH_MEASURED_ROOT_VIEWS_START);
synchronized (mAttachedRootViews) {
//将所有的ReactRootView与catalystInstance进行绑定
for (ReactRootView rootView : mAttachedRootViews) {
attachRootViewToInstance(rootView, catalystInstance);
}
}
//…
}

private void attachRootViewToInstance(
final ReactRootView rootView,
CatalystInstance catalystInstance) {
//…
UIManagerModule uiManagerModule = catalystInstance.getNativeModule(UIManagerModule.class);
//将ReactRootView作为根布局
final int rootTag = uiManagerModule.addRootView(rootView);
rootView.setRootViewTag(rootTag);
//执行JS的入口bundle页面
rootView.invokeJSEntryPoint();
//…
}
x
}

setupReactContext()方法主要完成每个ReactRootView与catalystInstance的绑定,绑定的过程主要做两件事情:

  1. 将ReactRootView作为根布局.
  2. 执行JS的入口bundle页面.

JS的页面入口我们可以设置mJSEntryPoint来自定义入口,如果不设置则是默认的入口AppRegistry。

private void defaultJSEntryPoint() {
//…
try {
//…
String jsAppModuleName = getJSModuleName();
catalystInstance.getJSModule(AppRegistry.class).runApplication(jsAppModuleName, appParams);
} finally {
Systrace.endSection(TRACE_TAG_REACT_JAVA_BRIDGE);
}
}

这里的调用方式实际上就是原生调用JS的方法,它调用的正是我们很熟悉的AppRegistry.js,AppRegistry.js调用runApplication()开始执行JS页面的渲染,最终转换为 Native UI显示在手机上。

到此为止,整个RN页面的启动流程就分析完了,我们接着来看看RN页面是如何渲染最终显示在手机上的。

三 渲染原理

上面我们也说了,RN页面的入口一般是AppRegistry.js,我们就从这个页面入手开始分析RN页面的渲染流程。先看一下RN页面的渲染流程序列图,如下所示:

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数Android工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
img
img
img
img
img
img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!

img
img

由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新
如果你觉得这些内容对你有帮助,可以添加V:vip204888 备注Android获取(资料价值较高,非无偿)
img

最后

我见过很多技术leader在面试的时候,遇到处于迷茫期的大龄程序员,比面试官年龄都大。这些人有一些共同特征:可能工作了7、8年,还是每天重复给业务部门写代码,工作内容的重复性比较高,没有什么技术含量的工作。问到这些人的职业规划时,他们也没有太多想法。

其实30岁到40岁是一个人职业发展的黄金阶段,一定要在业务范围内的扩张,技术广度和深度提升上有自己的计划,才有助于在职业发展上有持续的发展路径,而不至于停滞不前。

不断奔跑,你就知道学习的意义所在!

fIz-1711585711274)]

由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新
如果你觉得这些内容对你有帮助,可以添加V:vip204888 备注Android获取(资料价值较高,非无偿)
[外链图片转存中…(img-lFIF62Js-1711585711274)]

最后

我见过很多技术leader在面试的时候,遇到处于迷茫期的大龄程序员,比面试官年龄都大。这些人有一些共同特征:可能工作了7、8年,还是每天重复给业务部门写代码,工作内容的重复性比较高,没有什么技术含量的工作。问到这些人的职业规划时,他们也没有太多想法。

其实30岁到40岁是一个人职业发展的黄金阶段,一定要在业务范围内的扩张,技术广度和深度提升上有自己的计划,才有助于在职业发展上有持续的发展路径,而不至于停滞不前。

不断奔跑,你就知道学习的意义所在!

本文已被CODING开源项目:《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》收录

  • 26
    点赞
  • 26
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值