尽管React Native官方提供了许多原生api简化我们的开发流程,但是在实际开发过程中,往往需要我们自行封装原生代码供RN层进行调用,本文将分Android和iOS平台简单介绍具体流程。
Android:
假如我们想要原生提供一个方法,将JS抛出的异常写入到本地(这有助于我们在release包中迅速的定位问题的原因),首先我们需要创建一个继承自ReactContextBaseJavaModule类的工具类,作为导出原生方法的入口:
ToolModule.java
package com.smarthome.modules;
import android.util.Log;
import androidx.annotation.NonNull;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.react.bridge.ReactMethod;
import com.smarthome.utils.FileUtils;
import java.io.File;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;
public class ToolModule extends ReactContextBaseJavaModule {
private ReactContext mContext;
private final String TAG = "ToolModule";
public ToolModule(ReactApplicationContext reactContext) {
super(reactContext);
mContext = reactContext;
}
@Override
public String getName() {
return "ToolModule";
}
@ReactMethod
public void saveJSExceptionsToStorage(String exceptionText) {
Log.d(TAG, "exceptionText: " + exceptionText);
String externalFilePath = FileUtils.getExternalFileDir().getPath();
String targetDir = externalFilePath + File.separator + "jsExceptionLogs";
File log = new File(targetDir);
if (log.exists() ||
(!log.exists() && log.mkdir())) {
StringBuilder targetFilePath = new StringBuilder();
Date now = new Date();
DateFormat dateFormat = new SimpleDateFormat("yyyy_MM_dd_HH_mm_ss", Locale.getDefault());
targetFilePath.append(targetDir).append(File.separator).append("log_").append(dateFormat.format(now)).append(".txt");
File targetFile = new File(targetFilePath.toString());
FileUtils.writeTextToFiles(targetFile, exceptionText);
}
}
}
然后,我们需要再创建一个类继承自ReactPackage,并将工具类ToolModule实例化,添加到createNativeModules方法返回的List<NativeModule>集合中:
package com.smarthome.modules;
import androidx.annotation.NonNull;
import com.facebook.react.ReactPackage;
import com.facebook.react.bridge.NativeModule;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.uimanager.ViewManager;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
public class CustomPackage implements ReactPackage {
@Override
@NonNull
public List<ViewManager> createViewManagers(@NonNull ReactApplicationContext reactContext) {
return Arrays.<ViewManager>asList();
}
@Override
@NonNull
public List<NativeModule> createNativeModules(
@NonNull ReactApplicationContext reactContext) {
List<NativeModule> modules = new ArrayList<>();
modules.add(new ToolModule(reactContext));
return modules;
}
}
接着在MainApplication.java文件中,把CustomPackage类的实例添加到packages集合中即可:
packages.add(new CustomPackage());
最后我们只需要在RN代码中调用NativeModules.ToolModule.saveJSExceptionsToStorage,就可以调用原生层定义的函数。
iOS:
假设我们需要获取iOS部分系统信息,我们同样将方法定义在ToolModule中,先要分别创建对应的头文件和实现文件:
ToolModule.h
#import <Foundation/Foundation.h>
#import <React/RCTBridgeModule.h>
#import <React/RCTEventEmitter.h>
@interface ToolModule: RCTEventEmitter <RCTBridgeModule>
@end
ToolModule类需要继承RCTEventEmitter类(原生层发送事件通知RN层)并遵循RCTBridgeModule协议(RN层可以调用原生层方法)。然后是实现文件:
ToolModule.m
#import "ToolModule.h"
@implementation ToolModule
static id _instace;
+ (instancetype)allocWithZone:(struct _NSZone *)zone {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
_instace = [super allocWithZone:zone];
});
return _instace;
}
+ (BOOL)requiresMainQueueSetup {
return YES;
}
RCT_EXPORT_MODULE();
#pragma mark - 获取iOS系统信息
RCT_EXPORT_METHOD(equipmentInfo:(RCTPromiseResolveBlock)resolve
reject:(__unused RCTPromiseRejectBlock)reject){
//获取系统语言
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
NSArray *languages = [defaults objectForKey:@"AppleLanguages"];
NSString *currentLanguage = [languages objectAtIndex:0];
//获取系统版本号
NSString *sysVersion = [[UIDevice currentDevice] systemVersion];
//获取手机型号
NSString* phoneModel = [[UIDevice currentDevice] model];
//获取手机设备名
NSString* deviceName = [[UIDevice currentDevice] systemName];
NSDictionary *dic = @{@"language":currentLanguage,
@"sysVersion":sysVersion,
@"phoneModel":phoneModel,
@"deviceName":deviceName,
};
resolve(dic);
}
@end
上面的代码可以看到,我们只需要在实现文件中调用RCT_EXPORT_MODULE();,并将需要导出给RN使用的方法通过RCT_EXPORT_MODULE()宏注册成原生模块。这里的equipmentInfo方法返回的是一个Promise对象,成功决议的Promise中包含着我们需要的字典形式的iOS系统信息。
同样,在RN代码中,我们只需要通过NativeModules.ToolModule.equipmentInfo().then(dic => {});的形式获取原生iOS层传递过来的数据。
通过上面的例子,我们可以发现RN集成原生的能力是非常强的。这里介绍的仅仅只是RN主动调用原生提供的方法。实际上我们还可以通过接收原生事件的形式来与原生进行通信,甚至是自行封装原生视图组件供RN层使用,这将在我之后的博客中介绍。