React Native与Android的交互

论坛 期权论坛 脚本     
匿名技术用户   2020-12-27 17:08   349   0

在使用RN进行跨平台开发的过程中,经常会设计到跨平台调用相关的内容,而在于RN进行交互的时候,最核心的就是RN提供的Component和Module。
其中,Component是专门将Native的UI暴露出来供JS调用的,而Native Module则是将Native的模块暴露出来供JS调用的,其用途不一样。在实战开发中,由于RN实现的成本比较大,或者没办法实现,而原生是非常容易实现的,这时候就想到了自定义组件。

Component

例如,下面是一个自定义的View原生代码:

public class MyCustomView extends View {

    private Paint mPaint;

    public MyCustomView(ReactContext context) {
        super(context);
        mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        mPaint.setColor(0xffff0000);
    }


    public void setColor(int color){
        mPaint.setColor(color);
        invalidate();
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        setMeasuredDimension(300, 300);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        canvas.drawRect(0, 0, getWidth(), getHeight(), mPaint);
    }
}

然后,创建一个ViewManager共RN调用,ViewManager需要继承自SimpleViewManager或者ViewGroupManager,如果RN有默认的属性,可以在原生端使用ReactProp注解来给原生设置属性值。

public class MyCustomViewManager extends SimpleViewManager<MyCustomView> {
    protected static final String REACT_CLASS = "MyCustomView";

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

    @Override
    protected MyCustomView createViewInstance(ThemedReactContext reactContext) {
        return new MyCustomView(reactContext); 
    }

    // 设置属性共RN调用,名称需要和RN的属性对应
    @ReactProp(name = "color")
    public void setColor(MyCustomView view, String color) {
        view.setColor(Color.parseColor(color));
    }
}

然后创建一个ReactPackage,并将我们自定义的ViewManager添加到createViewManagers中。

public class CustomReactPackage implements ReactPackage {
    @Override
    public List<NativeModule> createNativeModules(ReactApplicationContext reactContext) {
        return Collections.emptyList();
    }

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

    //自定义的ViewManager都可以加在这里。
    @Override
    public List<ViewManager> createViewManagers(ReactApplicationContext reactContext) {
        return Arrays.<ViewManager>asList(
                new MyCustomViewManager()
        );
    }
}

然后将我们自定义的ReactPackage在Application中注册。

public class MainApplication extends Application implements ReactApplication {

    private final ReactNativeHost mReactNativeHost = new ReactNativeHost(this) {
        @Override
        public boolean getUseDeveloperSupport() {
            return BuildConfig.DEBUG;
        }

        @Override
        protected List<ReactPackage> getPackages() {
            return Arrays.<ReactPackage>asList(
                    new MainReactPackage(), 
                    new CustomReactPackage() 
            );
        }
    };

    @Override
    public ReactNativeHost getReactNativeHost() {
        return mReactNativeHost;
    }

    @Override
    public void onCreate() {
        super.onCreate();
        SoLoader.init(this, /* native exopackage */ false);
    }
}

为了方便其他RN开发人员调用,我们将原生组件封装成RN组件后再导出。

import React, {
  Component,
  PropTypes,
} from 'react';
import {
  requireNativeComponent,
  View,
  UIManager,
} from 'react-native';

const ReactNative = require('ReactNative'); // ReactNative通过import没用

export default class MyCustomView extends Component{
  constructor(props){
    super(props)
  }

  render(){
    // {...this.props} 一定需要设置,不让你永远也看不到
    return(
      <RCTMyCustomView 
        {...this.props} 
      </RCTMyCustomView>);
  }
}

MyCustomView.propTypes = {
  color: PropTypes.string,  // 设置color属性
  ...View.propTypes, // 这里一定需要设置,不然会报错
};

var RCTMyCustomView = requireNativeComponent('MyCustomView', MyCustomView); 

需要注意的是,如果报ReactNative找不到的错误,请修改ReactNative的导入方式为:

import ReactNative from 'react-native';

到此,我们就可以在需要使用的地方使用该组件了,注意给MyCustomView设置大小。

<MyCustomView
  color='#00ff00'
  style={{width:300, height:300}}
/>

这样就完成了JS调用原生组件的功能,但是在实战开发中,经常会设计的原生和js事件相关的传递,数据的传递可以参考我之前的文章:React Native原生模块向JS传递数据的几种方式

在与原生进行事件传递时,如果JS要给Native发送事件,Native需要借助getCommandsMap()和receiveCommand()来处理JS向Native发送事件逻辑。如果Native要给JS传递事件,可以使用getExportedCustomDirectEventTypeConstants()和addEventEmitters()。

 private static final int CHANGE_COLOR = 1;

    /**
     * 可以接收的JS发过来的事件,返回来的数据是一组对应了方法名以及方法对应的一个ID(这个ID需要唯一区分)的Map。
     */
    @Nullable
    @Override
    public Map<String, Integer> getCommandsMap() {
        return MapBuilder.of("changeColor", CHANGE_COLOR);
    }

    /**
     * 接收JS事件以后的处理。JS会通过一些发送发送相应的指令过来,Native会由receiveCommand来处理。事件过来时才会执行。
     */
    @Override
    public void receiveCommand(MyCustomView root, int commandId, @Nullable ReadableArray args) {
        switch (commandId) {
            case CHANGE_COLOR:
                root.changeColor();
                break;
        }
    }

    /**
     * 暴露了在JS中定义的方法,例如下面的"onChangeColor"是定义在JS中的方法。
     * 
     * Returned map should be of the form:
     * {
     *   "onTwirl": {
     *     "registrationName": "onTwirl"
     *   }
     * }
     */
    @Nullable
    @Override
    public Map<String, Object> getExportedCustomDirectEventTypeConstants() {
        return MapBuilder.<String, Object>builder()
                .put("changeColor", MapBuilder.of("registrationName", "onChangeColor"))
                .build();
    }

    /**
     * 发射入口,相当于将Native的一些事件也注册给JS。
     */
    @Override
    protected void addEventEmitters(final ThemedReactContext reactContext, final MyCustomView view) {
        super.addEventEmitters(reactContext, view);
        view.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
             reactContext.getNativeModule(UIManagerModule.class).getEventDispatcher()
                        .dispatchEvent(new ClickEvent(view.getId()));
            }
        });
    }

在上面的代码中可以看到Native会接受一个1(CHANGE_COLOR)的指令以及会回调一个onChangeColor的方法到JS。RN端借助UIManager.dispatchViewManagerCommand即可将事件发送给原生端,如果发送给module则使用 NativeModules.UIManager.dispatchViewManagerCommand(),下面是完整代码:

const ReactNative = require('ReactNative');

const CUSTOM_VIEW = "custom_view";

export default class MyCustomView extends Component{
  constructor(props){
    super(props)
    //绑定事件
    this._onChange = this._onChange.bind(this); 
  }

  // 把事件给Native
  _changeColor() {  
    let self = this;
    UIManager.dispatchViewManagerCommand(
      ReactNative.findNodeHandle(self.refs[CUSTOM_VIEW]),
      1,  // 发送的commandId为1
      null
    );
  }

  _onChange() {
    if (!this.props.handleClick) {
      return;
    }
    this.props.handleClick();
  }

  render(){
    return(
      <RCTMyCustomView 
        ref={CUSTOM_VIEW}
        {...this.props}
        onChangeColor={() => this._onChange()}>
      </RCTMyCustomView>);
  }
}

MyCustomView.propTypes = {
  handleClick: PropTypes.func,
  color: PropTypes.string,  
  ...View.propTypes,
};

var RCTMyCustomView = requireNativeComponent('MyCustomView', MyCustomView, {
  nativeOnly: {onChangeColor: true}
});

注意,ReactNative的导入方式,随着版本的不一样,导入的方式也不一样。
注意上面用到了nativeOnly,有时候有一些特殊的属性,想从原生组件中导出,但是又不希望它们成为对应React封装组件的属性,这时候就可以使用nativeOnly。下面就可以愉快的使用了:

<MyCustomView
  ref='view'
  color='#00ff00'
  handleSizeClick={() => this._handleSizeClick()}
  handleClick={() => this._handleClick()}
  style={{width:300, height:300}} />

这里写图片描述

NativeModule

NativeModule是用来定义Native模块供JS调用的。这样的场景会比较的多,比如Toast,在JS中没有Toast这类东西,但是Android/IOS中却很常见。例如,我们需要调用原生的Toast。

@ReactModule(name = "DemoToast")
public class DemoToastModule extends ReactContextBaseJavaModule {

    private static final String DURATION_SHORT_KEY = "SHORT";
    private static final String DURATION_LONG_KEY = "LONG";

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

    // Module的名称
    @Override
    public String getName() {
        return "DemoToast";
    }

    @Nullable
    @Override
    public Map<String, Object> getConstants() {
        final Map<String, Object> constants = new HashMap<>();
        constants.put(DURATION_SHORT_KEY, Toast.LENGTH_SHORT);
        constants.put(DURATION_LONG_KEY, Toast.LENGTH_LONG);
        return constants;
    }

    /**
     * 通过Callback回调到JS
     */
    @ReactMethod
    public void show(String message, int duration, Callback callback) {
        Toast.makeText(getReactApplicationContext(), message, duration).show();
        callback.invoke("Egos");
    }
}

其中,ReactMethod标示的show是给Js调用的,该方法会通过callback回调信息给Js。
然后,我们使用NativeModules将Native module转化成具体的JS组件,并导出以供第三方调用。

import { NativeModules } from 'react-native';
RCTDemoToast = NativeModules.DemoToast;   

var DemoToast = {

  SHORT: RCTDemoToast.SHORT,
  LONG: RCTDemoToast.LONG,

  show(message, duration){
    RCTDemoToast.show(message, duration, (msg) => {
      var str = msg;
    });
  }
};

module.exports = DemoToast;

然后在第三方使用即可,如果涉及到事件相关的内容。可以使用下面的方式。例如:

componentWillMount() {
  DeviceEventEmitter.addListener('testMethod', (event) => {var s = event;} );
}

当Native代码执行sendEvent指令时,就会在RN中执行上面代码。

WritableMap params = Arguments.createMap();
params.putString("xzh","Egos");
sendEvent(getReactApplicationContext(), "testMethod", params);

/**
* 也可以直接发送事件给JS代码
*/
private void sendEvent(ReactContext reactContext,
    String eventName, @Nullable WritableMap params) {
    reactContext.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class).emit(eventName, params); 
}

参数WritableMap可以实际需要添加。

分享到 :
0 人收藏
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

积分:7942463
帖子:1588486
精华:0
期权论坛 期权论坛
发布
内容

下载期权论坛手机APP