前言
最近一直在学习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;}@Overridepublic String getName() {return "CameraMoudle";}//打开登录设置界面,用于给RN调用@ReactMethodpublic 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 {@Overridepublic List<NativeModule> createNativeModules(ReactApplicationContext reactContext) {List<NativeModule> modules = new ArrayList<>();modules.add(new CameraMoudle(reactContext));return modules;}@Overridepublic List<ViewManager> createViewManagers(ReactApplicationContext reactContext) {return Collections.emptyList();}}
在Application中新增标识的代码
@Overrideprotected List<ReactPackage> getPackages() {return Arrays.<ReactPackage>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 在这里处理照片的裁剪@Overridepublic 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;}@Overridepublic 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内存泄露:
@Overrideprotected void onDestroy() {super.onDestroy();reactContext=null;}
差点忘了权限添加:
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" /><uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /><uses-permission android:name="android.permission.CAMERA" />
内容提供者:临时权限配置
<providerandroid:name="android.support.v4.content.FileProvider"android:authorities="${applicationId}.my.provider"android:exported="false"android:grantUriPermissions="true"><meta-dataandroid:name="android.support.FILE_PROVIDER_PATHS"android:resource="@xml/provider_paths"/></provider>
在android/src/main/res下新建xml文件夹 创建provider_paths.xml
<paths xmlns:android="/apk/res/android"><external-path name="external_files" path="."/></paths>
至此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标签:
<View style={{alignItems:'center'}}>{this.state.imgFile===''?null:<Image style={{width:width/2,height: height/2}} source={{uri:this.state.imgFile}}/>}</View>
以上调用就可以实现Rn调用原生的操作.
另外
RN与原生交互的方式有3中:RCTDeviceEventEmitter,Callback,Promise;其中第一种方式是唯一一种原生主动发送消息给RN的.
另外两种都是RN端作为主动方,原生被动返回.
以上如有疏漏的地方,望不吝赐教.