React Native 实现调用安卓原生方法实战

565 阅读3分钟

上一节说到了React Native 提供了与平台原生代码交互的能力,使得我们可以通过编写原生模块来扩展 React Native 的功能。本文将以实现调用toast为例,带你从头实现一个 React Native 调用 Android 原生方法的完整流程。

一.创建React Native项目

npx react-native init NativeModuleKotlinDemo
cd NativeModuleKotlinDemo

二.创建原生模块

  1. Android Studio打开 android 工程,等待项目构建成功
  2. 进入android/app/src/main/java/com/NativeModuleKotlinDemo/ 目录,创建一个名为modulespackage
  3. 创建ToastModule.kt
package com.nativemodulekotlindemo.modules

import android.widget.Toast
import com.facebook.react.bridge.ReactApplicationContext
import com.facebook.react.bridge.ReactContextBaseJavaModule
import com.facebook.react.bridge.ReactMethod

class ToastModule(reactContext: ReactApplicationContext) : ReactContextBaseJavaModule(reactContext) {

    override fun getName(): String {
        return "ToastModule" // 模块名称,供 JS 调用
    }

    @ReactMethod
    fun showToast(message: String) {
        Toast.makeText(reactApplicationContext, message, Toast.LENGTH_LONG).show()
    }
}

getName: 返回的字符串 ToastModule 是这个模块在 JavaScript 中的名称,JavaScript 中可以通过 NativeModules.ToastModule 来调用该模块。

@ReactMethod: 注解方法,表明这个方法可以被 JavaScript 调用。 只有加了 @ReactMethod 的方法,才能暴露给 React Native 的 JavaScript 端。

三.注册原生模块

1.创建MainApplicationPackage.kt用于注册模块

package com.nativemodulekotlindemo.modules

import com.facebook.react.ReactPackage
import com.facebook.react.bridge.NativeModule
import com.facebook.react.uimanager.ViewManager
import com.facebook.react.bridge.ReactApplicationContext

class MainApplicationPackage : ReactPackage {
    override fun createNativeModules(reactContext: ReactApplicationContext): List<NativeModule> {
        return listOf(
            ToastModule(reactContext) // 注册 ToastModule
        )
    }

    override fun createViewManagers(reactContext: ReactApplicationContext): List<ViewManager<*, *>> {
        return emptyList() // 如果没有自定义视图,返回空列表
    }
}

createNativeModules

用途:定义该包中包含的原生模块。

参数:reactContext 是 ReactApplicationContext,提供应用的上下文环境。

返回值:返回一个包含所有原生模块的列表(List)。

createViewManagers

用途:定义该包中包含的视图管理器。

参数:reactContext 提供应用的上下文环境。

返回值:返回一个包含所有视图管理器的列表(List<ViewManager<*, *>>)。

2.在MainApplication.kt中把包添加进去

package com.nativemodulekotlindemo

import android.app.Application
import com.facebook.react.PackageList
import com.facebook.react.ReactApplication
import com.facebook.react.ReactHost
import com.facebook.react.ReactNativeHost
import com.facebook.react.ReactPackage
import com.facebook.react.defaults.DefaultNewArchitectureEntryPoint.load
import com.facebook.react.defaults.DefaultReactHost.getDefaultReactHost
import com.facebook.react.defaults.DefaultReactNativeHost
import com.facebook.react.shell.MainReactPackage
import com.facebook.react.soloader.OpenSourceMergedSoMapping
import com.facebook.soloader.SoLoader
import com.nativemodulekotlindemo.modules.MainApplicationPackage
import java.util.Arrays


class MainApplication : Application(), ReactApplication {


  override val reactNativeHost: ReactNativeHost =
      object : DefaultReactNativeHost(this) {
        override fun getPackages(): List<ReactPackage> =
            PackageList(this).packages.apply {
              // Packages that cannot be autolinked yet can be added manually here, for example:
               add(MainApplicationPackage())  // 在这里添加手动注册包
            }

        override fun getJSMainModuleName(): String = "index"

        override fun getUseDeveloperSupport(): Boolean = BuildConfig.DEBUG

        override val isNewArchEnabled: Boolean = BuildConfig.IS_NEW_ARCHITECTURE_ENABLED
        override val isHermesEnabled: Boolean = BuildConfig.IS_HERMES_ENABLED
      }

  override val reactHost: ReactHost
    get() = getDefaultReactHost(applicationContext, reactNativeHost)

  override fun onCreate() {
    super.onCreate()
    SoLoader.init(this, OpenSourceMergedSoMapping)
    if (BuildConfig.IS_NEW_ARCHITECTURE_ENABLED) {
      // If you opted-in for the New Architecture, we load the native entry point for this app.
      load()
    }
  }
}

四. 在RN中调用暴露出来的方法

import React from 'react';
import {
  Button,
  SafeAreaView,
} from 'react-native';
import { NativeModules } from 'react-native';

const { ToastModule} = NativeModules;

const App = (): React.JSX.Element => {
  const test = () => {
    // 调用原生模块方法
    ToastModule.showToast('Hello from React Native!');
  };


  return (
    <SafeAreaView>
      <Button title="调toast" onPress={test} /> 
    </SafeAreaView>
  );
};

export default App;

这时我们就发现,已经调用成功了!

image.png

五:案例:RN获取设备的信息

1.编写原生模块代码

package com.nativemodulekotlindemo.modules

import android.os.Build
import com.facebook.react.bridge.ReactApplicationContext
import com.facebook.react.bridge.ReactContextBaseJavaModule
import com.facebook.react.bridge.ReactMethod
import com.facebook.react.bridge.Promise

class DeviceInfoModule(reactContext: ReactApplicationContext) : ReactContextBaseJavaModule(reactContext) {

    override fun getName(): String {
        return "DeviceInfoModule" // 模块名称,供 JS 调用
    }

    @ReactMethod
    fun getDeviceName(promise: Promise) {
        try {
            val deviceName = Build.MODEL ?: "Unknown"
            promise.resolve(deviceName)
        } catch (e: Exception) {
            promise.reject("ERROR", e.message)
        }
    }

    @ReactMethod
    fun getDeviceOS(promise: Promise) {
        try {
            val osVersion = Build.VERSION.RELEASE ?: "Unknown"
            promise.resolve(osVersion)
        } catch (e: Exception) {
            promise.reject("ERROR", e.message)
        }
    }

    @ReactMethod
    fun getManufacturer(promise: Promise) {
        try {
            val manufacturer = Build.MANUFACTURER ?: "Unknown"
            promise.resolve(manufacturer)
        } catch (e: Exception) {
            promise.reject("ERROR", e.message)
        }
    }
}

2.到MainApplicationPackage.kt注册

package com.nativemodulekotlindemo.modules

import com.facebook.react.ReactPackage
import com.facebook.react.bridge.NativeModule
import com.facebook.react.uimanager.ViewManager
import com.facebook.react.bridge.ReactApplicationContext

class MainApplicationPackage : ReactPackage {
    override fun createNativeModules(reactContext: ReactApplicationContext): List<NativeModule> {
        return listOf(
            ToastModule(reactContext), //注册Toast
            DeviceInfoModule(reactContext) //注册设备信息模块
        )
    }

    override fun createViewManagers(reactContext: ReactApplicationContext): List<ViewManager<*, *>> {
        return emptyList() // 如果没有自定义视图,返回空列表
    }
}

3.在RN调用

import React from 'react';
import {
  Button,
  SafeAreaView,
} from 'react-native';
import { NativeModules } from 'react-native';

const { ToastModule,DeviceInfoModule} = NativeModules;

const App = (): React.JSX.Element => {
  const requireToast = () => {
    // 调用原生模块方法
    ToastModule.showToast('Hello from React Native!');
  };
  const requireDeviceInfo = async() => {
    // 调用原生模块方法
  const deviceName = await DeviceInfoModule.getDeviceName();
  const deviceOS = await DeviceInfoModule.getDeviceOS();
  const manufacturer = await DeviceInfoModule.getManufacturer();
  console.log('deviceName',deviceName);
  console.log('deviceOS',deviceOS);
  console.log('manufacturer',manufacturer);

  };

  return (
    <SafeAreaView>
      <Button title="调toast" onPress={requireToast} /> {/* 添加 title 属性 */}
      <Button title="查询设备信息" onPress={requireDeviceInfo} /> {/* 添加 title 属性 */}
    </SafeAreaView>
  );
};

export default App;

成功打印! image.png

js去调用kotlin到代码,是不是很神奇?原理可以参考这里!juejin.cn/post/745444…