当在原生的app里面加载rn模块的时候,第一次加载的时候会发现加载的时候过长,白屏出现时间过长,这个因为生成rootview的时间过长导致,通过分析react native启动流程可以看出是因为createRootView和startReactApplication消耗时间较长,简单来说
那么我们可以提前加载bundle文件,比如可以在application生成的时候提前加载。
private static final Map CACHE = new WeakHashMap<>();
/**
* 初始化ReactRootView,并添加到缓存
*
* @param context 上下文对象
* @param componentName 加载的组件名
*/
public static void preLoad(Context context, String componentName) {
if (CACHE.get(componentName) != null) {
return;
}
ReactRootView rootView = new ReactRootView(new MutableContextWrapper(context));
rootView.startReactApplication(
((MainApplication) context.getApplicationContext()).getReactNativeHost().getReactInstanceManager(),
componentName,
null);
CACHE.put(componentName, rootView);
}
页面加载在reactnative中是交给ReactActivityDelegate去实现的,
protected void onCreate(Bundle savedInstanceState) {
if (mMainComponentName != null) {
loadApp(mMainComponentName);
}
mDoubleTapReloadRecognizer = new DoubleTapReloadRecognizer();
}
protected void loadApp(String appKey) {
if (mReactRootView != null) {
throw new IllegalStateException("Cannot loadApp while app is already running.");
}
mReactRootView = createRootView();
mReactRootView.startReactApplication(
getReactNativeHost().getReactInstanceManager(),
appKey,
getLaunchOptions());
getPlainActivity().setContentView(mReactRootView);
}
我们可以在重新自定义一个ReactActivityDelegate,重写onCreate方法获取我们提前预先加载缓存好的rootview
protected void onCreate(Bundle savedInstanceState) {
if (mMainComponentName != null) {
loadApp(mMainComponentName);
}
mDoubleTapReloadRecognizer = new DoubleTapReloadRecognizer();
}
protected void loadApp(String appKey) {
if (mReactRootView != null) {
throw new IllegalStateException("Cannot loadApp while app is already running.");
}
mReactRootView = RNRootViewPreLoader.getReactRootView(mMainComponentName);
if (mReactRootView == null)
mReactRootView = RNRootViewPreLoader.startReactApplication(
getPlainActivity(), getReactNativeHost().getReactInstanceManager(),
appKey,
getLaunchOptions());
getPlainActivity().setContentView(mReactRootView);
}
在loadApp方法里,首先在缓存里获取rootview,如果没有的话就生成一个rootview并把它存到缓存里,最后在我们Activity初始化的时候生成我们自定义的delegate
protected RNPreLoadDelegate createPreLoadReactDelegate() {
return new RNPreLoadDelegate(this, getMainComponentName());
}
这里贴一下
BUTTON是rn传统的启动方式,跳转到MainActivity,BUTTON1是预加载的启动方式,跳转到Main1Activity,可以看出BUTTON跳转的时候有一个白屏
但是上面代码是有问题的,问题在于preLoad这个方法context这个入参,context在这里考虑两种情况,
1.context是application,我们在js的代码里加一个modal(对应android的对话框)
可以看出这个是不支持展示对话框的
2.context是activity,我们在activity里面调用预加载preload方法,传入activity作为上下文对象,这样对话框可以正常展示
但是我们传入的是activity的context,并且缓存的这个rootview,那么就产生了内存泄漏,这也是有问题的
那我们可以考虑在缓存的时候用application的context来保证内存不泄露,在使用的时候替换成activity的context来展示modal,可以使用MutableContextWrapper,可以看一下RNRootViewPreLoader的具体代码
private static final Map CACHE = new WeakHashMap<>();
/**
* 初始化ReactRootView,并添加到缓存
*
* @param context 上下文对象
* @param componentName 加载的组件名
*/
public static void preLoad(Context context, String componentName) {
if (CACHE.get(componentName) != null) {
return;
}
ReactRootView rootView = new ReactRootView(new MutableContextWrapper(context.getApplicationContext()));
rootView.startReactApplication(
((MainApplication) context.getApplicationContext()).getReactNativeHost().getReactInstanceManager(),
componentName,
null);
CACHE.put(componentName, rootView);
}
/**
* 获取ReactRootView
*
* @param componentName 加载的组件名
* @return ReactRootView
*/
public static ReactRootView getReactRootView(Activity activity, String componentName) {
ReactRootView rootView = CACHE.get(componentName);
if (rootView.getContext() instanceof MutableContextWrapper) {
((MutableContextWrapper) rootView.getContext()).setBaseContext(
activity
);
}
return rootView;
}
/**
* 从当前界面移除 ReactRootView
*
* @param componentName 加载的组件名
*/
public static void detachView(String componentName) {
try {
ReactRootView rootView = CACHE.get(componentName);
if (rootView == null)
return;
ViewGroup parent = (ViewGroup) rootView.getParent();
if (parent != null) {
parent.removeView(rootView);
}
if (rootView.getContext() instanceof MutableContextWrapper) {
((MutableContextWrapper) rootView.getContext()).setBaseContext(
rootView.getContext().getApplicationContext()
);
}
} catch (Throwable e) {
Log.e("RNRootViewPreLoader", e.getMessage());
}
}
代码里面在预加载的时候传入application生成rootview,真正需要界面加载的时候,替换成activity,在activity finish的时候,将rootview的context再次替换为application
上述的context问题解决了,但是设想一下比如我们app端需要传递token,cookies到rn以便rn调用网络请求,代码如下
public class TokenModule extends ReactContextBaseJavaModule {
public static String NET_USER_ID = "NET_USER1";
public TokenModule(ReactApplicationContext reactContext) {
super(reactContext);
}
@Override
public String getName() {
return "TokenModule";
}
@Override
public Map getConstants() {
final Map constants = new HashMap<>();
constants.put("NET_USER_ID", NET_USER_ID);
return constants;
}
}
我们在点击button3的时候将TokenModule.NET_USER_ID 设置为 “NET_USER2”;
发现预加载的值不会变,而button对应的界面由于可以在activity的层面重新设置它的delegate,所以不会有这样的问题,代码如下
public class MainActivity extends ReactActivity {
ReactNativeHost mReactNativeHost;
/**
* Returns the name of the main component registered from JavaScript.
* This is used to schedule rendering of the component.
*/
@Override
protected String getMainComponentName() {
return "AwesomeProject";
}
@Override
protected void onCreate(Bundle savedInstanceState) {
mReactNativeHost = new ReactNativeHost(getApplication()) {
@Override
public boolean getUseDeveloperSupport() {
return BuildConfig.DEBUG;
}
@Override
protected List getPackages() {
return Arrays.asList(
new MainReactPackage(),
new AccountPackage()
);
}
@Override
protected String getJSMainModuleName() {
return "index";
}
};
super.onCreate(savedInstanceState);
}
@Override
protected ReactActivityDelegate createReactActivityDelegate() {
return new ReactActivityDelegate(this, getMainComponentName()) {
@Override
protected ReactNativeHost getReactNativeHost() {
return mReactNativeHost;
}
};
}
}
而如果我们在Main1Activity按照这种方式是行不通的,因为ReactContext早已经生成,在这种情况下,就需要重新生成rootView的ReactContext,代码如下
public static void refreshRootView(String componentName) {
ReactRootView rootView = CACHE.get(componentName);
if (rootView == null)
return;
// 如果不为空,重新生成ReactContext
rootView.getReactInstanceManager().recreateReactContextInBackground();
}
可以看出运行结果,数据确实变了
综上,就实现了react native bundle文件的预加载
主要代码如下:
RNPreLoadActivity.java
package com.awesomeproject.preloadreact;
import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.view.KeyEvent;
import com.facebook.react.ReactInstanceManager;
import com.facebook.react.ReactNativeHost;
import com.facebook.react.modules.core.DefaultHardwareBackBtnHandler;
import com.facebook.react.modules.core.PermissionAwareActivity;
import com.facebook.react.modules.core.PermissionListener;
import javax.annotation.Nullable;
public abstract class RNPreLoadActivity extends Activity implements DefaultHardwareBackBtnHandler, PermissionAwareActivity {
private RNPreLoadDelegate mPreLoadReactDelegate;
protected RNPreLoadActivity() {
mPreLoadReactDelegate = createPreLoadReactDelegate();
}
protected RNPreLoadDelegate createPreLoadReactDelegate() {
return new RNPreLoadDelegate(this, getMainComponentName());
}
/**
* 子类重写,返回RN对应的界面组件名称
*
* @return String 需要加载的组建名
*/
protected @Nullable
String getMainComponentName() {
return null;
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mPreLoadReactDelegate.onCreate(savedInstanceState);
}
@Override
protected void onPause() {
super.onPause();
mPreLoadReactDelegate.onPause();
}
@Override
protected void onResume() {
super.onResume();
mPreLoadReactDelegate.onResume();
}
@Override
protected void onDestroy() {
super.onDestroy();
mPreLoadReactDelegate.onDestroy();
RNRootViewPreLoader.detachView(getMainComponentName());
}
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
mPreLoadReactDelegate.onActivityResult(requestCode, resultCode, data);
}
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
return mPreLoadReactDelegate.onKeyDown(keyCode, event) || super.onKeyDown(keyCode, event);
}
@Override
public boolean onKeyUp(int keyCode, KeyEvent event) {
return mPreLoadReactDelegate.onKeyUp(keyCode, event) || super.onKeyUp(keyCode, event);
}
@Override
public boolean onKeyLongPress(int keyCode, KeyEvent event) {
return mPreLoadReactDelegate.onKeyLongPress(keyCode, event) || super.onKeyLongPress(keyCode, event);
}
@Override
public void onBackPressed() {
if (!mPreLoadReactDelegate.onBackPressed()) {
super.onBackPressed();
}
}
@Override
public void invokeDefaultOnBackPressed() {
super.onBackPressed();
}
@Override
public void onNewIntent(Intent intent) {
if (!mPreLoadReactDelegate.onNewIntent(intent)) {
super.onNewIntent(intent);
}
}
@Override
public void requestPermissions(
String[] permissions,
int requestCode,
PermissionListener listener) {
mPreLoadReactDelegate.requestPermissions(permissions, requestCode, listener);
}
@Override
public void onRequestPermissionsResult(
int requestCode,
String[] permissions,
int[] grantResults) {
mPreLoadReactDelegate.onRequestPermissionsResult(requestCode, permissions, grantResults);
}
protected final ReactNativeHost getReactNativeHost() {
return mPreLoadReactDelegate.getReactNativeHost();
}
protected final ReactInstanceManager getReactInstanceManager() {
return mPreLoadReactDelegate.getReactInstanceManager();
}
protected final void loadApp(String appKey) {
mPreLoadReactDelegate.loadApp(appKey);
}
}
RNPreLoadDelegate.java
package com.awesomeproject.preloadreact;
import android.annotation.TargetApi;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.os.Build;
import android.os.Bundle;
import android.support.v4.app.FragmentActivity;
import android.view.KeyEvent;
import com.facebook.infer.annotation.Assertions;
import com.facebook.react.ReactApplication;
import com.facebook.react.ReactInstanceManager;
import com.facebook.react.ReactNativeHost;
import com.facebook.react.ReactRootView;
import com.facebook.react.bridge.Callback;
import com.facebook.react.devsupport.DoubleTapReloadRecognizer;
import com.facebook.react.modules.core.DefaultHardwareBackBtnHandler;
import com.facebook.react.modules.core.PermissionListener;
import javax.annotation.Nullable;
public class RNPreLoadDelegate {
private final @Nullable
Activity mActivity;
private final @Nullable
FragmentActivity mFragmentActivity;
private final @Nullable
String mMainComponentName;
private @Nullable
ReactRootView mReactRootView;
private @Nullable
DoubleTapReloadRecognizer mDoubleTapReloadRecognizer;
private @Nullable
PermissionListener mPermissionListener;
private @Nullable
Callback mPermissionsCallback;
public RNPreLoadDelegate(Activity activity, @Nullable String mainComponentName) {
mActivity = activity;
mMainComponentName = mainComponentName;
mFragmentActivity = null;
}
public RNPreLoadDelegate(
FragmentActivity fragmentActivity,
@Nullable String mainComponentName) {
mFragmentActivity = fragmentActivity;
mMainComponentName = mainComponentName;
mActivity = null;
}
protected @Nullable
Bundle getLaunchOptions() {
return null;
}
protected ReactRootView createRootView() {
return new ReactRootView(getContext());
}
/**
* Get the {@link ReactNativeHost} used by this app. By default, assumes
* {@link Activity#getApplication()} is an instance of {@link ReactApplication} and calls
* {@link ReactApplication#getReactNativeHost()}. Override this method if your application class
* does not implement {@code ReactApplication} or you simply have a different mechanism for
* storing a {@code ReactNativeHost}, e.g. as a static field somewhere.
*/
protected ReactNativeHost getReactNativeHost() {
return ((ReactApplication) getPlainActivity().getApplication()).getReactNativeHost();
}
public ReactInstanceManager getReactInstanceManager() {
return getReactNativeHost().getReactInstanceManager();
}
protected void onCreate(Bundle savedInstanceState) {
if (mMainComponentName != null) {
loadApp(mMainComponentName);
}
mDoubleTapReloadRecognizer = new DoubleTapReloadRecognizer();
}
protected void loadApp(String appKey) {
if (mReactRootView != null) {
throw new IllegalStateException("Cannot loadApp while app is already running.");
}
mReactRootView = RNRootViewPreLoader.getReactRootView(getPlainActivity(), mMainComponentName);
if (mReactRootView == null) {
mReactRootView = RNRootViewPreLoader.startReactApplication(
getPlainActivity(), getReactNativeHost().getReactInstanceManager(),
appKey,
getLaunchOptions());
}
getPlainActivity().setContentView(mReactRootView);
}
protected void onPause() {
if (getReactNativeHost().hasInstance()) {
getReactNativeHost().getReactInstanceManager().onHostPause(getPlainActivity());
}
}
protected void onResume() {
if (getReactNativeHost().hasInstance()) {
getReactNativeHost().getReactInstanceManager().onHostResume(
getPlainActivity(),
(DefaultHardwareBackBtnHandler) getPlainActivity());
}
if (mPermissionsCallback != null) {
mPermissionsCallback.invoke();
mPermissionsCallback = null;
}
}
protected void onDestroy() {
if (mReactRootView != null) {
mReactRootView.unmountReactApplication();
mReactRootView = null;
}
if (getReactNativeHost().hasInstance()) {
getReactNativeHost().getReactInstanceManager().onHostDestroy(getPlainActivity());
}
}
public void onActivityResult(int requestCode, int resultCode, Intent data) {
if (getReactNativeHost().hasInstance()) {
getReactNativeHost().getReactInstanceManager()
.onActivityResult(getPlainActivity(), requestCode, resultCode, data);
}
}
public boolean onKeyDown(int keyCode, KeyEvent event) {
if (getReactNativeHost().hasInstance()
&& getReactNativeHost().getUseDeveloperSupport()
&& keyCode == KeyEvent.KEYCODE_MEDIA_FAST_FORWARD) {
event.startTracking();
return true;
}
return false;
}
public boolean onKeyUp(int keyCode, KeyEvent event) {
if (getReactNativeHost().hasInstance() && getReactNativeHost().getUseDeveloperSupport()) {
if (keyCode == KeyEvent.KEYCODE_MENU) {
getReactNativeHost().getReactInstanceManager().showDevOptionsDialog();
return true;
}
boolean didDoubleTapR = Assertions.assertNotNull(mDoubleTapReloadRecognizer)
.didDoubleTapR(keyCode, getPlainActivity().getCurrentFocus());
if (didDoubleTapR) {
getReactNativeHost().getReactInstanceManager().getDevSupportManager().handleReloadJS();
return true;
}
}
return false;
}
public boolean onKeyLongPress(int keyCode, KeyEvent event) {
if (getReactNativeHost().hasInstance()
&& getReactNativeHost().getUseDeveloperSupport()
&& keyCode == KeyEvent.KEYCODE_MEDIA_FAST_FORWARD) {
getReactNativeHost().getReactInstanceManager().showDevOptionsDialog();
return true;
}
return false;
}
public boolean onBackPressed() {
if (getReactNativeHost().hasInstance()) {
getReactNativeHost().getReactInstanceManager().onBackPressed();
return true;
}
return false;
}
public boolean onNewIntent(Intent intent) {
if (getReactNativeHost().hasInstance()) {
getReactNativeHost().getReactInstanceManager().onNewIntent(intent);
return true;
}
return false;
}
@TargetApi(Build.VERSION_CODES.M)
public void requestPermissions(
String[] permissions,
int requestCode,
PermissionListener listener) {
mPermissionListener = listener;
getPlainActivity().requestPermissions(permissions, requestCode);
}
public void onRequestPermissionsResult(
final int requestCode,
final String[] permissions,
final int[] grantResults) {
mPermissionsCallback = new Callback() {
@Override
public void invoke(Object... args) {
if (mPermissionListener != null && mPermissionListener.onRequestPermissionsResult(requestCode, permissions, grantResults)) {
mPermissionListener = null;
}
}
};
}
private Context getContext() {
if (mActivity != null) {
return mActivity;
}
return Assertions.assertNotNull(mFragmentActivity);
}
private Activity getPlainActivity() {
return ((Activity) getContext());
}
}
RNRootViewPreLoader.java
package com.awesomeproject.preloadreact;
import android.app.Activity;
import android.content.Context;
import android.content.MutableContextWrapper;
import android.os.Bundle;
import android.util.Log;
import android.view.ViewGroup;
import com.awesomeproject.MainApplication;
import com.facebook.react.ReactInstanceManager;
import com.facebook.react.ReactRootView;
import java.util.Map;
import java.util.WeakHashMap;
public class RNRootViewPreLoader {
private static final Map CACHE = new WeakHashMap<>();
/**
* 初始化ReactRootView,并添加到缓存
*
* @param context 上下文对象
* @param componentName 加载的组件名
*/
public static void preLoad(Context context, String componentName) {
if (CACHE.get(componentName) != null) {
return;
}
ReactRootView rootView = new ReactRootView(new MutableContextWrapper(context.getApplicationContext()));
rootView.startReactApplication(
((MainApplication) context.getApplicationContext()).getReactNativeHost().getReactInstanceManager(),
componentName,
null);
CACHE.put(componentName, rootView);
}
/**
* 获取ReactRootView
*
* @param componentName 加载的组件名
* @return ReactRootView
*/
public static ReactRootView getReactRootView(Activity activity, String componentName) {
ReactRootView rootView = CACHE.get(componentName);
if (rootView.getContext() instanceof MutableContextWrapper) {
((MutableContextWrapper) rootView.getContext()).setBaseContext(
activity
);
}
return rootView;
}
/**
* 从当前界面移除 ReactRootView
*
* @param componentName 加载的组件名
*/
public static void detachView(String componentName) {
try {
ReactRootView rootView = CACHE.get(componentName);
if (rootView == null)
return;
ViewGroup parent = (ViewGroup) rootView.getParent();
if (parent != null) {
parent.removeView(rootView);
}
if (rootView.getContext() instanceof MutableContextWrapper) {
((MutableContextWrapper) rootView.getContext()).setBaseContext(
rootView.getContext().getApplicationContext()
);
}
} catch (Throwable e) {
Log.e("RNRootViewPreLoader", e.getMessage());
}
}
public static ReactRootView startReactApplication(Activity plainActivity, ReactInstanceManager reactInstanceManager, String componentName, Bundle launchOptions) {
ReactRootView rootView = new ReactRootView(plainActivity);
rootView.startReactApplication(
reactInstanceManager,
componentName,
launchOptions);
CACHE.put(componentName, rootView);
return rootView;
}
public static void refreshRootView(String componentName) {
ReactRootView rootView = CACHE.get(componentName);
if (rootView == null)
return;
// 如果不为空,重新生成ReactContext
rootView.getReactInstanceManager().recreateReactContextInBackground();
}
}