一、问题分析

本篇博客同样和大家分享关于React Native的内容。想必大家在撸码中都发现了一个问题:从Android原生界面第一次跳转到React Native界面时,会有短暂的白屏过程,然后才会加载出界面。下次再跳转就不会出现类似问题。并且当我们杀死应用,重新启动App从Android Activity跳转到RN界面,依然会出现短暂白屏。

为什么第一次加载React Native界面会出现短暂白屏呢?大家别忘了,React Native的渲染机制是对于JsBundle的加载。项目中所有的js文件最终会被打包成一个JsBundle文件,Android环境下Bundle文件为:‘index.android.bundle’。系统在第一次渲染界面时,会首先加载JsBundle文件。那么问题肯定出现在加载JsBundle这个过程,即出现白屏可能是因为JsBundle正在加载。发现了原因,我们继续查看源码,看看是否能从源码中得知一二。

二、源码分析

Android集成的RN界面,需要继承ReactActivity,那么直接从ReactActivity源码入手:


?



1



2



3



4



5



6



7



8



9



10



11



12



13



14



15



16



17



18



19



20



21



22



23



24



25



26



27



28



29



30



31



32



33



34



35



36



37



38



39



40



41



42



43



44



45



46



47



48



49



50

public abstract class ReactActivity extends Activity          


                  implements DefaultHardwareBackBtnHandler, PermissionAwareActivity {          


                  


                  private final ReactActivityDelegate mDelegate;          


                  


                  protected ReactActivity() {          


                  mDelegate = createReactActivityDelegate();          


                  }          


                  


                  /**         


                  * Returns the name of the main component registered from JavaScript.         


                  * This is used to schedule rendering of the component.         


                  * e.g. "MoviesApp"         


                  */         


                  protected @Nullable String getMainComponentName() {          


                  return          null         ;          


                  }          


                  


                  /**         


                  * 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);          


                  mDelegate.onCreate(savedInstanceState);          


                  }          


                  


                  @Override          


                  protected void onPause() {          


                  super         .onPause();          


                  mDelegate.onPause();          


                  }          


                  


                  @Override          


                  protected void onResume() {          


                  super         .onResume();          


                  mDelegate.onResume();          


                  }          


                  


                  @Override          


                  protected void onDestroy() {          


                  super         .onDestroy();          


                  mDelegate.onDestroy();          


                  }          


                  // 其余代码略......          


         }


不难发现,ReactActivity中的行为都交给了ReactActivityDelegate类来处理。很明显是委托模式。至于白屏原因是因为第一次创建时,那么我们直接看onCreate即可。找到ReactActivityDelegate的onCreate方法:


?



1



2



3



4



5



6



7



8



9



10



11



12



13



14



15



16



17



18

protected void onCreate(Bundle savedInstanceState) {          


                  boolean needsOverlayPermission =          false         ;          


                  if          (getReactNativeHost().getUseDeveloperSupport() && Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {          


                  // Get permission to show redbox in dev builds.          


                  if          (!Settings.canDrawOverlays(getContext())) {          


                  needsOverlayPermission =          true         ;          


                  Intent serviceIntent =          new          Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION, Uri.parse(         "package:"          + getContext().getPackageName()));          


                  FLog.w(ReactConstants.TAG, REDBOX_PERMISSION_MESSAGE);          


                  Toast.makeText(getContext(), REDBOX_PERMISSION_MESSAGE, Toast.LENGTH_LONG).show();          


                  ((Activity) getContext()).startActivityForResult(serviceIntent, REQUEST_OVERLAY_PERMISSION_CODE);          


                  }          


                  }          


                  


                  if          (mMainComponentName !=          null          && !needsOverlayPermission) {          


                  loadApp(mMainComponentName);          


                  }          


                  mDoubleTapReloadRecognizer =          new          DoubleTapReloadRecognizer();          


                  }



从源码可以看到,最终调用了loadApp方法,继续跟踪loadApp方法:


?



1



2



3



4



5



6



7



8



9



10



11

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);          


         }



?



1



2



3

protected ReactRootView createRootView() {          


                  return          new          ReactRootView(getContext());          


                  }



loadApp方法中调用了createRootView创建了ReactRootView,即React Native界面,并且将界面设置到Activity中。那么问题很可能出现在这了。插个断点,调试看看执行时间。

一切恍然大悟,在createRootView和startReactApplication时,消耗了较长时间。

既然是createRootView和startReactApplication执行了耗时操作的问题,那么我们只需要将其提前执行,创建出ReactRootView并缓存下来。当跳转到React Native界面时,直接设置到ContentView即可。有了解决思路,又该到我们甩起袖子撸码了。

三、功能实现


?



1



2



3



4



5



6



7



8



9



10



11



12



13



14



15



16



17



18



19



20



21



22



23



24



25



26



27



28



29



30



31



32



33



34



35



36



37



38



39



40



41



42



43



44



45



46



47



48



49



50



51



52



53

/**         


                  * 预加载工具类         


                  * Created by Song on 2017/5/10.         


                  */         


         public class ReactNativePreLoader {          


                  


                  private static final Map<String,ReactRootView> CACHE =          new          ArrayMap<>();          


                  


                  /**         


                  * 初始化ReactRootView,并添加到缓存         


                  * @param activity         


                  * @param componentName         


                  */         


                  public static void preLoad(Activity activity, String componentName) {          


                  


                  if          (CACHE.get(componentName) !=          null         ) {          


                  return         ;          


                  }          


                  // 1.创建ReactRootView          


                  ReactRootView rootView =          new          ReactRootView(activity);          


                  rootView.startReactApplication(          


                  ((ReactApplication) activity.getApplication()).getReactNativeHost().getReactInstanceManager(),          


                  componentName,          


                  null         );          


                  


                  // 2.添加到缓存          


                  CACHE.put(componentName, rootView);          


                  }          


                  


                  /**         


                  * 获取ReactRootView         


                  * @param componentName         


                  * @return         


                  */         


                  public static ReactRootView getReactRootView(String componentName) {          


                  return          CACHE.get(componentName);          


                  }          


                  


                  /**         


                  * 从当前界面移除 ReactRootView         


                  * @param component         


                  */         


                  public static void deatchView(String component) {          


                  try          {          


                  ReactRootView rootView = getReactRootView(component);          


                  ViewGroup parent = (ViewGroup) rootView.getParent();          


                  if          (parent !=          null         ) {          


                  parent.removeView(rootView);          


                  }          


                  }          catch          (Throwable e) {          


                  Log.e(         "ReactNativePreLoader"         ,e.getMessage());          


                  }          


                  }


上述代码很简单,包含了三个方法:

(1)preLoad

负责创建ReactRootView,并添加到缓存。

(2)getReactRootView

获取创建的RootView

(3)deatchView

将添加的RootView从布局根容器中移除,在 ReactActivity 销毁后,我们需要把 view 从 parent 上卸载下来,避免出现重复添加View的异常。

从源码分析部分我们知道,集成React Native界面时,只需要继承ReactActivity,并实现getMainComponentName方法即可。加载创建视图的流程系统都在ReactActivity帮我们完成。现在因为自定义了ReactRootView的加载方式,要使用预加载方式,就不能直接继承ReactActivity。所以接下来需要我们自定义ReactActivity。

从源码中我们已经发现,ReactActivity的处理都交给了ReactActivityDelegate。所以我们可以自定义一个新的ReactActivityDelegate,只需要修改onCreate创建部分,其他照搬源码即可。


?



1



2



3



4



5



6



7



8



9



10



11



12



13



14



15



16



17



18



19



20



21



22



23



24



25



26



27



28



29



30



31



32



33



34



35



36



37



38



39



40



41



42



43



44



45



46



47



48



49



50



51



52



53



54



55



56



57



58



59



60



61



62



63



64



65



66



67



68



69



70



71



72



73



74



75



76



77



78



79



80



81



82



83



84



85



86



87



88



89



90



91



92



93



94



95



96



97



98



99



100



101



102



103



104



105



106



107



108



109



110



111



112



113



114



115



116



117



118



119



120



121



122



123



124



125



126



127



128



129



130



131



132



133



134



135



136



137



138



139



140



141



142



143



144



145



146



147



148



149



150



151



152



153



154



155



156



157



158



159



160



161



162



163

public class PreLoadReactDelegate {          


                  


                  private final Activity mActivity;          


                  private ReactRootView mReactRootView;          


                  private Callback mPermissionsCallback;          


                  private final String mMainComponentName;          


                  private PermissionListener mPermissionListener;          


                  private final int REQUEST_OVERLAY_PERMISSION_CODE = 1111;          


                  private DoubleTapReloadRecognizer mDoubleTapReloadRecognizer;          


                  


                  public PreLoadReactDelegate(Activity activity, @Nullable String mainComponentName) {          


                  this         .mActivity = activity;          


                  this         .mMainComponentName = mainComponentName;          


                  }          


                  


                  public void onCreate() {          


                  boolean needsOverlayPermission =          false         ;          


                  if          (getReactNativeHost().getUseDeveloperSupport() && Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {          


                  // Get permission to show redbox in dev builds.          


                  if          (!Settings.canDrawOverlays(mActivity)) {          


                  needsOverlayPermission =          true         ;          


                  Intent serviceIntent =          new          Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION, Uri.parse(         "package:"          + mActivity.getPackageName()));          


                  mActivity.startActivityForResult(serviceIntent, REQUEST_OVERLAY_PERMISSION_CODE);          


                  }          


                  }          


                  


                  if          (mMainComponentName !=          null          && !needsOverlayPermission) {          


                  // 1.从缓存中获取RootView          


                  mReactRootView = ReactNativePreLoader.getReactRootView(mMainComponentName);          


                  


                  if         (mReactRootView ==          null         ) {          


                  


                  // 2.缓存中不存在RootView,直接创建          


                  mReactRootView =          new          ReactRootView(mActivity);          


                  mReactRootView.startReactApplication(          


                  getReactInstanceManager(),          


                  mMainComponentName,          


                  null         );          


                  }          


                  // 3.将RootView设置到Activity布局          


                  mActivity.setContentView(mReactRootView);          


                  }          


                  


                  mDoubleTapReloadRecognizer =          new          DoubleTapReloadRecognizer();          


                  }          


                  


                  public void onResume() {          


                  if          (getReactNativeHost().hasInstance()) {          


                  getReactInstanceManager().onHostResume(mActivity, (DefaultHardwareBackBtnHandler)mActivity);          


                  }          


                  if          (mPermissionsCallback !=          null         ) {          


                  mPermissionsCallback.invoke();          


                  mPermissionsCallback =          null         ;          


                  }          


                  }          


                  


                  public void onPause() {          


                  if          (getReactNativeHost().hasInstance()) {          


                  getReactInstanceManager().onHostPause(mActivity);          


                  }          


                  }          


                  


                  public void onDestroy() {          


                  


                  if          (mReactRootView !=          null         ) {          


                  mReactRootView.unmountReactApplication();          


                  mReactRootView =          null         ;          


                  }          


                  if          (getReactNativeHost().hasInstance()) {          


                  getReactInstanceManager().onHostDestroy(mActivity);          


                  }          


                  


                  // 清除View          


                  ReactNativePreLoader.deatchView(mMainComponentName);          


                  }          


                  


                  public boolean onNewIntent(Intent intent) {          


                  if          (getReactNativeHost().hasInstance()) {          


                  getReactInstanceManager().onNewIntent(intent);          


                  return          true         ;          


                  }          


                  return          false         ;          


                  }          


                  


                  public void onActivityResult(int requestCode, int resultCode, Intent data) {          


                  if          (getReactNativeHost().hasInstance()) {          


                  getReactInstanceManager().onActivityResult(mActivity, requestCode, resultCode, data);          


                  }          else          {          


                  // Did we request overlay permissions?          


                  if          (requestCode == REQUEST_OVERLAY_PERMISSION_CODE && Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {          


                  if          (Settings.canDrawOverlays(mActivity)) {          


                  if          (mMainComponentName !=          null         ) {          


                  if          (mReactRootView !=          null         ) {          


                  throw          new          IllegalStateException(         "Cannot loadApp while app is already running."         );          


                  }          


                  mReactRootView =          new          ReactRootView(mActivity);          


                  mReactRootView.startReactApplication(          


                  getReactInstanceManager(),          


                  mMainComponentName,          


                  null         );          


                  mActivity.setContentView(mReactRootView);          


                  }          


                  }          


                  }          


                  }          


                  }          


                  


                  public boolean onBackPressed() {          


                  if          (getReactNativeHost().hasInstance()) {          


                  getReactInstanceManager().onBackPressed();          


                  return          true         ;          


                  }          


                  return          false         ;          


                  }          


                  


                  public boolean onRNKeyUp(int keyCode) {          


                  if          (getReactNativeHost().hasInstance() && getReactNativeHost().getUseDeveloperSupport()) {          


                  if          (keyCode == KeyEvent.KEYCODE_MENU) {          


                  getReactInstanceManager().showDevOptionsDialog();          


                  return          true         ;          


                  }          


                  boolean didDoubleTapR = Assertions.assertNotNull(mDoubleTapReloadRecognizer)          


                  .didDoubleTapR(keyCode, mActivity.getCurrentFocus());          


                  if          (didDoubleTapR) {          


                  getReactInstanceManager().getDevSupportManager().handleReloadJS();          


                  return          true         ;          


                  }          


                  }          


                  return          false         ;          


                  }          


                  


                  public void requestPermissions(String[] permissions, int requestCode, PermissionListener listener) {          


                  mPermissionListener = listener;          


                  mActivity.requestPermissions(permissions, requestCode);          


                  }          


                  


                  public void onRequestPermissionsResult(final int requestCode, final String[] permissions, final int[] grantResults) {          


                  mPermissionsCallback =          new          Callback() {          


                  @Override          


                  public void invoke(Object... args) {          


                  if          (mPermissionListener !=          null          && mPermissionListener.onRequestPermissionsResult(requestCode, permissions, grantResults)) {          


                  mPermissionListener =          null         ;          


                  }          


                  }          


                  };          


                  }          


                  


                  /**         


                  * 获取 Application中 ReactNativeHost         


                  * @return         


                  */         


                  private ReactNativeHost getReactNativeHost() {          


                  return          MainApplication.getInstance().getReactNativeHost();          


                  }          


                  


                  /**         


                  * 获取 ReactInstanceManager         


                  * @return         


                  */         


                  private ReactInstanceManager getReactInstanceManager() {          


                  return          getReactNativeHost().getReactInstanceManager();          


                  }          


         }


代码很长,重点在onCreate方法:


?



1



2



3



4



5



6



7



8



9



10



11



12



13



14



15

if          (mMainComponentName !=          null          && !needsOverlayPermission) {          


                  // 1.从缓存中获取RootView          


                  mReactRootView = ReactNativePreLoader.getReactRootView(mMainComponentName);          


                  


                  if         (mReactRootView ==          null         ) {          


                  


                  // 2.缓存中不存在RootView,直接创建          


                  mReactRootView =          new          ReactRootView(mActivity);          


                  mReactRootView.startReactApplication(          


                  getReactInstanceManager(),          


                  mMainComponentName,          


                  null         );          


                  }          


                  // 3.将RootView设置到Activity布局          


                  mActivity.setContentView(mReactRootView);



(1)首先从缓存中取ReactRootView

(2)缓存中不存在ReactRootView,直接创建。此时和系统帮我们创建ReactRootView没有区别

(3)将ReactRootView设置到Activity布局

很明显,我们让加载流程先经过缓存,如果缓存中已经存在了RootView,那么就可以直接设置到Activity布局,如果缓存中不存在,再去执行创建过程。


?



1

ReactNativePreLoader.preLoad(         this         ,         "HotRN"         );


我们在启动React Native前一个界面,执行preLoad方法优先加载出ReactRootView,此时就完成了视图预加载,让React Native界面达到秒显的效果。

四、效果对比

优化前:                                                                                                     优化后:


react大屏技术架构图_ide

                             

react大屏技术架构图_react大屏技术架构图_02

Ok,到此想必大家都想撸起袖子体验一下了,那就开始吧~~ 源码已分享到Github,别忘了给颗star哦

~

项目源码:https://github.com/songxiaoliang/ReactNativeApp