前言
提起React Native
,大家会自动联想起 JS 是运行在单线程上,进而在性能优化时忽略在线程上的做功,本篇文章与大家一起了解下 RN 执行涉及到的线程以及发现能提升性能的优化点。
1. React Native 涉及线程
1.1. 线程消息队列的创建流程
React Native
引擎CatalystInstanceImpl
初始化过程中会创建JS线程
和NativeModules线程
、创建消息队列代理类MessageQueueThreadImpl
的对象,并会将它们的引用传递到JS引擎中(C++构成的so文件),方便JS层代码逻辑执行和桥调用。同时 UI 线程的消息队列代理对象也会被创建,以便RN原生侧调用。
- 创建
ReactQueueConfigurationImpl
java
public class ReactQueueConfigurationImpl implements ReactQueueConfiguration {
private final MessageQueueThreadImpl mUIQueueThread;
private final MessageQueueThreadImpl mNativeModulesQueueThread;
private final MessageQueueThreadImpl mJSQueueThread;
// 创建ReactQueueConfigurationImpl对象的静态方法
// 简化后的代码
public static ReactQueueConfigurationImpl create(
ReactQueueConfigurationSpec spec, QueueThreadExceptionHandler exceptionHandler) {
MessageQueueThreadSpec uiThreadSpec = MessageQueueThreadSpec.mainThreadSpec();
// 通过标识获取ui线程。以下均会调用MessageQueueThreadImpl#create
MessageQueueThreadImpl uiThread = MessageQueueThreadImpl.create(uiThreadSpec, exceptionHandler);
// 创建js线程
MessageQueueThreadImpl jsThread = MessageQueueThreadImpl.create(spec.getJSQueueThreadSpec(), exceptionHandler);
// 创建nativeModules线程
MessageQueueThreadImpl nativeModulesThread = MessageQueueThreadImpl.create(spec.getNativeModulesQueueThreadSpec(), exceptionHandler);
return new ReactQueueConfigurationImpl(uiThread, nativeModulesThread, jsThread);
}
// ...
}
- 除 UI 线程外的其它线程需要单独创建并初始化 Looper
java
public static MessageQueueThreadImpl create(
MessageQueueThreadSpec spec, QueueThreadExceptionHandler exceptionHandler) {
switch (spec.getThreadType()) {
case MAIN_UI:
// ui线程走这里
return createForMainThread(spec.getName(), exceptionHandler);
case NEW_BACKGROUND:
// js线程和nativeModules线程走这里
return startNewBackgroundThread(spec.getName(), spec.getStackSize(), exceptionHandler);
default:
throw new RuntimeException("Unknown thread type: " + spec.getThreadType());
}
}
- 线程创建并初始化 Looper,线程名增加
mqt_
前缀
java
// ui线程------创建消息队列封装
private static MessageQueueThreadImpl createForMainThread(
String name, QueueThreadExceptionHandler exceptionHandler) {
Looper mainLooper = Looper.getMainLooper();
final MessageQueueThreadImpl mqt =
new MessageQueueThreadImpl(name, mainLooper, exceptionHandler);
// 次要代码简化...
return mqt;
}
// 其它线程------创建消息队列封装
private static MessageQueueThreadImpl startNewBackgroundThread(
final String name, long stackSize, QueueThreadExceptionHandler exceptionHandler) {
Thread bgThread =
new Thread(
null,
new Runnable() {
@Override
public void run() {
Looper.prepare();
// 次要代码简化...
Looper.loop();
}
},
"mqt_" + name,
stackSize);
bgThread.start();
// 这里会阻塞,等待线程创建和Looper初始化完成,first是looper
Pair<Looper, MessageQueueThreadPerfStats> pair = dataFuture.getOrThrow();
return new MessageQueueThreadImpl(name, pair.first, exceptionHandler, pair.second);
}
- MessageQueueThreadImpl 类的结构
java
@DoNotStrip
public class MessageQueueThreadImpl implements MessageQueueThread {
private MessageQueueThreadImpl(
String name,
Looper looper,
QueueThreadExceptionHandler exceptionHandler,
MessageQueueThreadPerfStats stats) {
mName = name;
mLooper = looper;
mHandler = new MessageQueueThreadHandler(looper, exceptionHandler);
mPerfStats = stats;
mAssertionErrorMessage = "Expected to be called from the '" + getName() + "' thread!";
}
// 本质是通过handler放到线程的消息队列中
@Override
public boolean runOnQueue(Runnable runnable) {
// 无关代码简化...
mHandler.post(runnable);
return true;
}
@DoNotStrip
@Override
public <T> Future<T> callOnQueue(final Callable<T> callable) {
final SimpleSettableFuture<T> future = new SimpleSettableFuture<>();
runOnQueue(
new Runnable() {
@Override
public void run() {
try {
future.set(callable.call());
} catch (Exception e) {
future.setException(e);
}
}
});
return future;
}
// ....
}
1.2. mqt_js线程
所有的 JS 代码执行都是使用的这个线程。 除此之外同步桥调用也会使用该线程,可以通过 ReactMethod 注解的 isBlockingSynchronousMethod 参数设置。
java
@ReactModule(name = "MyNativeModule")
class MyNativeModule : ReactContextBaseJavaModule() {
override fun getName(): String {
return "MyNativeModule"
}
// 设为同步桥
@ReactMethod(isBlockingSynchronousMethod = true)
fun sayHello(name: String) {
Thread.sleep(3000)
Log.i("MyNativeModule", "native侧 sayHello方法执行线程:" + Thread.currentThread().name)
}
}
初次使用的时候可能会怀疑这里真的和 JS 侧的线程是同一个吗?咱们测试一下(sayHello原生桥中加了3000ms的休眠)。
在JS侧方法中调用同步桥并打印下执行耗时:
javascript
const startTime = Date.now();
NativeModules.MyNativeModule.sayHello('1111');
console.log('JS侧 sayHello桥方法执行耗时' + (Date.now() - startTime));
打印日志:
native侧 sayHello方法执行线程:mqt_js
JS侧 sayHello桥方法执行耗时3002
从日志可以看出执行JS代码与Native同步桥方法的线程是同一个,并且同步桥会完全阻塞JS逻辑的执行。
1.3. mqt_native_modules线程
该线程主要用于执行Native异步桥方法。
测试一下:
在JavaModuleWrapper
的invoke
方法中打印线程名称:
java
public class JavaModuleWrapper {
// ...
@DoNotStrip
public void invoke(int methodId, ReadableNativeArray parameters) {
// ...
android.util.Log.i("RN测试", "线程:" + Thread.currentThread().getName() + " 调用方法:" + ((JavaMethodWrapper)mMethods.get(methodId)).getMethod().getName() + " 参数:" + parameters);
mMethods.get(methodId).invoke(mJSInstance, parameters);
}
}
结果:
从日志中发现自定义桥和视图相关的桥都是交由 mqt_native_modules 线程处理的。这里的线程处理是不区分优先级,其它桥的执行是会影响视图桥的执行(会影响首屏耗时) ,做性能优化时应考虑将非必需桥放到首屏之后调用。
1.4. 同步桥与异步桥
桥类型 | 线程切换过程 | 适用场景 | 备注 |
---|---|---|---|
同步桥 | 一直JS线程 | 桥方法运行耗时短 且 短时间内桥调用量级极小 | JS线程自己处理,减少线程切换成本;线下测试同步桥是异步桥执行耗时的30%左右。 |
异步桥 | JS线程(JS侧) → NativeModules线程(Native侧) → JS线程(JS侧) | 桥方法运行耗时长 或 短时间内桥调用量级较大 | JS线程与NativeModules线程并发处理事件 |
2. 检测耗时桥
Android支持通过Trace查看桥执行的耗时(只能查看异步桥),针对执行耗时较高的桥,建议另建线程调度,避免影响待执行队列中其它桥的执行(尤其不能影响UI相关的桥)。
2.1. 使用Perfetto录制Trace
2.1.1. Perfetto录制配置Perfetto UI

2.1.2. 安装目标apk
2.1.3. 执行命令 adb kill-server
2.1.4. 关闭Android Studio(AS的adb与Perfetto的adb连接冲突)
2.1.5. 设备重连数据线
2.1.6. Perfetto关联上设备 ui.perfetto.dev/#!/record/t...

2.1.7. 点击上图中的录制即可
在录制期间打开app中我们想检测的页面。

2.1.7. 录制完成后会自动打开trace分析页面
2.2. 通过SQL查Trace
Perfetto支持通过SQL查询Trace,可以查桥执行耗时、桥调用顺序等。
举例:按降序查桥执行耗时
在输入框中打英文:
,然后输入SQL
select * from slices where name like '%callJavaModuleMethod%' order by dur desc。

dur字段便是桥方法在native执行的耗时,单位是微秒。
如果期望看更细节的桥信息,可以在代码的Application
的onCreate
增加配置。
ini
// 丰富桥执行trace的配置
SystraceMessage.INCLUDE_ARGS = true
然后重新录制即可。

name中会展示详细的桥方法名,逐个排查耗时是否符合预期即可。
2.3. 线上统计
通过Trace只能看出大概桥耗时情况,线上case错综复杂,高中低端机可能出现不同的表现。因此可以在线上抽样记录耗时情况,在合适时机批量上报。
最后
希望本篇文章能给大家带来一些优化思路,也欢迎大家在评论区切磋交流。
推荐阅读: