react-native 优化

1、优化Component

很多人在定义一个class时都会去继承Component,这并没有错,但是如果一个子组建也继承了Component,那么当父组建render时也会导致子组建的render,怎么解决呢?其实很简单只需要我们的子组建去继承PureComponent即可。个人建议自定义的class都可以去继承PureComponent从而避免不必要的render。
2、优化方法的创建

内部方法的定义应该使用 onPress=()=>{}. 调用直接this.onPress即可,避免创建多个fun
3、善于使用shouldComponentUpdate

通过shouldComponentUpdate的返回结果我们可以去控制什么时候应该render,什么情况应该render。
4、listview代替scrollview

使用listview我们可以设置首次render时要渲染的ui数量,这样一定程度上优化了首次进入页面时所需要的渲染时间(renderHeader/pagesize)
5、使用InteractionManager

InteractionManager.runAfterInteractions(…)的官方文档说的很清楚,通过他可以处理一些耗时操作,所以我个人建议把网络请求放在次方法中去处理,这样很大程度上可以解决首次进入时的卡顿。
6、使用Animated去处理一些简单的动画

我想应该会有很多小伙伴遇到这样的问题,切换不同的state改变某个view的height/width,是不是很多小伙伴都会用state去控制height/width。并不是说这样是完全不对,只是这样耗费性能,因为你的state的改变会触发render,而对于一个过程来说,这样的render次数是我们不愿意看到的,那该怎么解决呢?其实大家可以使用Animated去代替state,Animated封装了一系列优雅的处理函数,完全可以实现你想要的效果,使用方法官方有介绍。

7、不要在render方法中去做一些层级很高的判断,去大操作的改变真实的DOM,这样非常浪费系统资源,尽量仅做一些数据呈现上的判断,减少对真实DOM结构无谓的增删。

8、对于页面中结构比较固定的控件,尽量都设置上Key值,同样可以减少对DOM的操作。

9、不要在render中做数据的处理,这样降低了系统创建Virtual DOM的效率,同时对于程序结构而言也会变的不清晰,我们可以根据需求把数据的处理放在 componentWillUpdate、componentDidMount、componentWillMount等函数中,这样提高了页面刷新效率也提高了开发效率。

7、启动优化
准备工具:
(1)adb shell

1
am start -W -n com.awesomeproject/.MainActivity 应用启动时间

(2)DDMS 通过trace Method 分析
(3)react-native 打包:

1
react-native bundle --entry-file index.android.js --bundle-output ./android/app/src/main/assets/main.bundle --platform android --assets-dest --dev false

开始分析:
(1)不同版本启动比较:0.39.2 vs 0.45.1(bundle包放在assert文件夹,大小:652kb)
0.45.1:首次安装启动:平均301ms 启动:平均201ms (纯RN应用)
0.39.2:首次安装启动:平均317ms 启动:平均204ms (纯RN应用)
不带有RN的首次安装启动:平均283ms 启动:平均185ms
版本升级对启动时间优化作用偏小

启动测试过程发现白屏的问题还是存在,react_native版本升级最高版本没有对白屏做优化

居于此做以下几方面优化:
白屏处理以及界面切换动画:

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
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
<!-- Base application theme. -->
<style name="AppTheme" parent="Theme.AppCompat.Light.NoActionBar">
<!-- Customize your theme here. -->
<item name="android:windowAnimationStyle">@style/AnimationActivity</item>
<item name="colorPrimary">@color/primary</item>
<item name="colorPrimaryDark">@color/primary_dark</item>
</style>


<!-- RN activity theme. -->
<style name="RNAppTheme" parent="Theme.AppCompat.Light.NoActionBar">
<!-- Customize your theme here. -->
<item name="android:windowBackground">@color/transparent</item>
<item name="android:windowIsTranslucent">true</item>
<item name="android:windowAnimationStyle">@style/AnimationActivity</item>
<item name="colorPrimary">@color/primary</item>
<item name="colorPrimaryDark">@color/primary_dark</item>
</style>


<style name="AnimationActivity" parent="@android:style/Animation.Translucent">
<item name="android:activityOpenEnterAnimation">@anim/push_left_in</item>
<item name="android:activityOpenExitAnimation">@anim/push_left_out</item>
<item name="android:activityCloseEnterAnimation">@anim/push_right_in</item>
<item name="android:activityCloseExitAnimation">@anim/push_right_out</item>
</style>



//push_right_in.xml
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
<translate
android:fromXDelta="100%p"
android:toXDelta="0"
android:duration="300"/>

<alpha
android:fromAlpha="0.8"
android:toAlpha="1.0"
android:duration="300" />

</set>


//push_right_out.xml
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
<translate
android:fromXDelta="0"
android:toXDelta="100%p"
android:duration="300"/>

<alpha
android:fromAlpha="1.0"
android:toAlpha="0.8"
android:duration="300" />

</set>

//push_left_out.xml
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
<translate
android:fromXDelta="-100%p"
android:toXDelta="0"
android:duration="300"/>

<alpha
android:fromAlpha="0.8"
android:toAlpha="1.0"
android:duration="300" />

</set>


//push_left_in.xml
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
<translate
android:fromXDelta="100%p"
android:toXDelta="0%"
android:duration="300"/>

<alpha
android:fromAlpha="0.0"
android:toAlpha="1.0"
android:duration="300" />

</set>

预加载:在APP启动时调用init方法预先启动加载bundle引擎

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
public class RNCacheInstanceManager {
private static ReactInstanceManager mManager = null;

//init
public static void init(Context context) {
mManager = createReactInstanceManager();
ReactRootView mRootView = new ReactRootView(context);
mRootView.startReactApplication(mManager, "AwesomeProject", null);
}

public static ReactInstanceManager getReactInstanceManager() {
return mManager;
}

private static ReactInstanceManager createReactInstanceManager() {

return ReactInstanceManager.builder()
.setApplication(MainApplication.getInstance())
.setBundleAssetName("main.bundle")
.setJSMainModuleName("index.android")
.addPackage(new MainReactPackage())
.setUseDeveloperSupport(true) //开发者支持,开发的时候要设置为true,不然无法使用开发者菜单
.setInitialLifecycleState(LifecycleState.RESUMED)
.build();
}
}

重写ReactRootView

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
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
public class RNReactRootView extends FrameLayout {

private ReactRootView mReactRootView;
private ViewGroup mLoadingView;

public RNReactRootView(Context context) {
super(context);
init();
}

public RNReactRootView(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}

public RNReactRootView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
init();
}

private void init() {
setVisibility(View.INVISIBLE);
//setBackgroundResource(R.color.white);
if (mLoadingView == null) {
mLoadingView = createDefaultLoadingView();
addView(mLoadingView, 0);
}
if (mReactRootView == null) {
mReactRootView = createReactRootView();
addView(mReactRootView, 1);
mReactRootView.getViewTreeObserver().addOnWindowFocusChangeListener(new ViewTreeObserver.OnWindowFocusChangeListener() {
@Override
public void onWindowFocusChanged(boolean hasFocus) {
// AnimatorSet set = (AnimatorSet) AnimatorInflater.loadAnimator(getContext(), R.animator.rn_push_left_in);
// set.setTarget(mReactRootView);
// set.start();
if(hasFocus) {
mReactRootView.postDelayed(new Runnable() {
@Override
public void run() {
setVisibility(View.VISIBLE);
Animation aa = AnimationUtils.loadAnimation(getContext(), R.anim.rn_push_left_in);
mReactRootView.startAnimation(aa);
}
}, 150);
}
Log.i("linzhenhua", "onWindowFocusChanged:" + hasFocus + " " + System.currentTimeMillis());
}
});
}
}

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
Log.i("linzhenhua", "onMeasure:" + " " + System.currentTimeMillis());
}

@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
}

private ReactRootView createReactRootView() {
ReactRootView reactRootView = new ReactRootView(getContext());
LayoutParams loadingViewLp = new LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);
reactRootView.setLayoutParams(loadingViewLp);
return reactRootView;
}

private ViewGroup createDefaultLoadingView() {
ViewGroup loadingView = new RelativeLayout(getContext());
TextView mTextView = new TextView(getContext());
mTextView.setText("加载中...");
mTextView.setGravity(Gravity.CENTER);
RelativeLayout.LayoutParams lp = new RelativeLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
lp.addRule(RelativeLayout.CENTER_IN_PARENT);
mTextView.setLayoutParams(lp);
loadingView.addView(mTextView);
LayoutParams loadingViewLp = new LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);
loadingView.setLayoutParams(loadingViewLp);
return loadingView;
}

public void addLoadView(ViewGroup loadingView) {
destoryLoadingView();
mLoadingView = loadingView;
FrameLayout.LayoutParams lp = new LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);
removeViewAt(0);
addView(mLoadingView, 0, lp);
}

private void destoryLoadingView() {
if (mLoadingView != null) {
mLoadingView.removeAllViews();
removeViewAt(0);
mLoadingView = null;
}
}

public void startReactApplication(ReactInstanceManager reactInstanceManager,
String moduleName,
@Nullable Bundle initialProperties)
{

if (mReactRootView != null) {
mReactRootView.startReactApplication(reactInstanceManager, moduleName, initialProperties);
}
}

public void unmountReactApplication() {
if (mReactRootView != null) {
mReactRootView.unmountReactApplication();
}
}
}

重写ReactActivity

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
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
public class RNReactActivity extends Activity implements DefaultHardwareBackBtnHandler, PermissionAwareActivity {

private final RNReactActivityDelegate mDelegate;

protected RNReactActivity() {
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 RNReactActivityDelegate createReactActivityDelegate() {
return new RNReactActivityDelegate(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();
}

@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
mDelegate.onActivityResult(requestCode, resultCode, data);
}

@Override
public boolean onKeyUp(int keyCode, KeyEvent event) {
return mDelegate.onKeyUp(keyCode, event) || super.onKeyUp(keyCode, event);
}

@Override
public void onBackPressed() {
if (!mDelegate.onBackPressed()) {
super.onBackPressed();
}
}

@Override
public void invokeDefaultOnBackPressed() {
super.onBackPressed();
}

@Override
public void onNewIntent(Intent intent) {
if (!mDelegate.onNewIntent(intent)) {
super.onNewIntent(intent);
}
}

@Override
public void requestPermissions(
String[] permissions,
int requestCode,
PermissionListener listener)
{

mDelegate.requestPermissions(permissions, requestCode, listener);
}

@Override
public void onRequestPermissionsResult(
int requestCode,
String[] permissions,
int[] grantResults)
{

mDelegate.onRequestPermissionsResult(requestCode, permissions, grantResults);
}

protected final ReactNativeHost getReactNativeHost() {
return mDelegate.getReactNativeHost();
}

protected final ReactInstanceManager getReactInstanceManager() {
return mDelegate.getReactInstanceManager();
}

protected final void loadApp(String appKey) {
mDelegate.loadApp(appKey);
}
}

重写ReactActivityDelegate

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
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
public class RNReactActivityDelegate {


private final int REQUEST_OVERLAY_PERMISSION_CODE = 1111;
private static final String REDBOX_PERMISSION_GRANTED_MESSAGE =
"Overlay permissions have been granted.";
private static final String REDBOX_PERMISSION_MESSAGE =
"Overlay permissions needs to be granted in order for react native apps to run in dev mode";

private final @Nullable
Activity mActivity;
private final @Nullable
FragmentActivity mFragmentActivity;
private final @Nullable String mMainComponentName;

private @Nullable
RNReactRootView mReactRootView;
private @Nullable
DoubleTapReloadRecognizer mDoubleTapReloadRecognizer;
private @Nullable
PermissionListener mPermissionListener;
private @Nullable
Callback mPermissionsCallback;

public RNReactActivityDelegate(Activity activity, @Nullable String mainComponentName) {
mActivity = activity;
mMainComponentName = mainComponentName;
mFragmentActivity = null;
}

public RNReactActivityDelegate(
FragmentActivity fragmentActivity,
@Nullable String mainComponentName)
{

mFragmentActivity = fragmentActivity;
mMainComponentName = mainComponentName;
mActivity = null;
}

protected @Nullable
Bundle getLaunchOptions() {
return null;
}

protected RNReactRootView createRootView() {
return new RNReactRootView(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) {
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();
}

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 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);
} else {
// Did we request overlay permissions?
if (requestCode == REQUEST_OVERLAY_PERMISSION_CODE && Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
if (Settings.canDrawOverlays(getContext())) {
if (mMainComponentName != null) {
loadApp(mMainComponentName);
}
Toast.makeText(getContext(), REDBOX_PERMISSION_GRANTED_MESSAGE, Toast.LENGTH_LONG).show();
}
}
}
}

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 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());
}
}

注意RNActivity状态栏的颜色还有待解决,目前默认是黑色

http://reactnative.cn/post/754 ReactNative安卓首屏白屏优化
http://web.jobbole.com/85451/ 探索react native首屏渲染最佳实践
https://sanwen8.cn/p/129FTGW.html 途牛原创 | React-Native 的优化进阶之旅
http://www.open-open.com/lib/view/open1479108118077.html 携程是如何做 React Native 优化的
https://yq.aliyun.com/articles/3208 React Native JS Module 加载性能优化

文章目录
,