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实现分析

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

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


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

查看所有标签

猜你喜欢:

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

Blog Design Solutions

Blog Design Solutions

Richard Rutter、Andy Budd、Simon Collison、Chris J Davis、Michael Heilemann、Phil Sherry、David Powers、John Oxton / friendsofED / 2006-2-16 / USD 39.99

Blogging has moved rapidly from being a craze to become a core feature of the Internetfrom individuals sharing their thoughts with the world via online diaries, through fans talking about their favori......一起来看看 《Blog Design Solutions》 这本书的介绍吧!

图片转BASE64编码
图片转BASE64编码

在线图片转Base64编码工具

随机密码生成器
随机密码生成器

多种字符组合密码

UNIX 时间戳转换
UNIX 时间戳转换

UNIX 时间戳转换