ReactNative在Android上首屏加载白屏优化

  1. 背景:
    在项目集成了RN模块后,发现在Android从原生页面跳转到RN页面的时候,出现一段比较明显的白屏之后才加载出页面,这种情况在iOS上的体验就比较好,所以查看了下网上的解决方案,发现在官方的中文文档上有一篇文章给了具体的解决步骤,因此决定试试。
    在这里要吐槽下,百度出来的解决 方案基本都是一样的,估计copy的人自己都没有跑过,代码写的都很乱,也没有完整的Demo可以参考,不过思路基本可以看懂,代码还是要自己写一写的。
    文档地址: http://reactnative.cn/post/754

  2. 解决思路:
    用内存换时间,在App初始化的时候就把RN的View加载到缓存,跳转时直接取缓存,达到秒开的效果。
    实际开发之后,发现第一次加载还是有短暂的白屏现象,但是相对之前已经不明显了,而且第一次加载之后的跳转都没有了白屏现象,可以算是成功的优化方案了。

  3. 具体步骤:
    3.1 新建缓存RootView管理器RNCacheViewManager

public class RNCacheViewManager {

private static ReactRootView mRootView = null;
private static ReactInstanceManager mManager = null;
private static RnInfo mRnInfo = null;
//初始化方法,App启动时调用
public static void init(Activity act, RnInfo rnInfo) {
    if (mManager == null) {
        updateCache(act, rnInfo);
    }
}

//更新cache,适合于版本升级时候更新cache
public static void updateCache(Activity act, RnInfo rnInfo) {
    mRnInfo = rnInfo;
    mManager = createReactInstanceManager(act);
    mRootView = new ReactRootView(act);
    mRootView.startReactApplication(mManager, rnInfo.getMainComponentName(), null);
}

public static ReactRootView getReactRootView() {
    if(mRootView==null){
        throw new RuntimeException("缓存view管理器尚未初始化!");
    }
    return mRootView;
}
public static RnInfo getRnInfo() {
    if(mRnInfo==null){
        throw  new RuntimeException("缓存view管理器尚未初始化!");
    }
    return mRnInfo;
}
//这里要remove掉rootview的parent对象,否则下次再setContentView时候会报错
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.onHostDestroy();
            mManager = null;
        }
        if (mRootView != null) {
            onDestroy();
            mRootView = null;
        }
        mRnInfo = null;
    } catch (Throwable e) {
        e.printStackTrace();
    }
}
//创建 ReactInstanceManager
private static ReactInstanceManager createReactInstanceManager(Activity act) {

    ReactInstanceManager mReactInstanceManager = ReactInstanceManager.builder()
            .setApplication(act.getApplication())
            .setBundleAssetName(getRnInfo().getBundleAssetName())
            .setJSMainModuleName(getRnInfo().getJSMainModuleName())
            .addPackage(getRnInfo().getMainReactPackage())
            .setUseDeveloperSupport(getRnInfo().getUseDeveloperSupport())
            .setInitialLifecycleState(LifecycleState.BEFORE_RESUME).build();

    return mReactInstanceManager;
}

}
```
RnInfo只是自定义的一个类,里面只是ReactNative的一些配置信息,这里就不贴出来了,需要的自行到git上看。
3.2 创建一个Activity,将缓存的View加载到当前Activity

public class MyPreloadReactActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        //加载缓存的mReactRootView
        ReactRootView mReactRootView = RNCacheViewManager.getReactRootView();
        setContentView(mReactRootView);
    }

    @Override
    protected void onDestroy() {
        //退出时要调用要RNCacheViewManager remove掉rootview的parent对象,否则下次再setContentView时候会报错
        RNCacheViewManager.onDestroy();
        super.onDestroy();
    }
}

3.3 App启动初始化加载RN View到缓存中
这一步虽然调用简单,但是也是很重要的一步。

//初始化ReactNative缓存处理器
        RNCacheViewManager.init(MainActivity.this, new RnInfo());
 到这里基本就完成了,后面附上效果图跟Demo的github地址。
 要注意的是这个处理针对的是原生项目加载RN页面,而且是加载离线的bundle文件。基友说iOS也是需要使用这种预加载策略的,之后再研究下iOS的预加载,研究成功的话再整理下实现的步骤。
  1. 效果图:
    4.1 未使用缓存加载:
cache.gif

4.2 使用缓存之后:

cached.gif

5 . GitHub地址:
Android:
https://git.oschina.net/GStick/androidiosfeatreactnative.git

6 . 后续补充:
以上都是在Demo上开发的,功能比较简单。之后正式在项目上使用的过程中发现两个比较大的坑,在此补充说下问题跟解决办法:

6.1 由于项目中使用原生与RN代码交互时是将自定义的ReactPackage与当前Activity绑定的,而预加载是在当前Activity初始化之前缓存的RootView,所以导致加入预加载的代码之后之前交互代码都无效了。

解决办法:需要修改自定义的ReactPackage,将其与Application绑定,然后用堆栈管理Activity,在自定义的ReactContextBaseJavaModule中获取当前的Activity作交互。

6.2 RN中有一个Modal控件比较特殊,在Android上是渲染成Dialog,渲染时把当前ReactRootView的context传给了Dialog,而不是当前Activity的context,导致RN上所有Modal控件都在初始化RootView管理器所在的Activity上弹出了。

这个问题困扰了我好久,最后是翻过重重外墙,在外网上找到了一个不是很明显的解决方法:

MutableContextWrapper

懂一点iOS的我看到Mutable这个前缀大概就知道这个API的大概作用了[嘿嘿],然后又去翻了一下API文档就隐约看到希望了。
大概的解决思路就是,初始化RootView管理器的时候使用MutableContextWrapper包住初始化使用的Activity context,在进入RN所在的Activity的onCreate()中用 setBaseContext(Activity act)替换MutableContextWrapper中的context对象。

mRootView = new ReactRootView(mutableContextWrapper);
RNCacheViewManager.getMutableContextWrapper().setBaseContext(PreloadReactActivity.this);

6.3 这里多补充一个问题,由于我们的应用存在多个Tab切换的场景,测试过程中发现ScrollViewscrollTo方法偶尔会出现滚动停止的现象,导致tab的切换卡在中间。经过很久的研究发现不是RN的问题,而是当Activity在缓存了RootView的情况下,对RootView的各种属性保存存在问题。
解决办法:在Activity OnCreate()之后更新缓存里面的RootView

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,711评论 5 468
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,932评论 2 376
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,770评论 0 330
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,799评论 1 271
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,697评论 5 359
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,069评论 1 276
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,535评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,200评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,353评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,290评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,331评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,020评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,610评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,694评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,927评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,330评论 2 346
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,904评论 2 341

推荐阅读更多精彩内容

  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 171,266评论 25 707
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,563评论 18 139
  • 这是一个听歌的夜晚。 很久很久没有在晚上听歌了。 从前喜欢一个人的时候,总是喜欢把感情寄托在那么几首歌里。每当夜晚...
    栗栗子的秋天阅读 239评论 0 0
  • 最近,我真的很闲,闲到了只能靠打游戏消磨光阴的程度。从大一入学到现在,是一个轮回,从无事可做到无事愿做。秋叶落,西...
    Cliff_阅读 1,901评论 4 3
  • 真后悔在死的时候 没有穿一件维多利亚的秘密。。。。
    黑白2阅读 79评论 0 0