原文地址:ReactNative安卓首屏白屏優化-githubjava
公司現有app中部分模塊使用reactnative開發,在實施的過程當中,rn良好的兼容性,極佳的加載、動畫性能,提高了咱們的開發、測試效率,提高了用戶體驗。react
可是,在android中,當點擊某個rn模塊的入口按鈕,彈出rn的activity到rn的頁面展示出來的過程當中,會有很明顯的白屏現象,不一樣的機型不一樣(cpu好的白屏時間短),大概1s到2s的時間。android
注意,只有在真機上纔會有此現象,在模擬器上沒有此現象徹底是秒開。ios上也是秒開,測試的最低版本是ios7,iphone4s。ios
reactnative版本0.20.0。git
jsbundle文件大小717kb。github
通過了大量的源碼閱讀,和網上資料查找,最終完美的解決了這個問題,不管什麼機型,均可以達到秒開,如圖(雖然下圖是模擬器的截圖,可是真機效果基本同樣):瀏覽器
通常優化速度問題,首先就是要找到時間分佈,而後根據二八原則,先優化耗時最長的部分。緩存
android集成rn都會繼承官方提供的ReactActivityapp
public class MainActivity extends ReactActivity {
而後只在本身的activity中覆蓋一些配置項。iphone
在官方的ReactActivity中的onCreate方法中
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); }
最慢的就是這兩行代碼,佔了90%以上的時間。
ReactRootView mReactRootView = createRootView(); mReactRootView.startReactApplication(mReactInstanceManager, getMainComponentName(), getLaunchOptions());
這兩行代碼就是把jsbundle文件讀入到內存中,並進行執行,而後初始化各個對象。
在app啓動時候,就將mReactRootView初始化出來,並緩存起來,在用的時候直接setContentView(mReactRootView),達到秒開。
緩存rootview管理器主要用於初始化和緩存rootview對象。
import android.app.Activity; import android.os.Bundle; import android.view.ViewParent; import com.facebook.react.LifecycleState; import com.facebook.react.ReactInstanceManager; import com.facebook.react.ReactPackage; import com.facebook.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(); } }
將官方的ReactActivity粘出來,重寫2個方法,onCreate和onDestroy,其他代碼不動。
onCreate方法中使用緩存rootview管理器來得到rootview對象,而不是從新建立。
這裏曾嘗試繼承ReactActivity,而不是重寫這個類,可是子類覆蓋onCreate方法時候,必需要調用super.onCreate,不然編譯會報錯,可是super.onCreate方法會從新建立rootview,因此實在是繞不過去了。
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); }
onDestroy方法中,不能再調用原有的mReactInstanceManager.destroy()方法了,不然rn初始化出來的對象會被銷燬,下次就用不了了。同時,要卸載掉rootview的parent對象,不然下次再setContentView時候回報錯。
protected void onDestroy() { RNCacheViewManager.onDestroy(); super.onDestroy(); }
RNCacheViewManager.onDestroy的方法:
public static void onDestroy() { try { ViewParent parent = getReactRootView().getParent(); if (parent != null) ((android.view.ViewGroup) parent).removeView(getReactRootView()); } catch (Throwable e) { e.printStackTrace(); } }
RNCacheViewManager.init((Activity) context, new RnInfo(moduleName, launchOptions));
其中RnInfo以下:
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() ); } }
但願本篇文檔能幫助遇到相似問題的小夥伴們。
reactnative雖然不是銀彈,可是在目前移動端瀏覽器兼容性弱爆了的狀況下,仍是能極大的提高開發測試效率的,性能也是極佳的,看好rn的將來。
http://zhuanlan.zhihu.com/magilu/2058748...