ReactNative安卓首屏白屏優化

vivid_gxp 8年前發布 | 32K 次閱讀 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...

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

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

 本文由用戶 vivid_gxp 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!