React-Native 安卓预加载优化方案

本文作者:ivweb 朱灵子

React-Native安卓预加载优化方案

本文针对使用React Native开发混合应用的过程中安卓端白屏时间较长的问题,提出了react-native安卓端RootView预加载优化方案,本文主要围绕以下几个方面展开分析:

  • 导致React-Native安卓端白屏时间较长的关键性因素
  • React-Native安卓预加载优化方案
  • React-Native安卓预加载方案实现细节

导致React-Native安卓端白屏时间较长的关键性因素

我们对不同网络状态下不同机型的React-Native线上项目进行了实时性能监控,下图所示为React Native IOS和安卓端线上性能数据对比分析图

对比IOS端与Android端的首屏时间数据,我们发现安卓端占有一定的劣势,我们在启动React-Native安卓应用时,会发现第一次启动React-Native安卓页面会有一个短暂的白屏过程,而且在完全退出后再进入,仍然会有这个白屏,为什么Android端的白屏时间较IOS较长呢?我们首先分析React-Native页面加载各个阶段的时间响应图

通过观察我们可以发现,React-Native页面加载时间占比最大的是React-Native bundle离线包加载与解析的时间,其次是首屏数据获取的时间。针对首屏获取时间较长的问题,项目已经采用React-Native前端异步数据缓存优化方案,而且在IOS和安卓端数据返回的平均值均在180ms左右,而页面加载的过程中界面渲染以及框架初始化的时间占比均只有9.3%,不为导致IOS和安卓端首屏时间差异较大的关键因素。综上可知,导致React-Native安卓端白屏时间较长的关键性因素是bundle离线包加载与解析的时间较长,因为React-Native安卓端bundle离线包加载与解析的过程是在java端完成的,而IOS bundle离线包加载与解析的过程是在Objective-C端完成的,java的执行效率较低,同时部分安卓低端机型性能较差。

因此,java执行效率较OC来讲相对较低,安卓端机型总体性能与IOS相比占有相对劣势都是导致React-Native安卓端bundle离线包加载与解析的时间较长的原因,也是造成React-Native安卓端白屏时间较长的关键性因素。

React-Native安卓预加载优化方案

为了优化React-Native安卓端线上业务的用户体验,我们提出了React-Native安卓Bundle预加载优化方案

首先展示的是React-Native安卓源码端ReactActivity中的onCreate方法

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

ReactInstanceManager是用来创建及管理CatalyInstance的实例的上层接口、控制开发调试,生命周期与ReactRootView所在activity保持一致。ReactActivity onCreate方法中的getReactInstanceManager()步骤中执行了bundle离线包文件位置与bundle文件名的设置,如下代码所示

 ReactInstanceManager.Builder builder = ReactInstanceManager.builder()
  .setApplication(mApplication)
  .setJSMainModuleName(getJSMainModuleName())
  .setUseDeveloperSupport(getUseDeveloperSupport())
  .setRedBoxHandler(getRedBoxHandler())
  .setUIImplementationProvider(getUIImplementationProvider())
  .setInitialLifecycleState(LifecycleState.BEFORE_CREATE);

for (ReactPackage reactPackage : getPackages()) {
  builder.addPackage(reactPackage);
}

String jsBundleFile = getJSBundleFile();
if (jsBundleFile != null) {
  builder.setJSBundleFile(jsBundleFile);
} else {
  builder.setBundleAssetName(Assertions.assertNotNull(getBundleAssetName()));
}
builder.build();

为了优化 安卓端bundle离线包加载与解析的过程,我们需要将ReactActivity onCreate方法中的createRootView、startReactApplication以及getReactInstanceManager这些步骤提前,也就是实现react-native安卓端RootView预加载。

React-Native安卓预加载方案实现细节

创建预加载类ReactPreLoader

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
  • 同时为了优化React-Native线上项目内存方面的占用率,在ReactActivity销毁后,我们需要使用onDestroy()方法把view从 parent 上卸载下来

获取预加载之后缓存在本地ArrayMap中的rootView

为了获取并使用预加载之后缓存在本地ArrayMap中的rootView,我们需要侵入activity的创建过程,因此我们需要对React-Native原生库库提供的ReactActivity进行改造,以下列出修改方法:

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());
        }
    }
    public abstract ReactInfo getReactInfo();
}

使用预加载之后缓存在本地ArrayMap中的rootView

首先,在进入当前React-Native activity 的父级 activity调用ReactPreLoader中的init方法,如下图所示:

ReactPreLoader.init(this, ReactCardActivity.reactInfo);

其中ReactCardActivity继承上一个模块中对React-Native源码库进行简单改造后的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;
    }
}

该React-Native安卓端预加载优化方案可以很大程度上减少安卓端React-Native线上项目的白屏时间,优化React-Native线上业务的业务体验!

原文链接:http://www.ivweb.io/topic/5958de8b1eb29e24e8a098a4

原创声明,本文系作者授权云+社区发表,未经许可,不得转载。

如有侵权,请联系 yunjia_community@tencent.com 删除。

编辑于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏开发之途

Gradle 常用配置总结

当项目逐渐演进的过程中,主工程依赖的 Module 可能会越来越多,此时就需要统一配置各个 Module 的编译参数了

703
来自专栏yang0range

React Native在Android当中实践(四)——代码集成

首先在项目根目录中创建一个空的index.js文件。(注意在0.49版本之前是index.android.js文件) index.js是React Nativ...

582
来自专栏向治洪

Android原生嵌入React Native

1.首先集成的项目目录 我使用的是直接按照react-native init Project 的格式来导入的,也就是说,我的Android项目目录是跟node...

2607
来自专栏向治洪

iOS Hybrid 框架

前言 Hybrid App(混合模式移动应用)是指介于web-app、native-app这两者之间的app,兼具“Native App良好用户交互体验的优势”...

3148
来自专栏菩提树下的杨过

spring cloud 学习(6) - zuul 微服务网关

微服务架构体系中,通常一个业务系统会有很多的微服务,比如:OrderService、ProductService、UserService...,为了让调用更简单...

5088
来自专栏dalaoyang

路由网关---zuul

Zuul:Zuul 是在云平台上提供动态路由,监控,弹性,安全等边缘服务的框架。Zuul 相当于是设备和 Netflix 流应用的 Web 网站后端所有请求的前...

2909
来自专栏刘望舒

Android PMS处理APK的复制

在上一篇文章Android包管理机制之PackageInstaller安装APK中,我们学习了PackageInstaller是如何安装APK的,最后会将APK...

1285
来自专栏walterlv - 吕毅的博客

.NET 中 GetProcess 相关方法的性能

2018-08-19 07:04

583
来自专栏coding

django2实战3.模型的增删改查使用交互shell添加数据修改数据查询数据删除数据

django对数据的操作采用的是ORM模式,即将数据库的增删改查抽象成对象方法的调用,开发人员只需要调用相关的方法,而不需要写sql语句。

1662
来自专栏向治洪

React Native调用Android相机图库

概述 在很多的React Native开发中,我们需要调用原生的api实现调用相机和图库的功能,网上用的最多的开源库如:react-native-image-p...

2195

扫码关注云+社区