基于最新版本React Native实现JsBundle预加载,界面秒开优化
来源:互联网 发布:ue编辑器 mac版 编辑:程序博客网 时间:2024/05/22 00:13
本文来自Songlcy投稿:文章地址:http://blog.csdn.net/u013718120/article/details/71538263
一、问题分析
本篇博客同样和大家分享关于React Native的内容。想必大家在撸码中都发现了一个问题:从Android原生界面第一次跳转到React Native界面时,会有短暂的白屏过程,然后才会加载出界面。下次再跳转就不会出现类似问题。并且当我们杀死应用,重新启动App从Android Activity跳转到RN界面,依然会出现短暂白屏。
刚创建的React Native交流10群:157867561,欢迎各位大牛,React Native技术爱好者加入交流!同时博客右侧欢迎微信扫描关注订阅号,移动技术干货,精彩文章技术推送!
为什么第一次加载React Native界面会出现短暂白屏呢?大家别忘了,React Native的渲染机制是对于JsBundle的加载。项目中所有的js文件最终会被打包成一个JsBundle文件,Android环境下Bundle文件为:‘index.android.bundle’。系统在第一次渲染界面时,会首先加载JsBundle文件。那么问题肯定出现在加载JsBundle这个过程,即出现白屏可能是因为JsBundle正在加载。发现了原因,我们继续查看源码,看看是否能从源码中得知一二。
二、源码分析
Android集成的RN界面,需要继承ReactActivity,那么直接从ReactActivity源码入手:
public abstract class ReactActivity extends Activity
implements DefaultHardwareBackBtnHandler, PermissionAwareActivity {
private final ReactActivityDelegate mDelegate;
protected ReactActivity() {
mDelegate = createReactActivityDelegate();
}
/**
* Returns the name of the main component registered from JavaScript.
* This is used to schedule rendering of the component.
* e.g. "MoviesApp"
*/
protected @Nullable String getMainComponentName() {
return
null
;
}
/**
* Called at construction time, override if you have a custom delegate implementation.
*/
protected ReactActivityDelegate createReactActivityDelegate() {
return
new
ReactActivityDelegate(
this
, getMainComponentName());
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super
.onCreate(savedInstanceState);
mDelegate.onCreate(savedInstanceState);
}
@Override
protected void onPause() {
super
.onPause();
mDelegate.onPause();
}
@Override
protected void onResume() {
super
.onResume();
mDelegate.onResume();
}
@Override
protected void onDestroy() {
super
.onDestroy();
mDelegate.onDestroy();
}
// 其余代码略......
}
不难发现,ReactActivity中的行为都交给了ReactActivityDelegate类来处理。很明显是委托模式。至于白屏原因是因为第一次创建时,那么我们直接看onCreate即可。找到ReactActivityDelegate的onCreate方法:
protected void onCreate(Bundle savedInstanceState) {
boolean needsOverlayPermission =
false
;
if
(getReactNativeHost().getUseDeveloperSupport() && Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
// Get permission to show redbox in dev builds.
if
(!Settings.canDrawOverlays(getContext())) {
needsOverlayPermission =
true
;
Intent serviceIntent =
new
Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION, Uri.parse(
"package:"
+ getContext().getPackageName()));
FLog.w(ReactConstants.TAG, REDBOX_PERMISSION_MESSAGE);
Toast.makeText(getContext(), REDBOX_PERMISSION_MESSAGE, Toast.LENGTH_LONG).show();
((Activity) getContext()).startActivityForResult(serviceIntent, REQUEST_OVERLAY_PERMISSION_CODE);
}
}
if
(mMainComponentName !=
null
&& !needsOverlayPermission) {
loadApp(mMainComponentName);
}
mDoubleTapReloadRecognizer =
new
DoubleTapReloadRecognizer();
}
从源码可以看到,最终调用了loadApp方法,继续跟踪loadApp方法:
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);
}
protected ReactRootView createRootView() {
return
new
ReactRootView(getContext());
}
loadApp方法中调用了createRootView创建了ReactRootView,即React Native界面,并且将界面设置到Activity中。那么问题很可能出现在这了。插个断点,调试看看执行时间。
一切恍然大悟,在createRootView和startReactApplication时,消耗了较长时间。
既然是createRootView和startReactApplication执行了耗时操作的问题,那么我们只需要将其提前执行,创建出ReactRootView并缓存下来。当跳转到React Native界面时,直接设置到ContentView即可。有了解决思路,又该到我们甩起袖子撸码了。
三、功能实现
/**
* 预加载工具类
* Created by Song on 2017/5/10.
*/
public class ReactNativePreLoader {
private static final Map<String,ReactRootView> CACHE =
new
ArrayMap<>();
/**
* 初始化ReactRootView,并添加到缓存
* @param activity
* @param componentName
*/
public static void preLoad(Activity activity, String componentName) {
if
(CACHE.get(componentName) !=
null
) {
return
;
}
// 1.创建ReactRootView
ReactRootView rootView =
new
ReactRootView(activity);
rootView.startReactApplication(
((ReactApplication) activity.getApplication()).getReactNativeHost().getReactInstanceManager(),
componentName,
null
);
// 2.添加到缓存
CACHE.put(componentName, rootView);
}
/**
* 获取ReactRootView
* @param componentName
* @return
*/
public static ReactRootView getReactRootView(String componentName) {
return
CACHE.get(componentName);
}
/**
* 从当前界面移除 ReactRootView
* @param component
*/
public static void deatchView(String component) {
try
{
ReactRootView rootView = getReactRootView(component);
ViewGroup parent = (ViewGroup) rootView.getParent();
if
(parent !=
null
) {
parent.removeView(rootView);
}
}
catch
(Throwable e) {
Log.e(
"ReactNativePreLoader"
,e.getMessage());
}
}
上述代码很简单,包含了三个方法:
(1)preLoad
负责创建ReactRootView,并添加到缓存。
(2)getReactRootView
获取创建的RootView
(3)deatchView
将添加的RootView从布局根容器中移除,在 ReactActivity 销毁后,我们需要把 view 从 parent 上卸载下来,避免出现重复添加View的异常。
从源码分析部分我们知道,集成React Native界面时,只需要继承ReactActivity,并实现getMainComponentName方法即可。加载创建视图的流程系统都在ReactActivity帮我们完成。现在因为自定义了ReactRootView的加载方式,要使用预加载方式,就不能直接继承ReactActivity。所以接下来需要我们自定义ReactActivity。
从源码中我们已经发现,ReactActivity的处理都交给了ReactActivityDelegate。所以我们可以自定义一个新的ReactActivityDelegate,只需要修改onCreate创建部分,其他照搬源码即可。
public class PreLoadReactDelegate {
private final Activity mActivity;
private ReactRootView mReactRootView;
private Callback mPermissionsCallback;
private final String mMainComponentName;
private PermissionListener mPermissionListener;
private final int REQUEST_OVERLAY_PERMISSION_CODE = 1111;
private DoubleTapReloadRecognizer mDoubleTapReloadRecognizer;
public PreLoadReactDelegate(Activity activity, @Nullable String mainComponentName) {
this
.mActivity = activity;
this
.mMainComponentName = mainComponentName;
}
public void onCreate() {
boolean needsOverlayPermission =
false
;
if
(getReactNativeHost().getUseDeveloperSupport() && Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
// Get permission to show redbox in dev builds.
if
(!Settings.canDrawOverlays(mActivity)) {
needsOverlayPermission =
true
;
Intent serviceIntent =
new
Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION, Uri.parse(
"package:"
+ mActivity.getPackageName()));
mActivity.startActivityForResult(serviceIntent, REQUEST_OVERLAY_PERMISSION_CODE);
}
}
if
(mMainComponentName !=
null
&& !needsOverlayPermission) {
// 1.从缓存中获取RootView
mReactRootView = ReactNativePreLoader.getReactRootView(mMainComponentName);
if
(mReactRootView ==
null
) {
// 2.缓存中不存在RootView,直接创建
mReactRootView =
new
ReactRootView(mActivity);
mReactRootView.startReactApplication(
getReactInstanceManager(),
mMainComponentName,
null
);
}
// 3.将RootView设置到Activity布局
mActivity.setContentView(mReactRootView);
}
mDoubleTapReloadRecognizer =
new
DoubleTapReloadRecognizer();
}
public void onResume() {
if
(getReactNativeHost().hasInstance()) {
getReactInstanceManager().onHostResume(mActivity, (DefaultHardwareBackBtnHandler)mActivity);
}
if
(mPermissionsCallback !=
null
) {
mPermissionsCallback.invoke();
mPermissionsCallback =
null
;
}
}
public void onPause() {
if
(getReactNativeHost().hasInstance()) {
getReactInstanceManager().onHostPause(mActivity);
}
}
public void onDestroy() {
if
(mReactRootView !=
null
) {
mReactRootView.unmountReactApplication();
mReactRootView =
null
;
}
if
(getReactNativeHost().hasInstance()) {
getReactInstanceManager().onHostDestroy(mActivity);
}
// 清除View
ReactNativePreLoader.deatchView(mMainComponentName);
}
public boolean onNewIntent(Intent intent) {
if
(getReactNativeHost().hasInstance()) {
getReactInstanceManager().onNewIntent(intent);
return
true
;
}
return
false
;
}
public void onActivityResult(int requestCode, int resultCode, Intent data) {
if
(getReactNativeHost().hasInstance()) {
getReactInstanceManager().onActivityResult(mActivity, requestCode, resultCode, data);
}
else
{
// Did we request overlay permissions?
if
(requestCode == REQUEST_OVERLAY_PERMISSION_CODE && Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
if
(Settings.canDrawOverlays(mActivity)) {
if
(mMainComponentName !=
null
) {
if
(mReactRootView !=
null
) {
throw
new
IllegalStateException(
"Cannot loadApp while app is already running."
);
}
mReactRootView =
new
ReactRootView(mActivity);
mReactRootView.startReactApplication(
getReactInstanceManager(),
mMainComponentName,
null
);
mActivity.setContentView(mReactRootView);
}
}
}
}
}
public boolean onBackPressed() {
if
(getReactNativeHost().hasInstance()) {
getReactInstanceManager().onBackPressed();
return
true
;
}
return
false
;
}
public boolean onRNKeyUp(int keyCode) {
if
(getReactNativeHost().hasInstance() && getReactNativeHost().getUseDeveloperSupport()) {
if
(keyCode == KeyEvent.KEYCODE_MENU) {
getReactInstanceManager().showDevOptionsDialog();
return
true
;
}
boolean didDoubleTapR = Assertions.assertNotNull(mDoubleTapReloadRecognizer)
.didDoubleTapR(keyCode, mActivity.getCurrentFocus());
if
(didDoubleTapR) {
getReactInstanceManager().getDevSupportManager().handleReloadJS();
return
true
;
}
}
return
false
;
}
public void requestPermissions(String[] permissions, int requestCode, PermissionListener listener) {
mPermissionListener = listener;
mActivity.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
;
}
}
};
}
/**
* 获取 Application中 ReactNativeHost
* @return
*/
private ReactNativeHost getReactNativeHost() {
return
MainApplication.getInstance().getReactNativeHost();
}
/**
* 获取 ReactInstanceManager
* @return
*/
private ReactInstanceManager getReactInstanceManager() {
return
getReactNativeHost().getReactInstanceManager();
}
}
代码很长,重点在onCreate方法:
if
(mMainComponentName !=
null
&& !needsOverlayPermission) {
// 1.从缓存中获取RootView
mReactRootView = ReactNativePreLoader.getReactRootView(mMainComponentName);
if
(mReactRootView ==
null
) {
// 2.缓存中不存在RootView,直接创建
mReactRootView =
new
ReactRootView(mActivity);
mReactRootView.startReactApplication(
getReactInstanceManager(),
mMainComponentName,
null
);
}
// 3.将RootView设置到Activity布局
mActivity.setContentView(mReactRootView);
(1)首先从缓存中取ReactRootView
(2)缓存中不存在ReactRootView,直接创建。此时和系统帮我们创建ReactRootView没有区别
(3)将ReactRootView设置到Activity布局
很明显,我们让加载流程先经过缓存,如果缓存中已经存在了RootView,那么就可以直接设置到Activity布局,如果缓存中不存在,再去执行创建过程。
ReactNativePreLoader.preLoad(
this
,
"HotRN"
);
我们在启动React Native前一个界面,执行preLoad方法优先加载出ReactRootView,此时就完成了视图预加载,让React Native界面达到秒显的效果。
四、效果对比
优化前: 优化后:
Ok,到此想必大家都想撸起袖子体验一下了,那就开始吧~~ 源码已分享到Github,别忘了给颗star哦~
项目源码:https://github.com/songxiaoliang/ReactNativeApp
- 基于最新版本React Native实现JsBundle预加载,界面秒开优化
- React Native基于最新版本实现JsBundle预加载,解决白屏等待,界面秒开优化
- React Native基于最新版本实现JsBundle预加载,解决白屏等待,界面秒开优化
- React Native 之 main.jsbundle生成方法
- React Native 0.31 Bundle 预加载优化
- React Native 0.31 Bundle 预加载优化
- React-Native网络请求加载界面
- React-Native 实现QQ登录界面
- React Native实现二维码管理界面
- 最新react-native视频教程
- React Native 之通过ip地址和main.jsbundle来启动服务器
- React Native ListView中图片加载优化处理(三)
- react-native 版本更新
- 更新react-native版本
- react-native版本升级
- react-native版本升级
- react-native加载图片
- [React Native]升级React Native版本
- DHTML技术演示---动态设置表格行间隔显示、表格排序、鼠标悬停样式改变
- 复习
- 线程池的好处
- Android学习六 Activity
- 水仙花数2074
- 基于最新版本React Native实现JsBundle预加载,界面秒开优化
- JAVA中定时器的使用
- 日志系统ELK使用详解(一)--如何使用
- 优化基于FPGA的深度卷积神经网络的加速器设计
- php排序算法-冒泡排序和快速排序
- 1.安卓全貌
- ubuntu16.04 下 安装rjava、Rwordseg、wordcloud安装
- G
- memcached在大负载高并发网站上的应用(一)