ReactNative ReactContext#getCurrentActivity解析

ReactNative与原生混合开发时,原生经常需要为ReactNative提供组件。提供组件时getCurrentActivity使用不当容易造成NullPointerException。

以下是一个简单的示例分析:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// 随便一个自定义view,继承自LinearLayout
class CustomView extends LinearLayout{

public CustomView(Context context) {
super(context);
}
}

// 提供给Rn的ViewManager
public class CustomViewManager extends SimpleViewManager<CustomView>{

@Override
public String getName() {
return "CustomView";
}

@Override
protected CustomView createViewInstance(ThemedReactContext reactContext) {
CustomView customView = new CustomView(reactContext.getCurrentActivity());
return customView;
}
}

测试发现会有偶现的Crash。
在LinearLayout中的context.obtainStyledAttributes()时出现NullPointerException。

1
2
3
4
5
6
7
public LinearLayout(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);

final TypedArray a = context.obtainStyledAttributes(
attrs, com.android.internal.R.styleable.LinearLayout, defStyleAttr, defStyleRes);
...
}

这个Context来自于reactContext.getCurrentActivity(),也就是CurrentActivity为空。

为什么getCurrentActivity会为空

查看ReactContext类

1
2
3
4
5
6
7
8
private @Nullable WeakReference<Activity> mCurrentActivity;
public @Nullable Activity getCurrentActivity() {
if (mCurrentActivity == null) {
return null;
}
return mCurrentActivity.get();
}
...

可以看到getCurrentActivity()从mCurrentActivity软引用获取Activity,如果软引用为空则为空。

那么mCurrentActivity什么时机赋值,继续看

1
2
3
4
5
6
7
8
9
10
11
public void onHostResume(@Nullable Activity activity) {
...
mCurrentActivity = new WeakReference(activity);
...
}

public void onNewIntent(@Nullable Activity activity, Intent intent) {
...
mCurrentActivity = new WeakReference(activity);
...
}

由此可见在Activity的OnCreate()生命周期mCurrentActivity可能为空。

获取Context和Activity的正确姿势

现在的问题就变成了自定义View需要Context或者Activity,而创建时mCurrentActivity可能为空。

再看创建ViewManager的过程,createViewInstance方法的参数ThemedReactContext就是一个Context。所以创建自定义View可以直接使用:

1
2
3
4
5
6
7
8
public class CustomViewManager extends SimpleViewManager<CustomView>{

@Override
protected CustomView createViewInstance(ThemedReactContext reactContext) {
CustomView customView = new CustomView(reactContext
return customView;
}
}

自定义组件如何获取Context?

直接调用View的getContext()方法即可,获取的Context就是ThemedReactContext。

自定义组件如何获取Activity?

先通过View的getContext()获取ThemedReactContext,然后获取Activity,工具方法如下:

1
2
3
4
5
6
7
8
9
10
11
12
@javax.annotation.Nullable
public static Activity getActivity(Context context) {
if (context instanceof Activity) {
return (Activity) context;
}

if (context instanceof ReactContext) {
ReactContext reactContext = ((ReactContext) context);
return reactContext.getCurrentActivity();
}
return null;
}

为什么设计getCurrentActivity()

1、ReactNative的package/module/viewmanager希望与Activity解耦,能在不同的Activity情况下使用。
2、getCurrentActivity()的返回值会随当前Activity变化而变化,因此不希望外部调用持有getCurrentActivity()的返回值,一旦持有就可能造成内存泄漏。

参考

ReactNative issues/8661
ReactNative issues/9310
ReactNative commit/96e412