-
背景:
在项目集成了RN模块后,发现在Android从原生页面跳转到RN页面的时候,出现一段比较明显的白屏之后才加载出页面,这种情况在iOS上的体验就比较好,所以查看了下网上的解决方案,发现在官方的中文文档上有一篇文章给了具体的解决步骤,因此决定试试。
在这里要吐槽下,百度出来的解决 方案基本都是一样的,估计copy的人自己都没有跑过,代码写的都很乱,也没有完整的Demo可以参考,不过思路基本可以看懂,代码还是要自己写一写的。
文档地址:http://reactnative.cn/post/754
-
解决思路:
用内存换时间,在App初始化的时候就把RN的View加载到缓存,跳转时直接取缓存,达到秒开的效果。
实际开发之后,发现第一次加载还是有短暂的白屏现象,但是相对之前已经不明显了,而且第一次加载之后的跳转都没有了白屏现象,可以算是成功的优化方案了。 -
具体步骤:
3.1 新建缓存RootView管理器RNCacheViewManager
public class RNCacheViewManager {
private static ReactRootView mRootView = null;
private static ReactInstanceManager mManager = null;
private static RnInfo mRnInfo = null;
//初始化方法,App启动时调用
public static void init(Activity act, RnInfo rnInfo) {
if (mManager == null) {
updateCache(act, rnInfo);
}
}
//更新cache,适合于版本升级时候更新cache
public static void updateCache(Activity act, RnInfo rnInfo) {
mRnInfo = rnInfo;
mManager = createReactInstanceManager(act);
mRootView = new ReactRootView(act);
mRootView.startReactApplication(mManager, rnInfo.getMainComponentName(), null);
}
public static ReactRootView getReactRootView() {
if(mRootView==null){
throw new RuntimeException("缓存view管理器尚未初始化!");
}
return mRootView;
}
public static RnInfo getRnInfo() {
if(mRnInfo==null){
throw new RuntimeException("缓存view管理器尚未初始化!");
}
return mRnInfo;
}
//这里要remove掉rootview的parent对象,否则下次再setContentView时候会报错
public static void onDestroy() {
try {
ViewParent parent = getReactRootView().getParent();
if (parent != null)
((android.view.ViewGroup) parent).removeView(getReactRootView());
} catch (Throwable e) {
e.printStackTrace();
}
}
//清理缓存数据
public static void clear() {
try {
if (mManager != null) {
mManager.onHostDestroy();
mManager = null;
}
if (mRootView != null) {
onDestroy();
mRootView = null;
}
mRnInfo = null;
} catch (Throwable e) {
e.printStackTrace();
}
}
//创建 ReactInstanceManager
private static ReactInstanceManager createReactInstanceManager(Activity act) {
ReactInstanceManager mReactInstanceManager = ReactInstanceManager.builder()
.setApplication(act.getApplication())
.setBundleAssetName(getRnInfo().getBundleAssetName())
.setJSMainModuleName(getRnInfo().getJSMainModuleName())
.addPackage(getRnInfo().getMainReactPackage())
.setUseDeveloperSupport(getRnInfo().getUseDeveloperSupport())
.setInitialLifecycleState(LifecycleState.BEFORE_RESUME).build();
return mReactInstanceManager;
}
}
```
RnInfo只是自定义的一个类,里面只是ReactNative的一些配置信息,这里就不贴出来了,需要的自行到git上看。
3.2 创建一个Activity,将缓存的View加载到当前Activity
public class MyPreloadReactActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//加载缓存的mReactRootView
ReactRootView mReactRootView = RNCacheViewManager.getReactRootView();
setContentView(mReactRootView);
}
@Override
protected void onDestroy() {
//退出时要调用要RNCacheViewManager remove掉rootview的parent对象,否则下次再setContentView时候会报错
RNCacheViewManager.onDestroy();
super.onDestroy();
}
}
3.3 App启动初始化加载RN View到缓存中
这一步虽然调用简单,但是也是很重要的一步。
//初始化ReactNative缓存处理器
RNCacheViewManager.init(MainActivity.this, new RnInfo());
到这里基本就完成了,后面附上效果图跟Demo的github地址。
要注意的是这个处理针对的是原生项目加载RN页面,而且是加载离线的bundle文件。基友说iOS也是需要使用这种预加载策略的,之后再研究下iOS的预加载,研究成功的话再整理下实现的步骤。
- 效果图:
4.1 未使用缓存加载:
4.2 使用缓存之后:
cached.gif5 . GitHub地址:
Android:
https://git.oschina.net/GStick/androidiosfeatreactnative.git
6 . 后续补充:
以上都是在Demo上开发的,功能比较简单。之后正式在项目上使用的过程中发现两个比较大的坑,在此补充说下问题跟解决办法:
6.1 由于项目中使用原生与RN代码交互时是将自定义的ReactPackage
与当前Activity绑定的,而预加载是在当前Activity初始化之前缓存的RootView
,所以导致加入预加载的代码之后之前交互代码都无效了。
解决办法:需要修改自定义的ReactPackage
,将其与Application
绑定,然后用堆栈管理Activity,在自定义的ReactContextBaseJavaModule
中获取当前的Activity作交互。
6.2 RN中有一个Modal
控件比较特殊,在Android上是渲染成Dialog
,渲染时把当前ReactRootView
的context传给了Dialog
,而不是当前Activity的context,导致RN上所有Modal
控件都在初始化RootView管理器
所在的Activity上弹出了。
这个问题困扰了我好久,最后是翻过重重外墙,在外网上找到了一个不是很明显的解决方法:
MutableContextWrapper
懂一点iOS的我看到Mutable
这个前缀大概就知道这个API的大概作用了[嘿嘿],然后又去翻了一下API文档就隐约看到希望了。
大概的解决思路就是,初始化RootView
管理器的时候使用MutableContextWrapper
包住初始化使用的Activity context,在进入RN所在的Activity的onCreate()
中用 setBaseContext(Activity act)
替换MutableContextWrapper
中的context对象。
mRootView = new ReactRootView(mutableContextWrapper);
RNCacheViewManager.getMutableContextWrapper().setBaseContext(PreloadReactActivity.this);
6.3 这里多补充一个问题,由于我们的应用存在多个Tab切换的场景,测试过程中发现ScrollView
的scrollTo
方法偶尔会出现滚动停止的现象,导致tab的切换卡在中间。经过很久的研究发现不是RN的问题,而是当Activity在缓存了RootView
的情况下,对RootView
的各种属性保存存在问题。
解决办法:在Activity OnCreate()
之后更新缓存里面的RootView
。
网友评论