Expo Android Native Module实现分析

栏目: IOS · Android · 发布时间: 5年前

内容简介:本文介绍了Expo在Native Module上的具体实现及其架构思路。上篇文章回顾:最近大前端较热的是Flutter,在大步追赶React Native的步伐。但这两者有个共同点,就是在要使用一些Native能力时,都得做Native的实现,然后再按各自的桥接方式提供给dart/js vm层调用。也就是说,在Native调用上,除了桥接方式不一样,Native本身功能的实现是可以一致并复用的。这不,Expo作为最全的React Native工具集,也在调整结构,试图定义Native Module的统一开发

本文介绍了Expo在Native Module上的具体实现及其架构思路。

上篇文章回顾: Kubernetes监控在小米的落地

引 言

最近大前端较热的是Flutter,在大步追赶React Native的步伐。但这两者有个共同点,就是在要使用一些Native能力时,都得做Native的实现,然后再按各自的桥接方式提供给dart/js vm层调用。也就是说,在Native调用上,除了桥接方式不一样,Native本身功能的实现是可以一致并复用的。这不,Expo作为最全的React Native工具集,也在调整结构,试图定义Native Module的统一开发标准。基于此实现的Native Module将有很大的通用性,除了在Expo项目内使用,也能在纯净react-native项目中使用,甚至还能在Flutter中使用。

Expo-permissions的使用

以expo-permissions为例,使用3.0.0版本。

参考项目链接:https://www.npmjs.com/package/expo-permissions/v/3.0.0

只需几步就可在react-native纯净项目中使用:

1、yarn add expo-permissions

2、android/settings.gradle添加项目

include ':expo-permissions'project(':expo-permissions').projectDir = new File(rootProject.projectDir, '../node_modules/expo-permissions/android')复制代码

3、在android/app/build.gradle添加依赖

api project(':expo-permissions')复制代码

4、重复上面步骤继续加expo-react-native-adapter、expo-permissions-interface、expo-image-loader-interface、expo-font-interface(adapter内部混入了一些模块,目前得加上,Expo团队应该还在做模块拆分中)

5、实例化ReactModuleRegistryProvider

import expo.modules.permissions.PermissionsPackage;private final ReactModuleRegistryProvider mModuleRegistryProvider = new ReactModuleRegistryProvider(Arrays.<Package>asList(  // 这里可添加其它基于expo-core实现的Native Module   new PermissionsPackage()), Arrays.<SingletonModule>asList());复制代码

6、由上步构造ModuleRegistryAdapter,它就是一个ReactPackage

7、将ModuleRegistryAdapter实例添加到ReactNativeHost的getPackages返回列表里,完成Native导出

8、在前端使用以下代码

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上的具体实现。

源码环境搭建

1、使用版本Android 2.10.8,下载地址:https://github.com/expo/expo/releases/tag/android%2F2.10.8

2、准备Android SDK/Android Studio/Anroid NDK 17c

3、在$SRC_ROOT/tools_public运行yarn

4、在Android Studio中打开$SRC_ROOT/android

5、IDE同步完成后,可直接Run

项目源码截图如下:

Expo Android Native Module实现分析

主要项目简介

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及expo-react-native-adapter的适配

Expo Android Native Module实现分析

上图为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

Expo Android Native 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的使用方式。

ViewManager

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-来命名了,如下图:

Expo Android Native Module实现分析

交流互动,欢迎下方留言。

本文首发于公众“小米云技术”,点击阅读原文。


以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

探索需求

探索需求

章柏幸、王媛媛、谢攀、杰拉尔德・温伯格、唐纳德・高斯 / 章柏幸、王媛媛、谢攀 / 清华大学出版社 / 2004-7-1 / 39.00元

本书将与您一起寻找"什么是客户真正想要的"这一问题的答案。 本书着眼于系统设计之前的需求过程,它是整个开发过程(如何设计人们想要的产品和系统)中最有挑战性的那部分。通过对一些需求分析中的常见误区和问题的分析和讨论,从和客户沟通开始,深入研究一些可能的需求,澄清用户和开发者期望值,最终给出了能够大幅度提高项目成功几率的一些建议方法。 本书由该领域内公认的两位作者合著,搜集了他们在大大小小......一起来看看 《探索需求》 这本书的介绍吧!

HTML 压缩/解压工具
HTML 压缩/解压工具

在线压缩/解压 HTML 代码

在线进制转换器
在线进制转换器

各进制数互转换器

MD5 加密
MD5 加密

MD5 加密工具