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 线上业务的业务体验!
来源: https://cloud.tencent.com/developer/article/1005382?fromSource=waitui