React Native启动白屏是一个很普遍但又很严重的问题,网上也有很多文章,这里就此问题,从分析到常用的解决方案做一个简单的总结。
先看图,白屏的现象:
图中手机为ZTE星星2号(专用测试机,为嘛?因为公司没给配啊,还有自己买的,所以就是专用的喽),Andriod 4.4的,可以看到白屏现象很严重,最后用自己的华为mate9,Android 8.0系统进行了测试,依然存在白屏的现象。
网上的解决方案,大概分为两种:
对ReactRootView以及ReactInstanceManager进行预加载,也就是说在本地配置一个启动界面,然后开始预加载RN的核心引擎,等完毕后直接跳转到RN的主界面,RN的主界面拿到初始化好的ReactRootView直接进行加载。这种方案,自己尝试了下,并不可行,只是减少了白屏的显示时间,实际上依然会有一小段时间的白屏现象。另外,采用这种方式,RN中组件的生命周期会得不到正确的执行。
在白屏上预先覆盖一张图片用于遮掩白屏,等到RN相关的ReactRootView初始化完毕后再移除掉,作者的博客:https://www.jianshu.com/p/78571e5435ec 此方案可行,测试了一下,确实解决了白屏的问题,但是我没选择此方案,因为我觉得还不够完美。
下面就这两个方案做一下简单的分析。
我的React Native 版本号为0.53.3,ReactActivity是RN的入口
ReactActivity源码
public abstract class ReactActivity extends Activity
implements DefaultHardwareBackBtnHandler, PermissionAwareActivity {
private final ReactActivityDelegate mDelegate;
protected ReactActivity() {
//构造中初始化相关的委派类
mDelegate = createReactActivityDelegate();
}
/**
* Called at construction time, override if you have a custom delegate implementation.
*/
protected ReactActivityDelegate createReactActivityDelegate() {
return new ReactActivityDelegate(this, getMainComponentName());
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//调用了委派类的onCreate方法
mDelegate.onCreate(savedInstanceState);
}
}
ReactActivityDelegate源码
public class ReactActivityDelegate {
public ReactActivityDelegate(Activity activity, @Nullable String mainComponentName) {
mActivity = activity;
mMainComponentName = mainComponentName;
mFragmentActivity = null;
}
public ReactActivityDelegate(
FragmentActivity fragmentActivity,
@Nullable String mainComponentName) {
mFragmentActivity = fragmentActivity;
mMainComponentName = mainComponentName;
mActivity = null;
}
protected @Nullable Bundle getLaunchOptions() {
return null;
}
protected ReactRootView createRootView() {
return new ReactRootView(getContext());
}
protected ReactNativeHost getReactNativeHost() {
return ((ReactApplication) getPlainActivity().getApplication()).getReactNativeHost();
}
public ReactInstanceManager getReactInstanceManager() {
return getReactNativeHost().getReactInstanceManager();
}
protected void onCreate(Bundle savedInstanceState) {
if (mMainComponentName != null) {
//mMainComponentName一定不会为空,所以一定会执行loadApp方法
loadApp(mMainComponentName);
}
mDoubleTapReloadRecognizer = new DoubleTapReloadRecognizer();
}
//RN的白屏问题就是出现在这里,两个比较耗时的函数分别为getReactNativeHost().getReactInstanceManager()和mReactRootView.startReactApplication,
//预先缓存就是针对这里的,把这一步的操作,提前完成,然后用的时候,直接从内存开始加载
protected void loadApp(String appKey) {
if (mReactRootView != null) {
throw new IllegalStateException("Cannot loadApp while app is already running.");
}
mReactRootView = createRootView();
mReactRootView.startReactApplication(
getReactNativeHost().getReactInstanceManager(),
appKey,
getLaunchOptions());
getPlainActivity().setContentView(mReactRootView);
}
}
ReactRootViewCacheManager工具类源码
public class ReactRootViewCacheManager {
private static HashMap rootViewHashMap = new HashMap<>();
/**
* 预加载RN渲染引擎
* @param activity
*/
public static void init(Activity activity,String moduleName){
//包含了,立刻返回
if(rootViewHashMap.containsKey(moduleName))return;
//ReactRootView 直接new就可以了
ReactRootView mReactRootView = new ReactRootView(activity);
//在MainApplication中提前初始化ReactInstanceManager
ReactInstanceManager reactInstanceManager = ((MainApplication) activity.getApplication()).getReactNativeHost().getReactInstanceManager();
//第一个参数为ReactInstanceManager
//第二个参数建议和RN主界面中getMainComponentName返回的内容一样,也就是此函数中的第二个形参moduleName
//第三个参数为null即可
mReactRootView.startReactApplication(reactInstanceManager,moduleName,null);
rootViewHashMap.put(moduleName,mReactRootView);
}
/**
* 获取指定的reactrootview
* @param moduleName
* @return
*/
public static ReactRootView getReactRootView(String moduleName){
ReactRootView reactRootView = rootViewHashMap.get(moduleName);
if(reactRootView != null){
ViewParent parent = reactRootView.getParent();
if(parent != null && parent instanceof ViewGroup){
((ViewGroup)parent).removeAllViews();
}
}
return reactRootView;
}
}
大概流程就是在本地的SplashActivity的onCreate中先调用ReactRootViewCacheManager.init方法,完成后在执行跳转到RN主界面即可,这里需要注意的细节,我们需要简单修改下ReactActivity的源码和ReactActivityDelegate的源码,怎么办?很简单,复制ReactActivity的源码到新创建的BaseReactActivity中,复制ReactActivityDelegate到BaseReactActivityDelegate中,并且把BaseReactActivity中的ReactActivityDelegate全部替换成BaseReactActivityDelegate即可。
SplashActivity源码
public class SplashActivity extends Activity {
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
ReactRootViewCacheManager.init(this,MainActivity.COMPONENT_NAME);
//因为ui线程是从上到下执行的,所以到这里了,就表示预加载成功
startActivity(new Intent(this,MainActivity.class));
}
}
BaseReactActivityDelegate源码只关注loadApp函数
protected void loadApp(String appKey) {
if (mReactRootView != null) {
throw new IllegalStateException("Cannot loadApp while app is already running.");
}
String mainComponentName = ((BaseReactActivity) getPlainActivity()).getMainComponentName();
//从缓存中读取初始化好的ReactRootView,如果不为空直接进行加载
ReactRootView reactRootView = ReactRootViewCacheManager.getReactRootView(mainComponentName);
if(reactRootView == null){
mReactRootView = createRootView();
mReactRootView.startReactApplication(
getReactNativeHost().getReactInstanceManager(),
appKey,
getLaunchOptions());
getPlainActivity().setContentView(mReactRootView);
}else{
getPlainActivity().setContentView(reactRootView);
}
}
此方案不做代码演示了,相信大家已经明白其中的流程了,说下他存在的问题,依然会有白屏现象,只不过这白屏现象不是RN造成的,而是安卓App本身就有白屏。
自己使用的方案就是解决安卓app启动白屏的思路,用到自定义主题,也就是android:windowBackground的属性。
看下最终效果图
遗留的问题和第二个解决方案类似,无法自由控制白屏的时间。
其他方式:
第一步:创建SplashActivity(不用创建layout布局文件)
public class SplashActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
new Handler().postDelayed (new Runnable () { //匿名内部类 创建线程
@Override
public void run() {
startActivity (new Intent(SplashActivity.this, MainActivity.class)); //界面转跳
finish();
}
},500); //第二个参数是停留的时间
}
}
第二步:在styles.xml中定义样式
windowBackground为闪屏页的图片,省去了activity的Layout布局文件。
第三步:配置文件中加载样式和启动项