上篇文章讲解了ReactNative的官方引入方式,官方方式不一定全部合适大家的项目应用。
这篇主要解说,引入跨平台后,我们的App带来了哪些大问题,而针对这些问题我们是怎么处理的 ,有哪些成果。
初始集成基于reactnative:0.55.2
跨平台方案在APP迭代过程中的一些主要问题
- ReactNative搭配native公共组件,交融与交互?
- ReactNative页面首次打开,速度不够快?
- ReactNative使用导致内存飚升,OOM概率大大提升?
一、商品详情如何使用ReactNative+Native?
思考: 初始 App使用Web+Native的方式实现商品详情的,痛点:打开商品慢。ReactNative官方只提供了activity级别的加载功能,想要相对低成本和可扩展性的方式,引入ReactNative替换,那必须探索将ReactNative支持更小单元的组件化,达到至少类Webview元素接近。
难点:商品详情支持重复打开,
剖析RN提供的ReactActivity.class页面加载源码,得到RN通过ReactNativeHost实现ReactInstanceManager加载页面和管理页面的生命周期。
//ReactActivity.class--->ReactDelegate.class
//加载RN页面的主要代码
public void loadApp(String appKey) {
if (mReactRootView != null) {
throw new IllegalStateException("Cannot loadApp while app is already running.");
}
mReactRootView = createRootView();
mReactRootView.startReactApplication(
getReactNativeHost().getReactInstanceManager(), appKey, mLaunchOptions);
}
//RN核心,Bridge引擎
public ReactInstanceManager getReactInstanceManager() {
return getReactNativeHost().getReactInstanceManager();
}
得到结论后,通过重写ReactNativeHost,创建了类似ReactActivity的RNFragment的小单元容器类,这样就很便捷了的解决reactnative引入项目的方式
ps:RN后续高版本,官网有采取一些issue,提供类似的ReactFragment容器类,但是了解加载原理,对自己自定义容器类很有必要,你有可能需要更进一步改造容器类。
二、引入ReactNative后,内存飚升,OOM概率大大提升?
**思考:**引入App项目后,商品详情打开商品详情无限重开,导致部分手机,打开6个左右商品详情页面就会引发OOM崩溃。在第一阶段对ReactNative容器进行了自定义,故针对这个情况我们第一时间通过栈方式,管理回收商品详情页面,让商品详情最多保持3个页面。
后续迭代中,商品详情不断的壮大,其他问题也导致了oom
开始引入后,参考京东&天猫,我们通过栈回收,解决了第一个OOM问题。
//不断改造的栈回收方式
public class RNStack extends Stack<ReactRootView> {
/**
* DES: 处理RN系统预留内存峰值进行页面层级管理
* TIME: 2018/12/17 0017 下午 2:20
*/
@Override
public synchronized boolean add(ReactRootView reactRootView) {
try {
return super.add(reactRootView);
} finally {
//RN 预留内存峰值
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
gcRnStack4Num(3);
} else {
gcRnStack4Num(2);
}
}
}
public boolean remove(ReactRootView reactRootView) {
reactRootView.unmountReactApplication();
return super.remove(reactRootView);
}
/**
* DES: num 表示可以保持的栈数量
* TIME: 2019/3/20 0020 下午 4:45
*/
private void gcRnStack4Num(int num) {
if (size() > num) {
ReactRootView rootView = firstElement();
if (rootView != null) {
Context context = rootView.getContext();
if (context instanceof Activity) {
((Activity) context).finish();
}
remove(rootView);
}
}
System.gc();
System.runFinalization();
}
}
后迭代中,商品详情不断的壮大,每个商品都引入了大几百的推荐商品列表,部分手机可见大部分推荐商品后,在第二个或者第三个商品详情又引发了OOM异常。后面通过RN页面修改加载逻辑,实现不可见回收方式解决的。
ps:这种情况,进过测试验证,发现Native中RN回收的速度比较慢,所以通过Native手段未未解决这种OOM
三、ReactNative页面首次打开速度不够快
思考: 使用一段时间后,商品详情RN页面,对比Web版本的详情打开已经很快了,但是对比同行的App,页面首次打开速度可以再度提速的
页面加载源码&测试验证发现,耗时地方主要在初始化引擎&加载RN bundle模块地方,且初始RN引擎&渲染加载可以分开。于是前期我们就做了,以空间换时间的概念提速,在启动app时,提前初始化。
//预加载方案
Looper.myQueue().addIdleHandler(new MessageQueue.IdleHandler() {
@Override
public boolean queueIdle() {
final ReactInstanceManager rim = getReactInstanceManager(application);
if (!rim.hasStartedCreatingInitialContext()) {
rim.addReactInstanceEventListener(new ReactInstanceManager.ReactInstanceEventListener() {
@Override
public void onReactContextInitialized(ReactContext context) {
// 初始化react上下文时调用(所有模块都已注册)。 总是调用UI线程。
rim.removeReactInstanceEventListener(this);
}
});
rim.createReactContextInBackground();
}
return false;
}
});
ps:后续参考了携程开源的CRN,他们也是通过这种方式提升加载速度的
优化后,我们的商品详情在中等手机上,首次打开提速了5倍,非首次没有变化,这是当时的测试结果
版本号 | 第一次 | 第二次 | 第三次 |
---|---|---|---|
3.68.08151 | 1196ms | 536ms | 502ms |
3.68.08133 | 7528ms | 530ms | 535ms |
ps:后续我们也再次进行了Bundle分包加载,提高 200ms左右的加载速度,没有第一次提速那么显著。
但是内存换速度也不是完美无瑕的,在我们app也引发了一些副作用
- App启动初始化东西太多,一次性占用太多内存,会导致某些手机,ANR几率比较高 (可以对部分机型取是否预加载做取舍)
总结
在对待新事物时,我们可以剖析源码或者参考同类开源方案,这样就可以最快最优得到解决方案。
跨平台方案一直是各大电商平台积极拥抱的,其比Native在实时更新方面更能满足业务需求。
ReactNative在引入App后,我们对其进行了各种适配支持,来满足业务需求,提高用户体验和交付速度,但其存在在一定程度上对app整体的健壮性与稳定性产生了负作用,需要任重道远继续探索与优化。