(点击
上方公众号
,可快速关注)
来源:伯乐在线专栏作者 - markzhai
链接:http://android.jobbole.com/84313/
点击 → 了解如何加入专栏作者
使用 React Native 开发混合应用的过程中,我们在打完 bundle 进 release 包后,会发现第一次进入页面(React 的 Activity)会有一个短暂的白屏过程(在真机上近
1秒
,在模拟器上比较快,在
200毫秒
左右),而且在完全退出后再进入,仍然会有这个白屏。
仔细查看加载过程(其实猜猜都能知道)后可以发现,这个过程就是在加载我们的 js bundle,通常即便是一个小的 RN 应用(混合应用中的子业务),也会动辄到 1MB 的大小,除非是完整的 RN 应用,可以把这个当做是启动速度,否则这样的加载速度都是对用户体验的很大伤害。
于是我们决定进行 Bundle 预加载的优化。
项目源码上传在:markzhai/react-native-preloader,稍后会上传到 maven,版本号会和 rn 保持一致。
https://github.com/markzhai/react-native-preloader
耗时操作
见 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
();
}
打点后可以发现耗时的其实是
-
createRootView();
-
startReactApplication();
这两个操作,所以考虑只需要提前创建 ReactRootView 进行 render,之后直接挂载该 view 上去即可。
预加载
创建预加载类 ReactPreLoader:
/**
* React Native Bundle Pre-loader.
*
* @author markzhai on 16/8/20
* @version 1.3.0
*/
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。
值得注意的是 onDestroy,在 ReactActivity 销毁后,我们需要把 view 从 parent 上卸载下来。
使用预加载的 view
使用预加载的 View,就需要侵入 activity 的创建过程,我们无法再使用 RN 库提供的 ReactActivity,只能建立自己的,以下列出修改的方法,其他方法照抄 ReactActivity:
/**
* Base Activity for React Native applications.
*
* @author markzhai on 16/7/28
* @version 1.3.0
*/
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
(),