ReactNative踩坑:ReactContext!=Activity

项目中有一个书架的View提供给ReactNative使用,监听ReactNative相应的生命周期进行界面刷新,代码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
//ViewManager
public class BookShelfManager extends SimpleViewManager<BookShelfView> {
private BookShelfView mBookShelfView;

@Override
protected BookShelfView createViewInstance(ThemedReactContext reactContext) {
mBookShelfView = new BookShelfView(reactContext.getCurrentActivity());
reactContext.addLifecycleEventListener(mBookShelfView);
mBookShelfView.setReactContext(reactContext);
return mBookShelfView;
}

//...
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class BookShelfView extends LinearLayout implements LifecycleEventListener {

public BookShelfView(Context context) {
super(context);
this.mContext = context;
initView();
}

public void setReactContext(ReactContext reactContext){
mReactContext = reactContext;
}

//form com.facebook.react.bridge.LifecycleEventListener
@Override
public void onHostResume() {
refresh();
}

//form com.facebook.react.bridge.LifecycleEventListener
@Override
public void onHostDestroy() {
mReactContext.removeLifecycleEventListener(this);
}

ViewManager将ReactContext传给BookShelfView,并且在ReactContext上注册了LifecycleEvent的监听。希望的行为是界面展示的时候触发onHostResume方法执行refresh刷新界面。

然而测试发现一个情况,打开多个ReactNative页面后返回到书架页面时,不能正常刷新。

调试后发现,其他ReactNative页面关闭时也会回调BookShelfView的onHostDestroy使得注销了监听,所以再也收不到onHostResume,所以界面也无法刷新了。

ReactContext源码中onHostResume方法的参数已经表明一个ReactContext可以对应多个Activity,而onHostDestroy是直接通知所有的listener跟Activity无关。

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
/**
* Should be called by the hosting Fragment in {@link Fragment#onResume}
*/
public void onHostResume(@Nullable Activity activity) {
mLifecycleState = LifecycleState.RESUMED;
mCurrentActivity = new WeakReference(activity);
ReactMarker.logMarker(ReactMarkerConstants.ON_HOST_RESUME_START);
for (LifecycleEventListener listener : mLifecycleEventListeners) {
try {
listener.onHostResume();
} catch (RuntimeException e) {
handleException(e);
}
}
ReactMarker.logMarker(ReactMarkerConstants.ON_HOST_RESUME_END);
}

/**
* Should be called by the hosting Fragment in {@link Fragment#onDestroy}
*/
public void onHostDestroy() {
UiThreadUtil.assertOnUiThread();
mLifecycleState = LifecycleState.BEFORE_CREATE;
for (LifecycleEventListener listener : mLifecycleEventListeners) {
try {
listener.onHostDestroy();
} catch (RuntimeException e) {
handleException(e);
}
}
mCurrentActivity = null;
}

所以在使用LifecyclerEventListener的时候我们需要自己做一层保护,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@Override
public void onHostResume() {
//防止其他Activity resume触发刷新
Activity activity = mReactContext.getCurrentActivity();
if (activity != null && activity == mContext){
refresh();
}
}

@Override
public void onHostDestroy() {
//友情提示:rn的ReactContext传给了不同activity,如果不作处理,
//其他RN Activity onDestroy时候会使得监听注销,页面无法刷新.
Activity activity = mReactContext.getCurrentActivity();
if (activity != null && activity == mContext){
mReactContext.removeLifecycleEventListener(this);
}
}

不同的Activity是否共用ReactContext取决于ReactNative同学的代码,不过Android的代码还是应当做一层保护。