前言
最近一直在学习RN的相关开发,想做一部分调用原生的实现,来练练手,于是就有了下面这个demo,由于本人是Android开发,所以只实现了Android端的效果.
Demo
主要实现
这种调用原生的操作,主要涉及到RN于原生交互和通信,官方给出的交互方式就是通过在Application中注册的模式实现交互.
首先我们需要先创建一个RN项目 CameraPage.js ,并把它设为启动页:页面比较简单,就是两个按钮拍照,照片一个Image标签用来展示原生的图片.开始新建与原生交互的类, CameraMoudle 继承自ReactContextBaseJavaModule类:
public class CameraMoudle extends ReactContextBaseJavaModule {
private ReactApplicationContext reactContext;
public CameraMoudle(ReactApplicationContext reactContext) {
super(reactContext);
this.reactContext =reactContext;
}
@Override
public String getName() {
return "CameraMoudle";
}
//打开登录设置界面,用于给RN调用
@ReactMethod
public void openNative(int tag) {
Intent intent = new Intent();
CameraAct.reactContext=reactContext;
CameraAct.tag=tag;
intent.setClass(reactContext, CameraAct.class);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
reactContext.startActivity(intent);
}
}
这个getName()方法中返回的就是RN端调用的引用名,相当于webview中addJavascriptInterface(JsInterface,JsName)中的JsName,下面的openNative方法是我们自定义的Rn端调用原生的方法.注意:这里面的reactContext是静态赋值.接下来创建CameraPackage.java继承ReactPackage这个CameraMoudle需要通过这个类在Application中注册才能使用:
public class CameraPackage implements ReactPackage {
@Override
public List createNativeModules(ReactApplicationContext reactContext) {
List modules = new ArrayList<>();
modules.add(new CameraMoudle(reactContext));
return modules;
}
@Override
public List createViewManagers(ReactApplicationContext reactContext) {
return Collections.emptyList();
}
}
在Application中新增标识的代码
@Override
protected List getPackages() {
return Arrays.asList(
new MainReactPackage(),
new CameraPackage()//新增包操作
);
}
新建一个空ActivityCameraAct.java,用来执行,跳转相机和相册的逻辑操作,由于不需要界面所有就不用setContentView啦!
拍照和相册相关操作:
private void choosePhoto() {
Intent intentToPickPic = new Intent(Intent.ACTION_PICK, null);
intentToPickPic.setDataAndType(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, "image/jpeg");
startActivityForResult(intentToPickPic, PHOTO_REQUEST_CODE);
}
private void takePhoto() {
// 跳转到系统的拍照界面
Intent intentToTakePhoto = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
// 指定照片存储位置为sd卡本目录下
mPhotoPath = Environment.getExternalStorageDirectory() + File.separator + "photo.jpeg";
// 获取图片所在位置的Uri路径 由于使用的tagBuildVersion 26需要通过内容提供者获取文件写入操作
mImageUri = FileProvider.getUriForFile(this,
getApplicationContext().getPackageName() + ".my.provider",
new File(mPhotoPath));
//下面这句指定调用相机拍照后的照片存储的路径
intentToTakePhoto.putExtra(MediaStore.EXTRA_OUTPUT, mImageUri);
startActivityForResult(intentToTakePhoto, CAMERA_REQUEST_CODE);
}
//当拍摄照片完成时会回调到onActivityResult 在这里处理照片的裁剪
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
if (resultCode == RESULT_OK) {
switch (requestCode) {
case CAMERA_REQUEST_CODE: {
sendToRn();
break;
}
case PHOTO_REQUEST_CODE: {
Uri mImageUri=data.getData();
if (!TextUtils.isEmpty(mImageUri.getAuthority())) {
String[] proj = {MediaStore.Images.Media.DATA};
Cursor cursor = managedQuery(mImageUri, proj, null, null,
null);
int column_index = cursor
.getColumnIndexOrThrow(MediaStore.Images.Media.DATA);
cursor.moveToFirst();
mPhotoPath = cursor.getString(column_index);
}else{
mPhotoPath= mImageUri.getPath();
}
sendToRn();
break;
}
default:
break;
}
}
super.onActivityResult(requestCode, resultCode, data);
}
由于我本地targetSdkVersion为26 所以需要动态申请权限和通过内容提供者获取文件读取操作(如果使用 22 版本就都不需要啦!)
private boolean checkPermission(){
String [] permissions =new String[]{
Manifest.permission.WRITE_EXTERNAL_STORAGE,
Manifest.permission.CAMERA
};
for (String permission:permissions) {
if (ContextCompat.checkSelfPermission(CameraAct.this, permission) != PackageManager.PERMISSION_GRANTED){
ActivityCompat.requestPermissions(CameraAct.this, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE, Manifest.permission.CAMERA},
tag==0?CAMERA_REQUEST_CODE:PHOTO_REQUEST_CODE);
return false;
}
}
return true;
}
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
switch (requestCode) {
case CAMERA_REQUEST_CODE:
takePhoto();
break;
case PHOTO_REQUEST_CODE:
choosePhoto();
break;
default:
break;
}
} else {
Toast.makeText(this, "一些权限被禁止", Toast.LENGTH_SHORT).show();
}
}
入口调用方法
private void startCamera() {
if(checkPermission()){
if (tag==0) {
takePhoto();
}else {
choosePhoto();
}
}
}
为了防止静态赋值的reactContext内存泄露:
@Override
protected void onDestroy() {
super.onDestroy();
reactContext=null;
}
差点忘了权限添加:
内容提供者:临时权限配置
android:name="android.support.v4.content.FileProvider"
android:authorities="${applicationId}.my.provider"
android:exported="false"
android:grantUriPermissions="true">
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/provider_paths"/>
在android/src/main/res下新建xml文件夹 创建provider_paths.xml
至此android端的配置已经完成,接下来修改RN端代码:CameraPage.js增加以下代码:
getPhoto=(statusCode)=>{
//调用原生
CameraMoudle.openNative(statusCode);
//注册监听
DeviceEventEmitter.addListener("photo", (params) => {
let index =params.indexOf('storage');
let file='file:///'+params.substr(index==-1?1:index);
this.setState({
imgFile:file
})
});
}
statusCode是用来区分照相和相册选择的,通用处理了一下,Image标签:
{this.state.imgFile===''?null:}
以上调用就可以实现Rn调用原生的操作.
另外
RN与原生交互的方式有3中:RCTDeviceEventEmitter,Callback,Promise;其中第一种方式是唯一一种原生主动发送消息给RN的.
另外两种都是RN端作为主动方,原生被动返回.
以上如有疏漏的地方,望不吝赐教.