ReactNative安卓首屏白屏優化

原文地址: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

優化效果

通過了大量的源碼閱讀,和網上資料查找,最終完美的解決了這個問題,不管什麼機型,均可以達到秒開,如圖(雖然下圖是模擬器的截圖,可是真機效果基本同樣):瀏覽器

clipboard.png

優化過程

時間分佈

通常優化速度問題,首先就是要找到時間分佈,而後根據二八原則,先優化耗時最長的部分。緩存

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),達到秒開。

步驟1 緩存rootview管理器

緩存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();
    }
}

步驟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...

http://zhuanlan.zhihu.com/magilu/2025970...

https://yq.aliyun.com/articles/3208?spm=...

相關文章
相關標籤/搜索