React Native 0.31 Bundle 預(yù)加載優(yōu)化

使用 React Native 開發(fā)混合應(yīng)用的過程中烦磁,我們?cè)诖蛲?bundle 進(jìn) release 包后滩愁,會(huì)發(fā)現(xiàn)第一次進(jìn)入頁面(React 的 Activity)會(huì)有一個(gè)短暫的白屏過程(在真機(jī)上近 1秒励翼,在模擬器上比較快镐侯,在 200毫秒 左右)技矮,而且在完全退出后再進(jìn)入泳炉,仍然會(huì)有這個(gè)白屏。

仔細(xì)查看加載過程(其實(shí)猜猜都能知道)后可以發(fā)現(xiàn)任洞,這個(gè)過程就是在加載我們的 js bundle蓄喇,通常即便是一個(gè)小的 RN 應(yīng)用(混合應(yīng)用中的子業(yè)務(wù)),也會(huì)動(dòng)輒到 1MB 的大小交掏,除非是完整的 RN 應(yīng)用妆偏,可以把這個(gè)當(dāng)做是啟動(dòng)速度,否則這樣的加載速度都是對(duì)用戶體驗(yàn)的很大傷害盅弛。

于是我們決定進(jìn)行 Bundle 預(yù)加載的優(yōu)化钱骂。

項(xiàng)目源碼上傳在:markzhai/react-native-preloader,稍后會(huì)上傳到 maven挪鹏,版本號(hào)會(huì)和 rn 保持一致见秽。

耗時(shí)操作

ReactActivityonCreate 方法:

@Override
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();
    }
  }

  mReactRootView = createRootView();
  mReactRootView.startReactApplication(
    getReactNativeHost().getReactInstanceManager(),
    getMainComponentName(),
    getLaunchOptions());
  setContentView(mReactRootView);
  mDoubleTapReloadRecognizer = new DoubleTapReloadRecognizer();
}

打點(diǎn)后可以發(fā)現(xiàn)耗時(shí)的其實(shí)是

  • createRootView();
  • startReactApplication();

這兩個(gè)操作,所以考慮只需要提前創(chuàng)建 ReactRootView 進(jìn)行 render讨盒,之后直接掛載該 view 上去即可解取。

預(yù)加載

創(chuàng)建預(yù)加載類 ReactPreLoader

/**
 * React Native Bundle Pre-loader.
 *
 * @author markzhai on 16/8/20
 * @version 1.3.0
 */
public class ReactPreLoader {

    private static final String TAG = "ReactPreLoader";

    private static final Map<String, ReactRootView> CACHE_VIEW_MAP =
            new ArrayMap<>();

    /**
     * Get {@link ReactRootView} with corresponding {@link ReactInfo}.
     */
    public static ReactRootView getRootView(ReactInfo reactInfo) {
        return CACHE_VIEW_MAP.get(reactInfo.getMainComponentName());
    }

    /**
     * Pre-load {@link ReactRootView} to local {@link Map}, you may want to
     * load it in previous activity.
     */
    public static void init(Activity activity, ReactInfo reactInfo) {
        if (CACHE_VIEW_MAP.get(reactInfo.getMainComponentName()) != null) {
            return;
        }
        ReactRootView rootView = new ReactRootView(activity);
        rootView.startReactApplication(
                ((ReactApplication) activity.getApplication()).getReactNativeHost().getReactInstanceManager(),
                reactInfo.getMainComponentName(),
                reactInfo.getLaunchOptions());
        CACHE_VIEW_MAP.put(reactInfo.getMainComponentName(), rootView);
    }

    /**
     * Remove {@link ReactRootView} from parent.
     */
    public static void onDestroy(ReactInfo reactInfo) {
        try {
            ReactRootView rootView = getRootView(reactInfo);
            ViewGroup parent = (ViewGroup) rootView.getParent();
            if (parent != null) {
                parent.removeView(rootView);
            }
        } catch (Throwable e) {
            Logger.e(TAG, e);
        }
    }
}

在 init 操作中,我們通過 ReactInfo 緩存把 view 緩存在本地的 ArrayMap催植。

值得注意的是 onDestroy肮蛹,在 ReactActivity 銷毀后,我們需要把 view 從 parent 上卸載下來创南。

使用預(yù)加載的 view

使用預(yù)加載的 View伦忠,就需要侵入 activity 的創(chuàng)建過程,我們無法再使用 RN 庫提供的 ReactActivity稿辙,只能建立自己的昆码,以下列出修改的方法,其他方法照抄 ReactActivity

/**
 * Base Activity for React Native applications.
 *
 * @author markzhai on 16/7/28
 * @version 1.3.0
 */
public abstract class MrReactActivity extends Activity
        implements DefaultHardwareBackBtnHandler, PermissionAwareActivity {

    @Override
    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();
            }
        }

        mReactRootView = ReactPreLoader.getRootView(getReactInfo());

        if (mReactRootView != null) {
            Logger.i(TAG, "use pre-load view");
        } else {
            Logger.i(TAG, "createRootView");
            mReactRootView = createRootView();
            if (mReactRootView != null) {
                mReactRootView.startReactApplication(
                        getReactNativeHost().getReactInstanceManager(),
                        getMainComponentName(),
                        getLaunchOptions());
            }
        }

        setContentView(mReactRootView);

        mDoubleTapReloadRecognizer = new DoubleTapReloadRecognizer();
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();

        if (mReactRootView != null) {
            mReactRootView.unmountReactApplication();
            mReactRootView = null;
            ReactPreLoader.onDestroy(getReactInfo());
        }
//        getReactNativeHost().clear();
    }

    public abstract ReactInfo getReactInfo();
}

使用

在進(jìn)入該 RN activity 的上一個(gè) activity 調(diào)用:

ReactPreLoader.init(this, ReactCardActivity.reactInfo);

ReactCardActivity 繼承我們自己的 ReactActivity:

public class ReactCardActivity extends MrReactActivity {

    public static final ReactInfo reactInfo = new ReactInfo("card", null);

    @Override
    protected String getMainComponentName() {
        return reactInfo.getMainComponentName();
    }

    @Override
    public ReactInfo getReactInfo() {
        return reactInfo;
    }
}

優(yōu)化后可以達(dá)到瞬間加載邻储。

已知的坑

由于進(jìn)行了預(yù)加載赋咽,目前已知的問題是 Modal 無法顯示 —— 因?yàn)?Modal 在 Android 的實(shí)現(xiàn)使用了 Dialog,而該 View 將創(chuàng)建 ReactRootView 的 context 作為參數(shù)傳給了 Dialog吨娜,而不是實(shí)際運(yùn)行時(shí)所在的 Activity context脓匿。查看源碼可以驗(yàn)證(com.facebook.react.views.modal)。

TimePickerAndroid 這類 picker 則沒有問題宦赠。

作為規(guī)避方案陪毡,目前使用 MutableContextWrapper
進(jìn)行 context 替換。見 GitHub 上的具體實(shí)現(xiàn)勾扭。

issue 9496

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末毡琉,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子妙色,更是在濱河造成了極大的恐慌桅滋,老刑警劉巖,帶你破解...
    沈念sama閱讀 219,490評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件身辨,死亡現(xiàn)場(chǎng)離奇詭異丐谋,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)栅表,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,581評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門笋鄙,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人怪瓶,你說我怎么就攤上這事萧落。” “怎么了洗贰?”我有些...
    開封第一講書人閱讀 165,830評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵找岖,是天一觀的道長。 經(jīng)常有香客問我敛滋,道長许布,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,957評(píng)論 1 295
  • 正文 為了忘掉前任绎晃,我火速辦了婚禮蜜唾,結(jié)果婚禮上杂曲,老公的妹妹穿的比我還像新娘。我一直安慰自己袁余,他們只是感情好擎勘,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,974評(píng)論 6 393
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著颖榜,像睡著了一般棚饵。 火紅的嫁衣襯著肌膚如雪村怪。 梳的紋絲不亂的頭發(fā)上喷橙,一...
    開封第一講書人閱讀 51,754評(píng)論 1 307
  • 那天,我揣著相機(jī)與錄音蜂莉,去河邊找鬼且蓬。 笑死欣硼,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的缅疟。 我是一名探鬼主播分别,決...
    沈念sama閱讀 40,464評(píng)論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼存淫!你這毒婦竟也來了耘斩?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,357評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤桅咆,失蹤者是張志新(化名)和其女友劉穎括授,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體岩饼,經(jīng)...
    沈念sama閱讀 45,847評(píng)論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡荚虚,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,995評(píng)論 3 338
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了籍茧。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片版述。...
    茶點(diǎn)故事閱讀 40,137評(píng)論 1 351
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖寞冯,靈堂內(nèi)的尸體忽然破棺而出渴析,到底是詐尸還是另有隱情,我是刑警寧澤吮龄,帶...
    沈念sama閱讀 35,819評(píng)論 5 346
  • 正文 年R本政府宣布俭茧,位于F島的核電站,受9級(jí)特大地震影響漓帚,放射性物質(zhì)發(fā)生泄漏母债。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,482評(píng)論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望毡们。 院中可真熱鬧迅皇,春花似錦、人聲如沸衙熔。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,023評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽青责。三九已至,卻和暖如春取具,著一層夾襖步出監(jiān)牢的瞬間脖隶,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,149評(píng)論 1 272
  • 我被黑心中介騙來泰國打工暇检, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留产阱,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,409評(píng)論 3 373
  • 正文 我出身青樓块仆,卻偏偏與公主長得像构蹬,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子悔据,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,086評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容