React-Native新架构中封装Android原生接口

2,478 阅读5分钟

随着react-native的0.74版本发布,新架构在新版本越来越完善,新架构抛弃了之前的JSBrige方式来调用。官方说这种方式调用调用性能更高。从 0.68 版本开始,React Native 提供了新架构,它为开发者提供了构建高性能和响应式应用的新功能。

新架构里面包含了下面三部分的内容

新旧架构对比

旧架构的问题

旧的架构曾经通过使用一个叫做桥(Bridge)的组件将所有必须从 JS 层传递到本地层的数据序列化来工作。桥可以被想象成一条总线,生产者层为消费者层发送一些数据。消费者可以读取数据,将其反序列化并执行所需的操作。

桥有一些固有的限制:

  • 它是异步的:某个层将数据提交给桥,再异步地"等待"另一个层来处理它们,即使有时候这并不是真正必要的。
  • 它是单线程的:JS 是单线程的,因此发生在 JS 中的计算也必须在单线程上进行。
  • 它带来了额外的开销:每当一个层必须使用另一个层时,它就必须序列化一些数据。另一层则必须对其进行反序列化。这里选择的格式是 JSON,因为它的简单性和人的可读性,但尽管是轻量级的,它也是有开销的。

新架构的改进

新架构放弃了"桥"的概念,转而采用另一种通信机制:JavaScript 接口(JSI)。JSI 是一个接口,允许 JavaScript 对象持有对 C++ 的引用,反之亦然。

一旦一个对象拥有另一个对象的引用,它就可以直接调用该对象的方法。例如一个 C++ 对象现在可以直接调用一个 JavaScript 对象在 JavaScript 环境中执行一个方法,反之亦然。

这个想法可以带来几个好处:

  • 同步执行:现在可以同步执行那些本来就不应该是异步的函数。
  • 并发:可以在 JavaScript 中调用在不同线程上执行的函数。
  • 更低的开销:新架构不需要再对数据进行序列化/反序列化,因此可以避免序列化的开销。
  • 代码共享:通过引入 C++,现在有可能抽象出所有与平台无关的代码,并在平台之间轻松共享它。
  • 类型安全:为了确保 JS 可以正确调用 C++ 对象的方法,反之亦然,因此增加了一层自动生成的代码。这些代码必须通过 Flow 或 TypeScript 类型化的 JS 规范来生成。

CODEING

  1. 创建一个最新版的react-native版本项目

    npx react-native init RN74NewArch
    

    然后在项目RN74NewArch平级创建一个RTNCalculator TurboModule模块。然后在RTNCalculator下面创建androidiosjs文件夹。Android文件夹存放原生相关的配置和代码,js文件夹下面存放react-native中调用相关的代码。整个项目结构代码如下。 image.png

  2. RTNCalculator的根目录下面android文件夹创建build.gradle文件。

    buildscript {
      ext.safeExtGet = {prop, fallback ->
        rootProject.ext.has(prop) ? rootProject.ext.get(prop) : fallback
      }
      repositories {
        // 配置为国内的阿里云镜像
        maven { url 'https://maven.aliyun.com/repository/public' }
        maven { url 'https://maven.aliyun.com/repository/google' }
        maven { url 'https://maven.aliyun.com/repository/gradle-plugin' }
        mavenCentral()
        google()
        gradlePluginPortal()
      }
      dependencies {
        classpath("com.android.tools.build:gradle:7.1.1")
      }
    }
    
    apply plugin: 'com.android.library'
    apply plugin: 'com.facebook.react'
    
    android {
      compileSdkVersion safeExtGet('compileSdkVersion', 31)
    }
    
    repositories {
      maven {
        url "$projectDir/../node_modules/react-native/android"
      }
      mavenCentral()
      google()
    }
    
    dependencies {
      implementation 'com.facebook.react:react-native:+'
      // 此处是你需要调用的原生API 接口三方包。如果是自己撸纯原生,
      /// 下面可以不用写。这儿模拟封装一个 Picker Image接口给js层调用
      implementation 'com.github.gzu-liyujiang.AndroidPicker:Common:4.1.13'
      implementation 'com.github.gzu-liyujiang.AndroidPicker:WheelView:4.1.13'
      implementation 'com.github.gzu-liyujiang.AndroidPicker:ImagePicker:4.1.13'
    }
    
  3. RTNCalculator/android/src/main下创建AndroidManifest.xml文件。在这里面存放你可能用到的一些配置。

    <manifest xmlns:android="http://schemas.android.com/apk/res/android"
         package="com.rtncalculator">
    </manifest>
    
  4. RTNCalculator/android/src/main/javavcom/rtncalculator中创建CalculatorPackage.java文件

    package com.rtncalculator;
    
    import androidx.annotation.Nullable;
    import com.facebook.react.bridge.NativeModule;
    import com.facebook.react.bridge.ReactApplicationContext;
    import com.facebook.react.module.model.ReactModuleInfo;
    import com.facebook.react.module.model.ReactModuleInfoProvider;
    import com.facebook.react.TurboReactPackage;
    
    import java.util.Collections;
    import java.util.List;
    import java.util.HashMap;
    import java.util.Map;
    
    public class CalculatorPackage extends TurboReactPackage {
    
    @Nullable
    @Override
    public NativeModule getModule(String name, ReactApplicationContext reactContext) {
       if (name.equals(CalculatorModule.NAME)) {
           return new CalculatorModule(reactContext);
       } else {
           return null;
       }
    }
    
    
    @Override
    public ReactModuleInfoProvider getReactModuleInfoProvider() {
       return () -> {
           final Map<String, ReactModuleInfo> moduleInfos = new HashMap<>();
           moduleInfos.put(
                   CalculatorModule.NAME,
                   new ReactModuleInfo(
                           CalculatorModule.NAME,
                           CalculatorModule.NAME,
                           false, // canOverrideExistingModule
                           false, // needsEagerInit
                           true, // hasConstants
                           false, // isCxxModule
                           true // isTurboModule
           ));
           return moduleInfos;
       };
    }
    }
    
  5. RTNCalculator/android/src/main/javavcom/rtncalculator中创建CalculatorModule.java文件

    package com.rtncalculator;
    
    import android.app.Activity;
    
    import androidx.annotation.NonNull;
    
    import com.facebook.react.bridge.Promise;
    import com.facebook.react.bridge.ReactApplicationContext;
    
    public class CalculatorModule extends NativeCalculatorSpec {
    
        public static String NAME = "RTNCalculator";
    
    
        CalculatorModule(ReactApplicationContext context) {
            super(context);
        }
    
        @Override
        @NonNull
        public String getName() {
            return NAME;
        }
        @Override
        public void onCamera(final Promise promise) {
            final Activity activity = this.getReactApplicationContext().getCurrentActivity();
            PickerModuleImpl pickerModule = new PickerModuleImpl(this.getReactApplicationContext());
            pickerModule.onCamera(activity, promise);
        }
    
        @Override
        public void onGallery(Promise promise) {
    
        }
    
    }
    
    1. RTNCalculator/android/src/main/javavcom/rtncalculator中创建PickerModuleImpl.java文件

       package com.rtncalculator;
      
       import android.app.Activity;
       import android.content.Intent;
       import android.net.Uri;
       import android.util.Log;
       import android.widget.Toast;
      
       import androidx.annotation.Nullable;
      
       import com.facebook.react.bridge.ActivityEventListener;
       import com.facebook.react.bridge.Promise;
       import com.facebook.react.bridge.ReactApplicationContext;
       import com.github.gzuliyujiang.imagepicker.ActivityBuilder;
       import com.github.gzuliyujiang.imagepicker.CropImageView;
       import com.github.gzuliyujiang.imagepicker.ImagePicker;
       import com.github.gzuliyujiang.imagepicker.PickCallback;
      
       public class PickerModuleImpl implements ActivityEventListener {
      
           public PickerModuleImpl(ReactApplicationContext reactContext) {
               reactContext.addActivityEventListener(this);
           }
           public void onCamera(final Activity activity, final Promise promise) {
               Log.d("onCamera...", "onCamera: 调用方法");
               PickCallback callback = new PickCallback() {
                   @Override
                   public void onPermissionDenied(String[] permissions, String message) {
                       Toast.makeText(activity, message, Toast.LENGTH_SHORT).show();
                   }
      
                   @Override
                   public void cropConfig(ActivityBuilder builder) {
                       Log.d("onCamera...", "onCamera: 读取配置");
                       builder.setMultiTouchEnabled(true)
                               .setGuidelines(CropImageView.Guidelines.ON_TOUCH)
                               .setCropShape(CropImageView.CropShape.OVAL)
                               .setRequestedSize(400, 400)
                               .setFixAspectRatio(true)
                               .setAspectRatio(1, 1);
                   }
      
                   @Override
                   public void onPickImage(@Nullable Uri imageUri) {
                       String path = String.valueOf(imageUri);
                       Log.d("onCamera...", "onCamera: 获取结果");
                       Toast.makeText(activity,path, Toast.LENGTH_SHORT).show();
                       promise.resolve(path);
                   }
               };
               ImagePicker.getInstance().startCamera(activity, true, callback);
           }
           @Override
           public void onActivityResult(Activity activity, int i, int i1, @Nullable Intent data) {
               Log.d("onCamera", "onActivityResult: ");
               ImagePicker.getInstance().onActivityResult(activity, i, i1, data);
           }
      
           @Override
           public void onNewIntent(Intent intent) {
      
           }
       }
      
  6. RTNCalculator/js中创建NativeCalculator.ts文件,定义在js类型声明

    import type {TurboModule} from 'react-native/Libraries/TurboModule/RCTExport';
    import {TurboModuleRegistry} from 'react-native';
    
    export interface Spec extends TurboModule {
     add(a: number, b: number): Promise<number>;
     sub(a: number, b: number): Promise<number>;
     onCamera(): Promise<string>;
     onGallery(): Promise<string>;
    }
    
    export default TurboModuleRegistry.get<Spec>(
     'RTNCalculator',
    ) as Spec | null;
    

使用TurBoModule

  1. RN74NewArch根目录下执行下面命令。安装RTNCalculator TurboModule

    yarn add ../RTNCalculator 
    
    
  2. 切换到RN74NewArch/android 目录下安装执行下面命令,通过codegen生成原生代码

    ./gradlew generateCodegenArtifactsFromSchema
    
  3. 导入使用

     import React, { useState } from 'react';
     import {
       Button,
       Image
     } from 'react-native';
     import RTNCalculator from 'rtn-calculator/js/NativeCalculator';
    
    
     function App(): React.JSX.Element {
       const [i , setI] = useState()
    
       const handlePress = async () =>{
         const ret = await RTNCalculator?.onCamera()
         console.log("......",ret)
         setI(ret)
       }
       return (
         <>
         <Image source={{ uri: i }} style={{ width: 100, height: 100}}/>
           <Button title='点我选择图片' onPress={handlePress}></Button>
         </>
       );
     }
    
    
     export default App;
    
    
    
  4. 运行项目

    npm run android
    
  5. 运行效果图

    MuMu12-20240708-155718.png

    MuMu12-20240708-155749.png

    MuMu12-20240708-155758.png

    MuMu12-20240708-155805.png