React Native开发经验集React Native实践

RN学习笔记之和本地交互

2017-05-18  本文已影响791人  AnonyPer

学习了RN的基本内容,虽然可以开发个全RN的项目,但基本上没有完全来使用RN构建一个项目的,都要使用到RN和本地的交互。
以Android为例(IOS原理一样),RN和本地的交互主要分为以下几种:

1、本地展示RN的component

2、RN调用本地的代码方法

3、RN传递参数给本地

4、本地传递参数给RN

5、RN展示本地的View

掌握了以上几种,基本上就可以开始开发混合了RN的项目了。本文主要记录一下以上几种方式的相关内容.

本地展示RN component

如果想本地展示RN的component,我们都知道Android本地是以activity或者fragment为载体,加载view来展示的。所以基本上可以确定,RN的Component也会作为一个view依赖于fragment或者activity来展示。
RN中作为展示的view对象叫做ReactRootView,根据其源码可以看出,

public class ReactRootView extends SizeMonitoringFrameLayout implements RootView
public class SizeMonitoringFrameLayout extends FrameLayout

ReactRootView 根本上是一个FrameLayout,更详细的源码不做过多分析。而ReactRootView的用法如下:

ReactRootView mReactRootView = new ReactRootView(context);
mReactRootView.startReactApplication(ReactInstanceManager rm, String componentName, Bundle b);

由此方法可以看出,我们想让ReactRootView生效,需要一个ReactInstanceManagercomponentNameBundle,三个参数分别是

1、ReactInstanceManager 管理JS context的管理器
2、需要加载Component名字
3、需要传递的参数,Bundle类型

而ReactInstanceManager是根据ReactNativeHost对象获取的(其底层也是通过ReactInstanceManager.builder来实现的,我们也可以自己去实现这个,但是一般会用ReactNativeHost,这样便于管理)ReactNativeHost对象是通过new ReactNativeHost(Application application)获取的。所以有上可以得出,我们需要几步来实现本地调用RN的component。

Android中需要做的:

1、在项目的application中实例化ReactNativeHost
2、通过ReactNativeHost获取ReactInstanceManager
3、在需要展示RN界面的地方实例化ReactRootView并加载对应注册的的component

RN中要做的

1、将对应的RN component进行注册,注册的名字和RN中的加载的component对应。
所以我们查阅大部分blog写出的demo都可以看到会创建一个工程,自定义一个application实现ReactApplication接口,里面实现一个public ReactNativeHost getReactNativeHost()的方法,然后在activity中集成ReactActivity,实现protected String getMainComponentName()的方法。或者我们自己去写一个类,提供ReactInstanceManager对象,这样就不用实现ReactActivity方法了,就有了更大的灵活性。
具体如下:

/**
 * RN pacakge 管理类 
 * Created by AnonyPer on 2017/5/8.
 */
public class ReactNativePackageManager {
    private static final ReactNativePackageManager reactNativePackageManager = new ReactNativePackageManager();
    private ReactNativeHost mReactNativeHost;
    /**
     * ReactContext上下文,后续传递参数给RN的时候用到
     * 
     */
    private ReactContext mReactContext;

    /**
     * 单例模式 实例化时实现ReactNativeHost
     */
    private ReactNativePackageManager() {
        //实现ReactNativeHost 并将application传递过来
        mReactNativeHost = new ReactNativeHost(AppApplication.getInstance()) {
            /**判断是否是debug模式,会自动开启摇一摇调试
             * @return
             */
            @Override
            public boolean getUseDeveloperSupport() {
                return BuildConfig.DEBUG;
            }

            /**
             * 注册本地的ReactPackage,这样才能在RN中调用
             * @return
             */
            @Override
            protected List<ReactPackage> getPackages() {
                return Arrays.<ReactPackage>asList(
                        new MainReactPackage(), new NativeReactPackage()
                );
            }

            /**获取JS bundle文件的地址,这里会在以后热更新部署的时候用到,程序加载JS的时候都是通过对应的bundle文件来加载的
             * @return
             */
            @Nullable
            @Override
            protected String getJSBundleFile() {
                return null;
            }
        };
        registerReactInstanceEventListener();
    }
    
    public static ReactNativePackageManager getInstance() {
        return reactNativePackageManager;
    }
    public ReactContext getReactContext() {
        return mReactContext;
    }

    /**
     * 提供接口,暴露ReactInstanceManager对象
     * @return
     */
    public ReactInstanceManager getReactInstanceManager() {
        return mReactNativeHost.getReactInstanceManager();
    }


    private void registerReactInstanceEventListener() {
        mReactNativeHost.getReactInstanceManager().addReactInstanceEventListener(mReactInstanceEventListener);

    }

    private void unRegisterReactInstanceEventListener() {
        mReactNativeHost.getReactInstanceManager().removeReactInstanceEventListener(mReactInstanceEventListener);
    }

    /**
     * 注册ReactInstanceEventListener,在ReactContextInitialized后获取ReactContext对象
     */
    private final ReactInstanceManager.ReactInstanceEventListener mReactInstanceEventListener =
            new ReactInstanceManager.ReactInstanceEventListener() {
                @Override
                public void onReactContextInitialized(ReactContext context) {
                    mReactContext = context;
                }
            };


}

Android中调用

mReactRootView = new ReactRootView(context);
        mReactInstanceManager = ReactNativePackageManager.getInstance().getReactInstanceManager();
        mReactRootView.startReactApplication(mReactInstanceManager, getComponentName(), translateModel != null ? translateModel.getBundle() : null);
        mReactRootView.setLayoutParams(new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, 0, 1));
        layout.addView(mReactRootView);

需要注意的是,RN中Component要用到相关的点击事件,需要在依赖的activity的onResume中将当前的activity对象传递给ReactInstanceManager,代码如下:

原方法:onHostResume(Activity activity, DefaultHardwareBackBtnHandler defaultBackButtonImpl) 
ReactNativePackageManager.getInstance().getReactInstanceManager().onHostResume(this.getActivity(), null);

这样在处理当前activity事件的时候才可以有效的将相关事件传递给RN,不然RN接收不到点击长按等事件。
代码如下:

/**
 * RN本地的通用Fragment,集成项目中原本通用BaseFragment,定义了RN需要的方法
 * Created by AnonyPer on 2017/5/4.
 */
public class ReactBaseFragment extends BaseFragment {
    private ReactRootView mReactRootView;
    private ReactInstanceManager mReactInstanceManager;
    private String componentName = "XXXXXXXXXX";

    /**
     * 在fragment基类中定义了一个getReactRootView方法,获取ReactRootView,便于加载到对应的view中
     * @return
     */
    @Override
    public ReactRootView getReactRootView() {
        return mReactRootView;
    }
    
    @Override
    public void setArguments(Bundle bundle) {
        super.setArguments(bundle);
        componentName = bundle.getString(TranslateModel.KEY_COMPONENT);
    }

    @Override
    public void onAttach(Context context) {
        super.onAttach(context);
        //以下的内容可以写在不同的方法中,只要在被调用显示前初始化好即可
        mReactRootView = new ReactRootView(context);
        mReactInstanceManager = ReactNativePackageManager.getInstance().getReactInstanceManager();
        mReactRootView.startReactApplication(mReactInstanceManager, getComponentName(), translateModel != null ? translateModel.getBundle() : null);
    }
    
    @Override
    public void onResume() {
        super.onResume();
        ReactNativePackageManager.getInstance().getReactInstanceManager().onHostResume(this.getActivity(), null);
    }

    @Override
    public void onPause() {
        super.onPause();
        ReactNativePackageManager.getInstance().getReactInstanceManager().onHostPause(this.getActivity());
    }

    /**
     * 获取对应的Component name
     * @return
     */
    protected String getComponentName() {
        return componentName;
    }
    @Override
    public void onDestroy() {
        super.onDestroy();
        ReactNativePackageManager.getInstance().getReactInstanceManager().onHostDestroy(this.getActivity());
    }

    /**
     * 发送数据给RN
     * @param eventName 事件对应的name,RN中会根据该名字找到对应的方法
     * @param params 需要传递的参数
     */
    protected void sendEvent(String eventName,
                             @Nullable WritableMap params) {
        if (ReactNativePackageManager.getInstance().getReactContext() != null) {
            ReactNativePackageManager.getInstance().getReactContext()
                    .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class)
                    .emit(eventName, params);

        }
    }
}

部分BaseFragment代码如下:

public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container,
                             @Nullable Bundle savedInstanceState) {
        ...
        mContentView = getReactRootView();
        if (mContentView != null) {
            mContentView.setLayoutParams(new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, 0, 1));
            //layout是自定义的一个view,用来包容需要显示的view,可以是本地的也可以是RN的
            layout.addView(mContentView);
        }
        ...
    }
//获取ReactRootView方法,默认为null,在用到RN的fragment中自己去重写。
public ReactRootView getReactRootView() {
        return null;
    }

RN中的关键代码如下:

registerComponent('com.XXX.XXX', () => TestComponent);

至此,Android本地已完美显示RN中的Component了。

RN调用本地的代码方法/RN传递参数给本地

RN调用本地的代码和方法以及RN传递参数给本地是一体的,这一部分挺简单,就不详细说了,具体代码如下:

Android本地代码

public class ToastModule extends ReactContextBaseJavaModule {

    private static final String REACT_CLASS = "JFToast";

    public ToastModule(ReactApplicationContext reactContext) {
        super(reactContext);
    }

    @Override
    public String getName() {
        return REACT_CLASS;
    }

    /**
     * 调用本地的方法,并传递一个字符串参数
     * @param tip
     */
    @ReactMethod
    public void showDelayInfoTip(String tip) {
        //用全局toast  因为有一些手机toast是和界面相关的,如果弹toast时界面消失,toast也会消失
        if(!TextUtils.isEmpty(tip)){
            Toast.makeText(AppApplication.getInstance(),tip,Toast.LENGTH_LONG).show();
        }
    }

    /**调用本地方法,并传递一个对象
     * @param readableMap 参数对象,以键值对方式存在
     */
    @ReactMethod
    public void executeCallback(ReadableMap readableMap) {
        //具体代码
        readableMap.getString("XX");
        readableMap.getArray("XX");
        readableMap.getBoolean("XX");
        readableMap.getDouble("XX");
        readableMap.getMap("XX");
    }
    /**
     * RN调用本地执行登录的方法
     *
     * @param promise Promise是ES6中增加的对于异步编程和回调更加友好的API 有resolve和reject两个方法,resolve用来处理正确处理结果的情况,reject用来处理异常的情况
     *                参考博文:https://segmentfault.com/a/1190000004508328
     */
    @ReactMethod
    public void executeLogin(final Promise promise) {
        
        //执行登录操作,并将登录后的信息返回给RN
        //jump to login
        promise.resolve(UserInfoConfig.getInstance().getWritableMap());
    }
    /**
     * RN调用本地判断是否登录的方法,如果已登录 直接返回,如果没有登录 直接跳转登录,然后登陆成功之后再次执行回调给RN
     *
     * @param callback RN传递过来的回调 Callback 的数据类型对应了react-native中的 function
     *                 在JavaScript调用Java之后,处理结果会以Callback的形式回到JavaScript中,在JavaScript中再对相应的结果进行处理。
     *                 参考博文:https://segmentfault.com/a/1190000004508328
     */
    @ReactMethod
    public void onLineExecute(final Callback callback) {
        callback.invoke(null, UserInfoConfig.getInstance().getWritableMap());
    }

}

然后将相关的module添加到ReactPackage中并通知RN

public class NativeReactPackage implements ReactPackage {
    @Override
    public List<Class<? extends JavaScriptModule>> createJSModules() {
        return Collections.emptyList();
    }

    @Override
    public List<ViewManager> createViewManagers(ReactApplicationContext reactContext) {
        List<ViewManager> managers = new ArrayList<>();
        managers.add(new RefreshControlViewManager());
        return managers;
    }

    @Override
    public List<NativeModule> createNativeModules(
            ReactApplicationContext reactContext) {
        List<NativeModule> modules = new ArrayList<>();
        modules.add(new ToastModule(reactContext));
        return modules;
    }
}
@Override
            protected List<ReactPackage> getPackages() {
                return Arrays.<ReactPackage>asList(
                        new MainReactPackage(), new NativeReactPackage()
                );
            }

RN代码

class Home extends Component<any, any>{
    render() {

        return (
            <TouchableHighlight onPress={()=>{
                ToastModule.showDelayInfoTip("测试的内容");
                  }}>
                <Text >Component</Text>
                  </TouchableHighlight>
              
        );
    }
}

本地传递参数给RN

本地传递给RN参数分为两种,其实上面的代码中都包含了,一种是在RN调用本地方法时回调给RN,一种是主动地发送消息给RN,代码如下:

protected void sendEvent(String eventName,
                             @Nullable WritableMap params) {
        if (ReactNativePackageManager.getInstance().getReactContext() != null) {
            ReactNativePackageManager.getInstance().getReactContext()
                    .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class)
                    .emit(eventName, params);

        }
    }

RN展示本地的View

RN调用本地的view的情况是因为有一些效果还是还是使用本地的方法比较方便,所以本地实现RN来调用,本地重要代码如下:

public class RefreshControlViewManager extends ViewGroupManager<PullRefreshView> {

    private static final String REACT_CLASS = "ARefreshView";

    @Override
    public String getName() {
        return REACT_CLASS;
    }

    /**
     * 返回一个本地的view对象
     * @param context
     * @return
     */
    @Override
    public PullRefreshView createViewInstance(ThemedReactContext context) {
        return new PullRefreshView(context);
    }
}```
上面的NativeReactPackage中已经将view告诉RN了:
``` @Override
    public List<ViewManager> createViewManagers(ReactApplicationContext reactContext) {
        List<ViewManager> managers = new ArrayList<>();
        managers.add(new RefreshControlViewManager());
        return managers;
    }```
RN中调用如下:

import { PropTypes } from 'react'
import {
requireNativeComponent,
View,
Platform
} from 'react-native';
var ARefreshView = undefined;
if (Platform.OS == 'android') {
let refresh = {
name: 'ARefreshView',
propTypes: {
...View.propTypes // 包含默认的View的属性
},
};
ARefreshView = requireNativeComponent('ARefreshView', refresh);
}

export default ARefreshView;

在Component中使用的时候如下:

return (<ARefreshView style={{ flex: 1 }}>{list}</ARefreshView>)

到这里,RN和本地交互用到的几种情况都有说完了,热更新部分待后续再议。
上一篇下一篇

猜你喜欢

热点阅读