ReactNative安卓首屏白屏优化
vivid_gxp
8年前
<p>原文地址:<a href="/misc/goto?guid=4959670177595359000">ReactNative安卓首屏白屏优化-github</a></p> <p> </p> <p>问题描述</p> <p>公司现有app中部分模块使用reactnative开发,在实施的过程中,rn良好的兼容性,极佳的加载、动画性能,提升了我们的开发、测试效率,提升了用户体验。</p> <p>但是,在android中,当点击某个rn模块的入口按钮,弹出rn的activity到rn的页面展现出来的过程中,会有很明显的白屏现象,不同的机型不同(cpu好的白屏时间短),大概1s到2s的时间。</p> <p>注意,只有在真机上才会有此现象,在模拟器上没有此现象完全是秒开。ios上也是秒开,测试的最低版本是ios7,iphone4s。</p> <p>reactnative版本0.20.0。</p> <p>jsbundle文件大小717kb。</p> <p>优化效果</p> <p>经过了大量的源码阅读,和网上资料查找,最终完美的解决了这个问题,无论什么机型,都可以达到秒开,如图(虽然下图是模拟器的截图,但是真机效果基本一样):</p> <p><img src="https://simg.open-open.com/show/a51e7486b59b21bc628ccdfd7872a642.gif"></p> <p>优化过程</p> <h2>时间分布</h2> <p>一般优化速度问题,首先就是要找到时间分布,然后根据二八原则,先优化耗时最长的部分。</p> <p>android集成rn都会继承官方提供的ReactActivity</p> <pre> <code class="language-java">public class MainActivity extends ReactActivity {</code></pre> <p>然后只在自己的activity中覆盖一些配置项。</p> <p>在官方的ReactActivity中的onCreate方法中</p> <pre> <code class="language-java">protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); if (getUseDeveloperSupport() && Build.VERSION.SDK_INT >= 23) { // Get permission to show redbox in dev builds. if (!Settings.canDrawOverlays(this)) { Intent serviceIntent = new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION); startActivity(serviceIntent); FLog.w(ReactConstants.TAG, REDBOX_PERMISSION_MESSAGE); Toast.makeText(this, REDBOX_PERMISSION_MESSAGE, Toast.LENGTH_LONG).show(); } } mReactInstanceManager = createReactInstanceManager(); ReactRootView mReactRootView = createRootView(); mReactRootView.startReactApplication(mReactInstanceManager, getMainComponentName(), getLaunchOptions()); setContentView(mReactRootView); }</code></pre> <p>最慢的就是这两行代码,占了90%以上的时间。</p> <pre> <code class="language-java">ReactRootView mReactRootView = createRootView(); mReactRootView.startReactApplication(mReactInstanceManager, getMainComponentName(), getLaunchOptions());</code></pre> <p>这两行代码就是把jsbundle文件读入到内存中,并进行执行,然后初始化各个对象。</p> <h2>优化思路---内存换时间</h2> <p>在app启动时候,就将mReactRootView初始化出来,并缓存起来,在用的时候直接setContentView(mReactRootView),达到秒开。</p> <h3>步骤1 缓存rootview管理器</h3> <p>缓存rootview管理器主要用于初始化和缓存rootview对象。</p> <pre> <code class="language-java">import android.app.Activity; import android.os.Bundle; import android.view.ViewParent; import com.非死book.react.LifecycleState; import com.非死book.react.ReactInstanceManager; import com.非死book.react.ReactPackage; import com.非死book.react.ReactRootView; import java.lang.reflect.Field; /** * 缓存view管理 */ public class RNCacheViewManager { private static ReactRootView mRootView = null; private static ReactInstanceManager mManager = null; private static AbsRnInfo mRnInfo = null; //初始化 public static void init(Activity act, AbsRnInfo rnInfo) { init(act, rnInfo, null); } public static void init(Activity act, AbsRnInfo rnInfo, Bundle launchOptions) { if (mManager == null) { updateCache(act, rnInfo, launchOptions); } } public static void updateCache(Activity act, AbsRnInfo rnInfo) { updateCache(act, rnInfo, null); } //更新cache,适合于版本升级时候更新cache public static void updateCache(Activity act, AbsRnInfo rnInfo, Bundle launchOptions) { mRnInfo = rnInfo; mManager = createReactInstanceManager(act); mRootView = new ReactRootView(act); mRootView.startReactApplication(mManager, rnInfo.getMainComponentName(), launchOptions); } //设置模块名称,因为是private,只能通过反射赋值 public static void setModuleName(String moduleName) { try { Field field = ReactRootView.class.getDeclaredField("mJSModuleName"); field.setAccessible(true); field.set(getReactRootView(), moduleName); } catch (Throwable e) { throw new RuntimeException(e); } } //设置启动参数,因为是private,只能通过反射赋值 public static void setLaunchOptions(Bundle launchOptions) { try { Field field = ReactRootView.class.getDeclaredField("mLaunchOptions"); field.setAccessible(true); field.set(getReactRootView(), launchOptions); } catch (Throwable e) { throw new RuntimeException(e); } } public static ReactRootView getReactRootView() { if(mRootView==null){ throw new RuntimeException("缓存view管理器尚未初始化!"); } return mRootView; } public static ReactInstanceManager getReactInstanceManager() { if(mManager==null){ throw new RuntimeException("缓存view管理器尚未初始化!"); } return mManager; } public static AbsRnInfo getRnInfo() { if(mRnInfo==null){ throw new RuntimeException("缓存view管理器尚未初始化!"); } return mRnInfo; } 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.onDestroy(); mManager = null; } if (mRootView != null) { onDestroy(); mRootView = null; } mRnInfo = null; } catch (Throwable e) { e.printStackTrace(); } } private static ReactInstanceManager createReactInstanceManager(Activity act) { ReactInstanceManager.Builder builder = ReactInstanceManager.builder() .setApplication(act.getApplication()) .setJSMainModuleName(getRnInfo().getJSMainModuleName()) .setUseDeveloperSupport(getRnInfo().getUseDeveloperSupport()) .setInitialLifecycleState(LifecycleState.BEFORE_RESUME); for (ReactPackage reactPackage : getRnInfo().getPackages()) { builder.addPackage(reactPackage); } String jsBundleFile = getRnInfo().getJSBundleFile(); if (jsBundleFile != null) { builder.setJSBundleFile(jsBundleFile); } else { builder.setBundleAssetName(getRnInfo().getBundleAssetName()); } return builder.build(); } } </code></pre> <h3>步骤2 重写ReactActivity</h3> <p>将官方的ReactActivity粘出来,重写2个方法,onCreate和onDestroy,其余代码不动。</p> <p>onCreate方法中使用缓存rootview管理器来获得rootview对象,而不是重新创建。</p> <p>这里曾尝试继承ReactActivity,而不是重写这个类,但是子类覆盖onCreate方法时候,必须要调用super.onCreate,否则编译会报错,但是super.onCreate方法会重新创建rootview,所以实在是绕不过去了。</p> <pre> <code class="language-java">protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); if (RNCacheViewManager.getRnInfo().getUseDeveloperSupport() && Build.VERSION.SDK_INT >= 23) { // Get permission to show redbox in dev builds. if (!Settings.canDrawOverlays(this)) { Intent serviceIntent = new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION); startActivity(serviceIntent); FLog.w(ReactConstants.TAG, REDBOX_PERMISSION_MESSAGE); Toast.makeText(this, REDBOX_PERMISSION_MESSAGE, Toast.LENGTH_LONG).show(); } } mReactInstanceManager = RNCacheViewManager.getReactInstanceManager(); ReactRootView mReactRootView = RNCacheViewManager.getReactRootView(); setContentView(mReactRootView); }</code></pre> <p>onDestroy方法中,不能再调用原有的mReactInstanceManager.destroy()方法了,否则rn初始化出来的对象会被销毁,下次就用不了了。同时,要卸载掉rootview的parent对象,否则下次再setContentView时候回报错。</p> <pre> <code class="language-java">protected void onDestroy() { RNCacheViewManager.onDestroy(); super.onDestroy(); }</code></pre> <p>RNCacheViewManager.onDestroy的方法:</p> <pre> <code class="language-java">public static void onDestroy() { try { ViewParent parent = getReactRootView().getParent(); if (parent != null) ((android.view.ViewGroup) parent).removeView(getReactRootView()); } catch (Throwable e) { e.printStackTrace(); } }</code></pre> <h3>步骤3 在app启动时候初始化缓存rootview管理器</h3> <pre> <code class="language-java">RNCacheViewManager.init((Activity) context, new RnInfo(moduleName, launchOptions));</code></pre> <p>其中RnInfo如下:</p> <pre> <code class="language-java">public class RnInfo extends AbsRnInfo { private String mModuleName; private Bundle mLaunchOptions; public RnInfo(String moduleName) { this.mModuleName = moduleName; } public RnInfo(String moduleName, Bundle launchOptions) { this.mModuleName = moduleName; this.mLaunchOptions = launchOptions; } @Nullable @Override public Bundle getLaunchOptions() { return mLaunchOptions; } @Override public String getMainComponentName() { return mModuleName; } @Override public String getJSMainModuleName() { return RNKeys.Default.DEf_JS_MAIN_MODULE_NAME; } @Nullable @Override public String getJSBundleFile() { return RNManager.getJsBundlePath(); } @Override public boolean getUseDeveloperSupport() { return true; } @Override public List<ReactPackage> getPackages() { return Arrays.asList( new MainReactPackage(), new BBReactPackage() ); } }</code></pre> <p>结语</p> <p>希望本篇文档能帮助遇到类似问题的小伙伴们。</p> <p>reactnative虽然不是银弹,但是在目前移动端浏览器兼容性弱爆了的情况下,还是能极大的提升开发测试效率的,性能也是极佳的,看好rn的未来。</p> <p>参考文章</p> <p><a href="/misc/goto?guid=4959670177719528790">http://zhuanlan.zhihu.com/magilu/2058748...</a></p> <p><a href="/misc/goto?guid=4959670177819478892">http://zhuanlan.zhihu.com/magilu/2025970...</a></p> <p><a href="https://yq.aliyun.com/articles/3208?spm=5176.100239.yqblog1.39.t2g49u&utm_source=tuicool&utm_medium=referral">https://yq.aliyun.com/articles/3208?spm=...</a></p>