React Native in Android的项目实战03--基于ReactRootView封装RN Fragment

ReactNative提供了ReactRootView,提供了集成RN View的途径。那么我们来看一下,如何通过ReactRootView,来封装项目中可以使用的Fragment。

深入ReactRootView源码

首先来看下

1
2
3
4
public class ReactRootView extends SizeMonitoringFrameLayout
implements RootView, MeasureSpecProvider {

}

SizeMonitoringFrameLayout类就是FrameLayout的再封装,对外提供了一个onSizeChange的监听方法.

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 SizeMonitoringFrameLayout extends FrameLayout {

public interface OnSizeChangedListener {
void onSizeChanged(int width, int height, int oldWidth, int oldHeight);
}

private @Nullable OnSizeChangedListener mOnSizeChangedListener;

//...省略构造函数

public void setOnSizeChangedListener(OnSizeChangedListener onSizeChangedListener) {
mOnSizeChangedListener = onSizeChangedListener;
}

@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);

if (mOnSizeChangedListener != null) {
mOnSizeChangedListener.onSizeChanged(w, h, oldw, oldh);
}
}
}

再回来看ReactRootView内部,提供了startReactApplication()方法, 需要我们传入ReactInstanceManager和模块名等参数。

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
public void startReactApplication(ReactInstanceManager reactInstanceManager, String moduleName) {
startReactApplication(reactInstanceManager, moduleName, null);
}

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

public void startReactApplication(ReactInstanceManager reactInstanceManager, String moduleName,
@Nullable Bundle initialProperties, @Nullable String initialUITemplate) {
Systrace.beginSection(TRACE_TAG_REACT_JAVA_BRIDGE, "startReactApplication");
try {
UiThreadUtil.assertOnUiThread();
Assertions.assertCondition(
mReactInstanceManager == null,
"This root view has already been attached to a catalyst instance manager");

mReactInstanceManager = reactInstanceManager;
mJSModuleName = moduleName;
mAppProperties = initialProperties;
mInitialUITemplate = initialUITemplate;

if (!mReactInstanceManager.hasStartedCreatingInitialContext()) {
mReactInstanceManager.createReactContextInBackground();
}
attachToReactInstanceManager();
} finally {
Systrace.endSection(TRACE_TAG_REACT_JAVA_BRIDGE);
}
}

startReactApplication内部主要是做了两件事,一件是在后台去创建React
Context,另一个是attachToReactInstanceManager(),那么另一个是attachToReactInstanceManager()做了什么呢?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
private void attachToReactInstanceManager() {
Systrace.beginSection(TRACE_TAG_REACT_JAVA_BRIDGE, "attachToReactInstanceManager");
try {
if (mIsAttachedToInstance) {
return;
}

mIsAttachedToInstance = true;
Assertions.assertNotNull(mReactInstanceManager).attachRootView(this);
getViewTreeObserver().addOnGlobalLayoutListener(getCustomGlobalLayoutListener());
} finally {
Systrace.endSection(TRACE_TAG_REACT_JAVA_BRIDGE);
}
}

ReactInstanceManager.attachRootView(ReactRootView)又做了什么呢?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
private final Set<ReactRootView> mAttachedRootViews = Collections.synchronizedSet(
new HashSet<ReactRootView>());


@ThreadConfined(UI)
public void attachRootView(ReactRootView rootView) {
UiThreadUtil.assertOnUiThread();
mAttachedRootViews.add(rootView);

// Reset view content as it's going to be populated by the application content from JS.
rootView.removeAllViews();
rootView.setId(View.NO_ID);

// If react context is being created in the background, JS application will be started
// automatically when creation completes, as root view is part of the attached root view list.
ReactContext currentContext = getCurrentReactContext();
if (mCreateReactContextThread == null && currentContext != null) {
attachRootViewToInstance(rootView);
}
}

可以看到ReactInstanceManager内部维护了一个Set,所有attach到ReactInstanceManager的ReactRootView都会被记录下来。

1
2
3
4
5
6
7
8
9
10
11
12
13
private void attachRootViewToInstance(final ReactRootView rootView) {
//...省略无关紧要的代码
rootView.runApplication();
//...
UiThreadUtil.runOnUiThread(new Runnable() {
@Override
public void run() {
//...
rootView.onAttachedToReactInstance();
}
});
//...
}

ReactRootView的runApplication()方法我们就不再深入了,可以看一下它的注释说明

Calls into JS to start the React application. Can be called multiple times with the same rootTag, which will re-render the application from the root.

可以看到,目的就是调到JS那里去启动React application。

而onAttachedToReactInstance()方法如下:

1
2
3
4
5
6
7
8
9
10
public void onAttachedToReactInstance() {
// Create the touch dispatcher here instead of having it always available, to make sure
// that all touch events are only passed to JS after React/JS side is ready to consume
// them. Otherwise, these events might break the states expected by JS.
// Note that this callback was invoked from within the UI thread.
mJSTouchDispatcher = new JSTouchDispatcher(this);
if (mRootViewEventListener != null) {
mRootViewEventListener.onAttachedToReactInstance(this);
}
}

可以看到,目的就是将原生的View绑定上Js的Touch事件分发。至此,我们重新梳理下ReactRootView的逻辑,可以明确以下几点:

  1. ReactRootView继承自Android上FrameLayout,对外提供onSizeChange的接口。
  2. ReactRootView提供了startReactApplication方法,用于在后台去创建React
    Context以及绑定ReactRootView到ReactApplication上。
  3. 绑定ReactRootView到ReactApplication上这一步做了两件事,一个是启动React application,一个是启用JSTouchDispatcher,用于View的JS事件分发。

封装ReactRootView

为了封装出我们需要的ReactNatvieFragment,我们需要对ReactRootView本身做一些封装,以实现怎么几个目的:

  1. 外部不需要处理ReactInstanceManager的逻辑
  2. 通过Bundle或者其他参数的形式,传入url,通知JS具体加载哪个页面

假设我们封装的一层View叫做RNBaseRootView,那么RNBaseRootView的构造函数可以是:

1
2
3
4
5
6
public RNBaseRootView(Context context, String module, Bundle bundle) {
super(context);
this.mModule = module;
this.mBundle = bundle;
initial();
}

为了抽离RNBaseRootView和ReactInstanceManager的逻辑,我们把ReactInstanceManager的相关处理逻辑委托给RNApiManager类,例如初始化ReactInstanceManage:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@UiThread
private synchronized void initialReactInstanceManager() {
ReactInstanceManagerBuilder builder = ReactInstanceManager.builder()
.setApplication(mRnDependency.getApplication())
.addPackage(new MainReactPackage())
.addPackage(new CommonReactPackage())
.addPackage(new CustomComponentPackage())
.setJSBundleFile(mRnDependency.isRNDebug() ? "" : mRnDependency.bundlePath())
.setUseDeveloperSupport(mRnDependency.isRNDebug())
.setInitialLifecycleState(LifecycleState.BEFORE_RESUME);
if (mRnDependency.isRNDebug()) {
builder.setJSMainModulePath("index");
}
if (mRnDependency.isRNDebug()) {
mBundlePath = mReactInstanceManager.getDevSupportManager().getJSBundleURLForRemoteDebugging();
} else {
mBundlePath = mRnDependency.bundlePath();
}
mReactInstanceManager.createReactContextInBackground();
}

类似的,释放React Natvie的资源也可以放在里面

1
2
3
4
5
6
7
8
9
10
11
@UiThread
public synchronized void destroyReact() {
if (mReactInstanceManager != null) {
mReactInstanceManager.destroy();
mReactInstanceManager = null;
}

if (FrescoModule.hasBeenInitialized()) {
Fresco.getImagePipeline().clearMemoryCaches();
}
}

这样,在NBaseRootView的初始化方法里面,我们可以通过调用RNApiManager类,完成我们的初始化操作。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
private void initial() {
//...
RNApiManager.getInstance().initialReact(new OnInitialCompleteListener() {
@Override
public void onInitialSuccess() {
if (initialed)
return;
mReactInstanceManager = RNApiManager.getInstance().getReactInstanceManager();
initialed = true;
vReactRootView.startReactApplication(mReactInstanceManager, mModule, mBundle);

if (vReactRootView != null) {
sendPageSize(vReactRootView.getMeasuredWidth(), vReactRootView.getMeasuredHeight());
}
}

@Override
public void onInitialFail() {
//...
}
});
}

接下来,ReactNativeFragment的实现就水到渠成了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class ReactNativeFragment extends Fragment {
private RNBaseRootView vRNBaseRootView;
private String mModule;
private Bundle mBundle;

public RNBaseRootView createRootView(Context context, String module, Bundle bundle) {
vRNBaseRootView = new RNBaseRootView(context, module, bundle);
return vRNBaseRootView;
}

@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
if (vRNBaseRootView == null) {
vRNBaseRootView = createRootView(getActivity(), mModule, mBundle);
}
return vRNBaseRootView;
}

//...
}

这样,我们就可以在工程中顺利的使用我们自己封装的ReactNativeFragment,可以很容易的嵌入到Tab中。