今天帮同事解决了一个在Fragment里面使用RN页面的需求,记录一下.
ReactFragment
package com.xxx;
import android.content.Intent;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v4.app.Fragment;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import com.facebook.infer.annotation.Assertions;
import com.facebook.react.ReactApplication;
import com.facebook.react.ReactInstanceManager;
import com.facebook.react.ReactNativeHost;
import com.facebook.react.ReactRootView;
import com.facebook.react.common.LifecycleState;
import com.facebook.react.devsupport.DoubleTapReloadRecognizer;
import com.facebook.react.modules.core.DefaultHardwareBackBtnHandler;
import com.facebook.react.modules.core.PermissionListener;
public class MyFragment extends Fragment {
public static final String ARG_COMPONENT_NAME = "arg_component_name";
public static final String ARG_LAUNCH_OPTIONS = "arg_launch_options";
private static MyFragment newInstance(@NonNull String componentName, Bundle launchOptions) {
MyFragment fragment = new MyFragment();
Bundle args = new Bundle();
args.putString(ARG_COMPONENT_NAME, componentName);
args.putBundle(ARG_LAUNCH_OPTIONS, launchOptions);
fragment.setArguments(args);
return fragment;
}
private String mComponentName;
private Bundle mLaunchOptions;
private ReactRootView mReactRootView;
@Nullable
private DoubleTapReloadRecognizer mDoubleTapReloadRecognizer;
@Nullable
private PermissionListener mPermissionListener;
// region Lifecycle
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (getArguments() != null) {
mComponentName =getArguments().getString(ARG_COMPONENT_NAME);
mLaunchOptions = getArguments().getBundle(ARG_LAUNCH_OPTIONS);
}
if (mComponentName == null) {
throw new IllegalStateException("Cannot loadApp if component name is null");
}
mDoubleTapReloadRecognizer = new DoubleTapReloadRecognizer();
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
mReactRootView = new ReactRootView(getContext());
mReactRootView.startReactApplication(
getReactNativeHost().getReactInstanceManager(),
mComponentName,
mLaunchOptions);
return mReactRootView;
}
@Override
public void onResume() {
super.onResume();
if (getReactNativeHost().hasInstance()) { getReactNativeHost().getReactInstanceManager().onHostResume(getActivity(), (DefaultHardwareBackBtnHandler) getActivity());
}
}
@Override
public void onPause() {
super.onPause();
if (getReactNativeHost().hasInstance()) {
getReactNativeHost().getReactInstanceManager().onHostPause(getActivity());
}
}
@Override
public void onDestroy() {
super.onDestroy();
if (mReactRootView != null) {
mReactRootView.unmountReactApplication();
mReactRootView = null;
}
if (getReactNativeHost().hasInstance()) {
ReactInstanceManager reactInstanceMgr = getReactNativeHost().getReactInstanceManager();
if (reactInstanceMgr.getLifecycleState() != LifecycleState.RESUMED) {
reactInstanceMgr.onHostDestroy(getActivity());
getReactNativeHost().clear();
}
}
}
// endregion
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
if (mPermissionListener != null &&
mPermissionListener.onRequestPermissionsResult(requestCode, permissions, grantResults)) {
mPermissionListener = null;
}
}
// endregion
// region Helpers
/**
* Helper to forward hardware back presses to our React Native Host
*/
public void onBackPressed() {
if (getReactNativeHost().hasInstance()) {
getReactNativeHost().getReactInstanceManager().onBackPressed();
}
}
public void onActivityResult(int requestCode, int resultCode, Intent data) {
if (getReactNativeHost().hasInstance()) {
getReactNativeHost().getReactInstanceManager().onActivityResult(getActivity(), requestCode, resultCode, data);
}
}
public void onNewIntent(Intent intent) {
if (getReactNativeHost().hasInstance()) {
getReactNativeHost().getReactInstanceManager().onNewIntent(intent);
}
}
/**
* Helper to forward onKeyUp commands from our host Activity.
* This allows ReactFragment to handle double tap reloads and dev menus
*
* @param keyCode keyCode
* @param event event
* @return true if we handled onKeyUp
*/
@SuppressWarnings("unused")
public boolean onKeyUp(int keyCode, KeyEvent event) {
boolean handled = false;
if (getReactNativeHost().getUseDeveloperSupport() && getReactNativeHost().hasInstance()) {
if (keyCode == KeyEvent.KEYCODE_MENU) {
getReactNativeHost().getReactInstanceManager().showDevOptionsDialog();
handled = true;
}
boolean didDoubleTapR = Assertions.assertNotNull(mDoubleTapReloadRecognizer).didDoubleTapR(keyCode, getActivity().getCurrentFocus());
if (didDoubleTapR) {
getReactNativeHost().getReactInstanceManager().getDevSupportManager().handleReloadJS();
handled = true;
}
}
return handled;
}
// endregion
/**
* 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 ((MainApplication) getActivity().getApplication()).getReactNativeHost();
}
/**
* Builder class to help instantiate a {@link MyFragment}
*/
public static class Builder {
private final String mComponentName;
private Bundle mLaunchOptions;
/**
* Returns new Builder for creating a {@link MyFragment}
*
* @param componentName The name of your React Native component
*/
public Builder(String componentName) {
mComponentName = componentName;
}
/**
* Set the Launch Options for our React Native instance.
*
* @param launchOptions launchOptions
* @return Builder
*/
public Builder setLaunchOptions(Bundle launchOptions) {
mLaunchOptions = launchOptions;
return this;
}
public MyFragment build() {
return MyFragment.newInstance(mComponentName, mLaunchOptions);
}
}
}
Activity 你自己的任意FragmentActivity(你可能想继承自己的BaseFragmentActivity,而不是去继承ReactActivity or ReactFragmentActivity )
package com.xxx;
import android.os.Bundle;
import android.support.v4.app.FragmentActivity;
import com.facebook.react.modules.core.DefaultHardwareBackBtnHandler;
import com.facebook.react.modules.core.PermissionListener;
public class MainActivity extends FragmentActivity implements DefaultHardwareBackBtnHandler{
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
getSupportFragmentManager().beginTransaction().add(R.id.f1,new MyFragment.Builder("DvaDemo").build()).commit();
}
@Override
public void invokeDefaultOnBackPressed() {
super.onBackPressed();
}
}
activity_main布局
这里只是证明了可行性,没有深度使用,感觉实际使用过程中可能遇到一些问题,比如RN页面有复杂跳转逻辑,原生也有一些页面跳转、切换逻辑,那么系统返回键在某些情况下可能需要特殊处理,又或者RN页面中有requestPermission,那么callback可能也要特殊处理(我们的Activity(or Fragment?)去继承PermissionAwareActivity做一些特殊处理?)。因为没有深度使用,所以不确定会遇到哪些问题,但是所有问题应该都是可以解决的。
参考:
https://stackoverflow.com/questions/35221447/react-native-inside-a-fragment
https://medium.com/@pradeet/react-native-fragments-182d35459ca1