react-native 之与"android原生模块"交互

使用react-native写移动端项目,怎么能够少的了与原生模块的交互。否则,混合开发,那不是白瞎了这个名字喽。

使用react-native 与android原生模块交互方式

1,使用回调的Callback方式实现与android原生模块交互
2,使用JavaScript的Promise方式实现与android原生模块交互
3,使用react-native的DeviceEventEmitter 方式实现与android原生模块交互

在描述三种交互方式之前,先描述一下我的交互逻辑。这里我通过从react-native的js代码中启用安卓原生代码,打开相册并获取其中一张照片并回调到js页面方法中并进行接收、赋值、更新页面。
原生代码打开相册并获取图片地址功能我引入了github上的一个第三方库,在react-native项目的android/app/build.gradle中进行依赖处理,然后编写原生java代码逻辑调用打开相册功能,并实现react-native中的js与java的互相访问调用来实现交互。

原生模块的逻辑实现

编写原生逻辑
暴露原模块接口向js
注册原生模块
导出、导入react-native原生模块

step导入原生的android模块

首先导入原生的android模块项目代码到Android Studio,像这样
这里写图片描述
build成功之后,引入相册功能的gralde依赖到android/app/build.gradle,像这样加入
compile 'com.github.LuckSiege.PictureSelector:picture_library:v2.1.4'//引入相机功能第三方
然后在android/build.gradle中配置maven {url 'https://jitpack.io'}当然这些在第三方依赖库有使用说明,我就不多描述了。说一点略微重要的,在使用第三方库时候,她们使用的依赖方式是implementation 而不是 compile这个是你的IDE高版本的时候方法,这是如果在使用引入方式是compile无法正常通过编译,那么你可以使用该库的之前版本,可到Branch中查看。只是为了解决Filed to resolve:com.android.support:support-xx:xx找不到该版本号这种问题。
这里写图片描述
若在刚导入了原生代码,build上有问题请查看博客,或许会有帮助。

step编写原生的android模块代码

/**
 * @Title:ImageCrop
 * @Package:com.imoocchapterone
 * @Description:选择剪切图片的接口
 * @Auther:YJH
 * @Email:yuannunhua@gmail.com
 * @Date:2018/6/1415:28
 */
public interface ImageSelector {

    /**
     * @param errorCallback
     * @param successCallback
     */
    void chooseImgCallback(Callback successCallback, Callback errorCallback);

    /**
     * @param promise
     */
    void chooseImgPromise(Promise promise);
}
/**
 * @Title:ImageCropImp
 * @Package:com.imoocchapterone
 * @Description:选择剪切图片的具体类
 * @Auther:YJH
 * @Email:yuannunhua@gmail.com
 * @Date:2018/6/1415:30
 */
public class ImageSelectorimpl implements ImageSelector, ActivityEventListener {

    private Activity mActivity;
    private Promise mPromise;
    private Callback errorCallback;
    private Callback successCallback;

    private ImageSelectorimpl(Activity activity) {
        this.mActivity = activity;
    }


    /**
     * 功能:获取 ImageSelectorimpl 对象的实例
     * @param activity
     * @return
     */
    public static ImageSelectorimpl of(Activity activity) {
        return new ImageSelectorimpl(activity);
    }

    /**
     * 使用Promise回调方式
     * 功能:实现ImageCrop接口中的方法
     * @param promise
     */
    @Override
    public void chooseImgPromise(Promise promise) {
        this.mPromise = promise;
        getSelectAlbum();
    }

    /**
     * 使用Callback回调方式
     * 功能:实现ImageCrop接口中的方法
     * @param promise
     */
    @Override
    public void chooseImgCallback(Callback successCallback, Callback errorCallback) {
        this.successCallback = successCallback;
        this.errorCallback = errorCallback;
        getSelectAlbum();
    }

    public void updateActivity(Activity activity) {
        this.mActivity = activity;
    }


    /**
     * 功能:实现ActivityEventListener接口中的方法
     * @param activity
     * @param requestCode
     * @param resultCode
     * @param data
     */
    @Override
    public void onActivityResult(Activity activity, int requestCode, int resultCode, Intent data) {
        if (resultCode == RESULT_OK) {
            switch (requestCode) {
                case PictureConfig.CHOOSE_REQUEST:
                    // 图片选择结果回调
                    selectList = PictureSelector.obtainMultipleResult(data);
//                    successCallback.invoke(selectList.get(0).getPath());//Callback回调
                    mPromise.resolve(selectList.get(0).getPath());//mPromise回调
                    break;
            }
        } else {
            mPromise.reject("no img get.");//mPromise回调
//            errorCallback.invoke(CODE_ERROR_CROP, "裁剪失败");//Callback回调
        }
    }

    /**
     * 功能:实现ActivityEventListener接口中的方法
     *
     * @param intent
     */
    @Override
    public void onNewIntent(Intent intent) {
//   无操作
    }

    private int chooseMode = PictureMimeType.ofAll();
    private List<LocalMedia> selectList = new ArrayList<>();

    //打开相册功能
    public void getSelectAlbum() {
        // 进入相册 以下是例子:用不到的api可以不写
        PictureSelector.create(mActivity)
                .openGallery(chooseMode)//全部.PictureMimeType.ofAll()、图片.ofImage()、视频.ofVideo()、音频.ofAudio()
                .theme(R.style.picture_default_style)//主题样式(不设置为默认样式) 也可参考demo values/styles下 例如:R.style.picture.white.style
                .maxSelectNum(1)// 最大图片选择数量 int
                .minSelectNum(1)// 最小选择数量 int
                ...
                .forResult(PictureConfig.CHOOSE_REQUEST);//结果回调onActivityResult code

    }
}

以上是在原生代码中去实现操作原生模块逻辑的java代码
其中在类ImageSelectorimpl.class中实现了接口ActivityEventListener,并由此重写其中方法onActivityResult(..),作为用户执行startActivityForResult(..)程序的回调。关键的是,在onActivityResult(..)中拿到原生逻辑的Activity回调,是要将结果回调到js代码逻辑页面以提供使用。由此需要有设置对方法onActivityResult(..)的监听。则后面会通过addActivityEventListener(..)方法来实现。

/**
 * @Title:ImageSelectorModule
 * @Package:com.imoocchapterone
 * @Description:实现对react-native的接口暴露
 * @Auther:YJH
 * @Email:yuannunhua@gmail.com
 * @Date:2018/6/1415:59
 */
public class ImageSelectorModule extends ReactContextBaseJavaModule implements ImageSelector {

    private ImageSelectorimpl mImageSelectorimpl;

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

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


    /**
     * 功能:向React Native暴露的接口,并执行数据交互操作
     */
    @ReactMethod
    @Override
    public void chooseImgCallback(Callback successCallback, Callback errorCallback) {
        if(null == mImageSelectorimpl){
            mImageSelectorimpl = ImageSelectorimpl.of(getCurrentActivity());
            getReactApplicationContext().addActivityEventListener(mImageSelectorimpl);
        }
        mImageSelectorimpl.chooseImgCallback(successCallback, errorCallback);
    }

    @ReactMethod
    @Override
    public void chooseImgPromise(Promise promise) {
        if(null == mImageSelectorimpl){
            mImageSelectorimpl = ImageSelectorimpl.of(getCurrentActivity());
            getReactApplicationContext().addActivityEventListener(mImageSelectorimpl);
        }

//        mImageSelectorimpl.updateActivity(getCurrentActivity());
        mImageSelectorimpl.chooseImgPromise(promise);
    }


}

当然对于ImageSelectorModule.classImageSelectorimpl.class其实可以合成一个类并同时继承类ReactContextBaseJavaModule和实现接口ActivityEventListener也是可以的。暂不多说。。
在这个类中继承了一个抽象类ReactContextBaseJavaModule,实现了在其抽象父类BaseJavaModule中的关键方法getName()。返回一个字符串,当然这个返回值的名字,你可以随便起,但时请记住,这个字符串名字最后是要与你在js中调用的组件NativeModules中的一个子组件名字是要保持一致的。后面会分析到!
在这个类中继承了一个抽象类ReactContextBaseJavaModule,通过注解标识@ReactMethod实现对js暴露。
在这个类中继承了一个抽象类ReactContextBaseJavaModule,看其源码


public abstract class ReactContextBaseJavaModule extends BaseJavaModule {
 ...

  /**
   * Subclasses can use this method to access catalyst context passed as a constructor
   */
  protected final ReactApplicationContext getReactApplicationContext() {
    return mReactApplicationContext;
  }

  /**
   * Get the activity to which this context is currently attached, or {@code null} if not attached.
   *
   * DO NOT HOLD LONG-LIVED REFERENCES TO THE OBJECT RETURNED BY THIS METHOD, AS THIS WILL CAUSE
   * MEMORY LEAKS.
   *
   * For example, never store the value returned by this method in a member variable. Instead, call
   * this method whenever you actually need the Activity and make sure to check for {@code null}.
   */
  protected @Nullable final Activity getCurrentActivity() {
    return mReactApplicationContext.getCurrentActivity();
  }
}

关键的两个方法getReactApplicationContext()getCurrentActivity()
方法getCurrentActivity()从注释上看,就是拿到一个activity来供我们操作。
方法getReactApplicationContext()从注释上看,”子类可以使用此方法访问作为构造函数传递上下文”。其实就是为了提供将原生模块注册到react-native组件NativeModule的队列并绑定接口ReactPackage所使用的
看下面代码的具体实现

/**
 * @Title:ImageReactPackage
 * @Package:com.imoocchapterone
 * @Description:注册、导出react-native的原生模块<格式是基本固定的>
 * @Auther:YJH
 * @Email:yuannunhua@gmail.com
 * @Date:2018/6/1416:44
 */
public class ImageReactPackage implements ReactPackage {

    @Override
    public List<NativeModule> createNativeModules(ReactApplicationContext reactContext) {
        List<NativeModule> modules = new ArrayList<>();
        modules.add(new ImageSelectorModule(reactContext));
        return modules;

    }

  ...
}

实现注册,且只有在实现了接口ReactPackage才有资格注册。

public class MainApplication extends Application implements ReactApplication {

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

...
}

以上就是在android模块引入第三方类库,并实现可与react-native的js进行交互的基础逻辑配置。
创建一个js类ImageSelector,导出NativeModules.ImageSelector备后面使用。NativeModules我们看到是个组件,此时ImageSelector也既有可能成为了组件类型。并且ImageSelector这个单词的形成是在原生代码模块的类ImageSelectorModule.class中的方法getName()中得到!并且ImageSelector能调用暴露java接口方法说明js 类 ImageSelector好像继承(具备)了java 抽象类 ReactContextBaseJavaModule的功能。即从继承java 抽象类 ReactContextBaseJavaModule的类作为调用的交互入口。

/**
 * Created by YJH on 2018/6/14.
 */
import {NativeModules} from 'react-native';
export default NativeModules.ImageSelector;

当然有导出,只在我们需要使用的地方导入就可以使用了
import ImageSelector from '../../nativeJs/ImageSelector';

1使用回调的Callback方式实现与android原生模块交互

在一个组件的按钮被点击时调用方法

...

onPress={() => this.onSelectPicturesCallback()}

...

/**
* Callback 方式
* 功能:获取使用原生打开相册并获取图片的回调地址方式
*/
onSelectPicturesCallback() {
   ImageSelector.chooseImgCallback((result) => {
        console.log(result);
        this.setState({
             imgUrl: result
        })
   }, (error) => {
        console.log(error);
        this.setState({
            imgUrl: error
        })
  })
}

...

通过这种方式可以在state中成功更新的imgUrl值。

2使用JavaScript的Promise方式实现与android原生模块交互

...

onPress={() => this.onSelectPicsFucX()}

...

/**
* Promise 方式1
* 功能:获取使用原生打开相册并获取图片的回调地址方式
*/
onSelectPicsFuc1(){
    ImageSelector.chooseImgPromise().then((result) => {
        this.setState({
            imgUrl: result
        })
    }).catch(error => {
        console.log(error);
        this.setState({
             imgUrl: error
        })
    });
}
/**
* Promise 方式2
* 功能:获取使用原生打开相册并获取图片的回调地址方式
*/
async onSelectPicsFuc2() {
    var _promise =  await ImageSelector.chooseImgPromise();
    _promise.then((result) => {
         this.setState({
             imgUrl: result
         })
    }).catch(error => {
        console.log(error);
        this.setState({
             imgUrl: error
        })
   });
}

/**
* Promise 方式3
* 功能:获取使用原生打开相册并获取图片的回调地址方式
*/
async onSelectPicsFuc3() {
     var imgPromise = await ImageSelector.chooseImgPromise();
     this.setState({
         imgUrl: imgPromise
     })
}

...

可以看到这里展示了使用promise获取与原生模块交互的逻辑回调。并使用了三方种方法来实现!
其中方式1方式3可以获得Promise对象并在state中更新的imgUrl值。但是方式2是不能实现的,而且是错误并提醒报错的。因为使用了asycn/await方式之后,返回的是一个内容结果,而不是Promise对象。
注意,被桥接的原生方法的最后一个参数是一个Promise对象,那么该JS方法会返回一个Promise对象。且,不需在js中传入promise对象参数。

3使用react-native的DeviceEventEmitter 方式实现与android原生模块交互

使用以上两种方式回去与原生模式的交互回调,只能执行一次。一次启动,带动一次回调,然后结束!如果想要在原生模块向js发送多次事件。那么,就要用到事件发射器。她可实现,原生模块可以在没有被直接调用的情况下就可以往JavaScript发送消息事件。好的实现一下
在js类中实现

...

componentDidMount() {
   //注册扫描监听
   DeviceEventEmitter.addListener('onScanningResult', this.onScanningResult);
}

onScanningResult = (eventName, params) => {

   //收到原生代码的发射通知,弹出toast提示
   this.toast.show(eventName, DURATION.LENGTH_SHORT);
};

componentWillUnmount() {
   DeviceEventEmitter.removeListener('onScanningResult', this.onScanningResult);//移除扫描监听
}
...

在原生模块实现

public interface ImageSelector {

   ...

    void sendEventMethod(String eventName, @Nullable WritableMap params);
    void sendEventMethod(ReactContext reactContext,String eventName, @Nullable WritableMap params);
}
public class ImageSelectorimpl implements ImageSelector, ActivityEventListener {

  ...
    /**
     * 功能:发射器实现通知功能的-具体方法
     * @param reactContext
     * @param eventName
     * @param params
     */
    @Override
    public void sendEventMethod(ReactContext reactContext, String eventName, @Nullable WritableMap params) {
        reactContext.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class)
                .emit(eventName, params);
    }
    @Override
    public void sendEventMethod(String eventName, @Nullable WritableMap params) {
    }
   ...
}
public class ImageSelectorModule extends ReactContextBaseJavaModule implements ImageSelector {

    ...

    /**
     * 功能:发射器实现通知功能方法
     * @param eventName
     * @param params
     */
    @Override
    public void sendEventMethod(String eventName, @Nullable WritableMap params) {
        this.sendEventMethod(reactContext, eventName, params);
    }

    @Override
    public void sendEventMethod(ReactContext reactContext, String eventName, @Nullable WritableMap params) {
        mImageSelectorimpl.sendEventMethod(reactContext, eventName, params);
    }
}

只是在原先的代码中添加了写方法,来实现可多次进行发射器发送通知的功能:

ImageSelectorModule实例.sendEventMethod(String eventName, @Nullable WritableMap params);

参考资料:
https://www.hellojava.com
https://stackoverflow.com/questions/
https://github.com/crazycodeboy/RNStudyNotes/blob/
http://www.lcode.org/react-native
http://www.devio.org/2017/01/22/React-Native-Android

  • 2
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值