Expo Android 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.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及expo-react-native-adapter的適配

上圖爲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

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-來命名了,以下圖:

交流互動,歡迎下方留言。

本文首發於公衆「小米雲技術」,點擊閱讀原文

相關文章
相關標籤/搜索