本文介绍了Expo在Native Module上的具体实现及其架构思路。
上篇文章回顾: Kubernetes监控在小米的落地
以expo-permissions为例,使用3.0.0版本。前端
参考项目连接:https://www.npmjs.com/package/expo-permissions/v/3.0.0node
只需几步就可在react-native纯净项目中使用:react
一、yarn add expo-permissions android
二、android/settings.gradle添加项目git
include ':expo-permissions'
project(':expo-permissions').projectDir = new File(rootProject.projectDir,
'../node_modules/expo-permissions/android')复制代码
三、在android/app/build.gradle添加依赖github
api project(':expo-permissions')复制代码
四、重复上面步骤继续加expo-react-native-adapter、expo-permissions-interface、expo-image-loader-interface、expo-font-interface(adapter内部混入了一些模块,目前得加上,Expo团队应该还在作模块拆分中)npm
五、实例化ReactModuleRegistryProviderreact-native
import expo.modules.permissions.PermissionsPackage;
private final ReactModuleRegistryProvider mModuleRegistryProvider = new ReactModuleRegistryProvider(Arrays.<Package>asList(
// 这里可添加其它基于expo-core实现的Native Module
new PermissionsPackage()
), Arrays.<SingletonModule>asList());复制代码
六、由上步构造ModuleRegistryAdapter,它就是一个ReactPackageapi
七、将ModuleRegistryAdapter实例添加到ReactNativeHost的getPackages返回列表里,完成Native导出promise
八、在前端使用如下代码
import * as Permissions from 'expo-permissions'
const { status, expires, permissions } = await Permissions.askAsync(Permissions.LOCATION, Permissions.CONTACTS)复制代码
注意:在AndroidManifest.xml声明须要的权限才能动态请求成功。
另可尝试用react-native-unimodules(https://github.com/unimodules/react-native-unimodules)提供的一些通用脚本及Package代码生成,以简化引用方式,固然这样也须要加入可能本身并不想要的依赖。
下面咱们就以Android实现为例,来简单看看Expo在Native Module上的具体实现。
一、使用版本Android 2.10.8,下载地址:https://github.com/expo/expo/releases/tag/android%2F2.10.8
二、准备Android SDK/Android Studio/Anroid NDK 17c
三、在$SRC_ROOT/tools_public运行yarn
四、在Android Studio中打开$SRC_ROOT/android
五、IDE同步完成后,可直接Run
项目源码截图以下:
expo-core: Expo Native Module定义
expo-permissions-interface:针对permissions相关的Native能力的独立模块接口定义
expo-permissions:对expo-permissions-interface的实现,基于expo-core所定义的接口,不依赖react-native,这样可独立使用
expo-react-native-adapter:将Expo Native Module适配到react-native,上面示例中的ReactModuleRegistryProvider、ModuleRegistryAdapter都在此实现
modules/expo-flutter-adapter:将Expo Native Module适配到flutter
expoview:各子模块集成、核心实现
app: Host App主项目,应用入口定义,依赖expoview
上面的层次结构很是清楚了,愈来愈多的模块会变成interface,并实现,而后可独立使用。
上图为expo-core的代码文件,代码不多,主要是接口定义,能够理解为本身把react-native原生定义的ReactPackage/NativeModule/ViewManager又定义了一遍,这样就是独立统一无依赖的,可提供给上层adatper,咱们以expo-react-native-adapter说明。
InternalModule
InternalModule主要给expo其它内部模块使用,使用者仅依赖接口,是很典型的依赖倒置方法:
public interface InternalModule {
List<Class> getExportedInterfaces();
}复制代码
它们的实现实例会装配到ModuleRegistry,使用方式以下:
mEventEmitter = moduleRegistry.getModule(EventEmitter.class);复制代码
ModuleRegistry是各Module的集中地,用对应get方法拿到Module实例。
ExportedModule子类就是给js实现Native功能的地方,对应react-native的NativeModule。它将ExpoMethod标记的方法收集起来,暴露给上层;另外在invokeExportedMethod被调时,转调到实际的ExpoMethod。
可是js到Native的入口应该为ReactMethod标记,它在哪里呢?
咱们看到expo-react-native-adapter项目中NativeModulesProxy类的callMethod方法,以下:
private final static String NAME = "ExpoNativeModuleProxy";
@ReactMethod
public void callMethod(String moduleName, Dynamic methodKeyOrName, ReadableArray arguments, final Promise promise) {
String methodName;
if (methodKeyOrName.getType() == ReadableType.String) {
methodName = methodKeyOrName.asString();
} else if (methodKeyOrName.getType() == ReadableType.Number) {
methodName = mExportedMethodsReverseKeys.get(moduleName).get(methodKeyOrName.asInt());
} else {
promise.reject(UNEXPECTED_ERROR, "Method key is neither a String nor an Integer -- don't know how to map it to method name.");
return;
}
try {
List<Object> nativeArguments = getNativeArgumentsForMethod(arguments, mModuleRegistry.getExportedModule(moduleName).getExportedMethodInfos().get(methodName));
nativeArguments.add(new PromiseWrapper(promise));
mModuleRegistry.getExportedModule(moduleName).invokeExportedMethod(methodName, nativeArguments);
} catch (IllegalArgumentException e) {
promise.reject(ARGS_TYPES_MISMATCH_ERROR, e.getMessage(), e);
} catch (RuntimeException e) {
promise.reject(UNEXPECTED_ERROR, "Encountered an exception while calling native method: " + e.getMessage(), e);
} catch (NoSuchMethodException e) {
promise.reject(
UNDEFINED_METHOD_ERROR,
"Method " + methodName + " of Java module " + moduleName + " is undefined.",
e
);
}
}复制代码
即实际导出js层的是ExpoNativeModuleProxy.callMethod,再经过mModuleRegistry.getExportedModule转接到ExportedModule的invokeExportedMethod,这样完成了js到ExpoMethod的调用。
至于js层自己会有些逻辑,封装ExpoNativeModuleProxy的使用。最终看到的使用方法就是咱们上面给出的expo-permissions的使用方式。
expo的ViewManager对应react-native的ViewManager,即Native UI Components,也就是导出后在js上使用的React Component,其中给Component导出属性用ExpoProp标记。
最终是经过expo-react-native-adapter的SimpleViewManagerAdapter类以适配的方式完成实际的导出。如如下代码:
@Nullable
@Override
public Map<String, Object> getConstants() {
return ViewManagerAdapterUtils.getConstants(mViewManager);
}
@Override
public String getName() {
return ViewManagerAdapterUtils.getViewManagerAdapterName(mViewManager);
}
@ReactProp(name = "proxiedProperties")
public void setProxiedProperties(V view, ReadableMap proxiedProperties) {
ViewManagerAdapterUtils.setProxiedProperties(getName(), mViewManager, view, proxiedProperties);
}
@Nullable
@Override
public Map<String, Object> getExportedCustomDirectEventTypeConstants() {
return ViewManagerAdapterUtils.getExportedCustomDirectEventTypeConstants(mViewManager);
}复制代码
即实际是由ViewManagerAdapterUtils来完成从react-native的ViewManager到expo的ViewManager的统一映射工做。对于proxiedProperties在js层也须要作些适配,在NativeViewManagerAdapter.tsx里,这里再也不赘述。
expo-core经过内部标准的模块定义,给上层适配,不只拆分了各Native功能独立实现独立使用,还能提供给Flutter使用,多了不少便利性,也让Native开发者能专一于Native功能开发。另外,expo使用者也不用一上来就expo全家桶,能根据须要定制使用expo。
目前expo master源码最新的模块接口项目名称已经由expo-前缀改成uni-来命名了,以下图:
交流互动,欢迎下方留言。
本文首发于公众“小米云技术”,点击阅读原文。