ReactNative安卓首屏白屏優化
原文地址:ReactNative安卓首屏白屏優化-github
問題描述
公司現有app中部分模塊使用reactnative開發,在實施的過程中,rn良好的兼容性,極佳的加載、動畫性能,提升了我們的開發、測試效率,提升了用戶體驗。
但是,在android中,當點擊某個rn模塊的入口按鈕,彈出rn的activity到rn的頁面展現出來的過程中,會有很明顯的白屏現象,不同的機型不同(cpu好的白屏時間短),大概1s到2s的時間。
注意,只有在真機上才會有此現象,在模擬器上沒有此現象完全是秒開。ios上也是秒開,測試的最低版本是ios7,iphone4s。
reactnative版本0.20.0。
jsbundle文件大小717kb。
優化效果
經過了大量的源碼閱讀,和網上資料查找,最終完美的解決了這個問題,無論什么機型,都可以達到秒開,如圖(雖然下圖是模擬器的截圖,但是真機效果基本一樣):
優化過程
時間分布
一般優化速度問題,首先就是要找到時間分布,然后根據二八原則,先優化耗時最長的部分。
android集成rn都會繼承官方提供的ReactActivity
public class MainActivity extends ReactActivity {
然后只在自己的activity中覆蓋一些配置項。
在官方的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),達到秒開。
步驟1 緩存rootview管理器
緩存rootview管理器主要用于初始化和緩存rootview對象。
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();
}
}
步驟2 重寫ReactActivity
將官方的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();
}
}
步驟3 在app啟動時候初始化緩存rootview管理器
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...