本文作者: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线上业务的业务体验!