启动白屏终极解决方案

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布局文件。

 



第三步:配置文件中加载样式和启动项

 



    
        
        
    



 

你可能感兴趣的:(启动白屏终极解决方案)