使用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.class
和 ImageSelectorimpl.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,@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,@Nullable WritableMap params) {
mImageSelectorimpl.sendEventMethod(reactContext,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