react-native 预加载优化

预加载的方案和原理网上其实也有非常多。我先简单的贴一下代码

Delegate

需要两个新增的类: 一个是继承 ReactActivityDelegate 用于承载 Rn 的 activity 重写 createReactActivityDelegate 方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
public class DPReactActivityDelegate extends ReactActivityDelegate {
private Activity mActivity;
private String mMainComponentName;
private ReactRootView mReactRootView;
public DPReactActivityDelegate(Activity activity, @Nullable String mainComponentName) {
super(activity, mainComponentName);
mActivity = activity;
mMainComponentName = mainComponentName;
}
@Override
protected void onCreate(Bundle savedInstanceState) {
// 先设置一个 contentView,保证加载的时候不是白屏
mActivity.setContentView(R.layout.activity_launch);
Class<ReactActivityDelegate> clazz = ReactActivityDelegate.class;
try {
Field field = clazz.getDeclaredField("mDoubleTapReloadRecognizer");
field.setAccessible(true);
field.set(this, new DoubleTapReloadRecognizer());
} catch (Exception e) {
e.printStackTrace();
}
if (BuildConfig.DEBUG && Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && !Settings.canDrawOverlays(mActivity)) {
TextView textView = new TextView(mActivity);
textView.setLayoutParams(new LinearLayout.LayoutParams(LinearLayout.LayoutParams.WRAP_CONTENT, LinearLayout.LayoutParams.WRAP_CONTENT));
textView.setText(RNCacheViewManager.REDBOX_PERMISSION_MESSAGE + "\nPlease exit the app and grant again");
textView.setTextColor(Color.rgb(255, 0, 0));
mActivity.setContentView(textView);
} else {
loadApp(null);
}
}
@Override
protected void onDestroy() {
RNCacheViewManager.onDestroy(mActivity);
super.onDestroy();
}
@Override
protected void loadApp(String appKey) {
if (mReactRootView != null) {
throw new IllegalStateException("Cannot loadApp while app is already running.");
}
//从缓存中读取初始化好的ReactRootView,如果不为空直接进行加载
ReactRootView reactRootView = RNCacheViewManager.getRootView(mActivity);
// 在 xml 中其实添加了一个 framLayout 作为 rn rootview 的载体
FrameLayout frameLayout = mActivity.findViewById(R.id.rn_rootView);
if(reactRootView == null){
mReactRootView = createRootView();
mReactRootView.startReactApplication(
getReactNativeHost().getReactInstanceManager(),
appKey,
getLaunchOptions());
frameLayout.addView(mReactRootView);
}else{
frameLayout.addView(reactRootView);
}
frameLayout.setVisibility(View.VISIBLE);
}
}

某些小 app 可能就一个 activity ,所以启动页可以使用 windowBackground 替代。而目前大部分 app 的 hybrid 开发的,前面的页面基本都是 native, 这样就会导致打开后,第一时间看到的并不是 windowBackground,而是空白的 activity,所以这个时候需要先人为添加一个 contentView,然后再加载完 reactRootView 后,add 到 我们的 contentView 的节点中。

CacheViewManager

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
public class RNCacheViewManager {
public static final int REQUEST_OVERLAY_PERMISSION_CODE = 1111;
public static final String REDBOX_PERMISSION_MESSAGE =
"Overlay permissions needs to be granted in order for react native apps to run in dev mode";
private static ReactRootView mRootView = null;
public static ReactRootView getRootView(Activity activity) {
if (mRootView.getContext() instanceof MutableContextWrapper) {
((MutableContextWrapper) mRootView.getContext()).setBaseContext(
activity
);
}
return mRootView;
}
public static ReactNativeHost getReactNativeHost(Activity activity) {
return ((ReactApplication) activity.getApplication()).getReactNativeHost();
}
public static void init(Activity act, String moduleName, Bundle lauchOptions) {
boolean needsOverlayPermission = false;
if (mRootView != null) {
return;
}
if (BuildConfig.DEBUG && Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && !Settings.canDrawOverlays(act)) {
needsOverlayPermission = true;
Intent serviceIntent = new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION, Uri.parse("package:" + act.getPackageName()));
FLog.w(ReactConstants.TAG, REDBOX_PERMISSION_MESSAGE);
Toast.makeText(act, REDBOX_PERMISSION_MESSAGE, Toast.LENGTH_LONG).show();
act.startActivityForResult(serviceIntent, REQUEST_OVERLAY_PERMISSION_CODE);
}
if (!needsOverlayPermission) {
mRootView = new ReactRootView(new MutableContextWrapper(act.getApplicationContext()));
mRootView.startReactApplication(
getReactNativeHost(act).getReactInstanceManager(),
moduleName,
lauchOptions);
}
}
/**
* 不能再调用原有的销毁方法,否则rn初始化出来的对象会被销毁,同时
* 在ReactActivity销毁后,我们需要把 view从父视图中移除。
*/
public static void onDestroy(Activity activity) {
try {
ReactRootView reactRootView = getRootView(activity);
if (reactRootView.getContext() instanceof MutableContextWrapper) {
((MutableContextWrapper) reactRootView.getContext()).setBaseContext(
activity.getApplicationContext()
);
}
ViewParent parent = getRootView(activity).getParent();
if (parent != null)
((android.view.ViewGroup) parent).removeView(getRootView(activity));
} catch (Throwable e) {
e.printStackTrace();
}
}
}

这边使用了 MutableContextWrapper,是因为 rn 加载的 activity 和 预加载的 activity 不是同一个 activity,这样会导致 js 中的 modal ,也就是 android 中的 dialog,会提示 activity 已销毁,无法弹出。所以在 init 的时候,是使用 MutableContextWrapper,而在 getRootView 的时候,再重新 setBaseContext ,把真正的容器 activity 设置进去。这样就能保证 dialog 弹出的 是在当前的 activity。

使用

在前置 activity 中:

1
2
3
// 在 startActivity 前调用这个,时机自己把握
RNCacheViewManager.init(this, "DataPlusReactNative", null);

在 reactActivity 中:

1
2
3
4
5
@Override
protected ReactActivityDelegate createReactActivityDelegate() {
return new DPReactActivityDelegate(this, getMainComponentName());
}

使用自定义的 delegate 就好了。

总结

这样做可以灵活的显示 reactRootView 未加载完之前要显示的界面。效果也挺明显的,但是我比较懒,就不贴图啦。

Abner_泥阿布 wechat
欢迎您扫一扫上面的微信公众号,订阅我们的公众号!
或者欢迎加入QQ群:568863373。


如果你觉得这篇文章对你有帮助,请点击下面的分享链接,你还可以选择扫描二维码进行打赏!

我的Github

我的新浪微博