上一节说到了React Native 提供了与平台原生代码交互的能力,使得我们可以通过编写原生模块来扩展 React Native 的功能。本文将以实现调用toast为例,带你从头实现一个 React Native 调用 Android 原生方法的完整流程。
一.创建React Native项目
npx react-native init NativeModuleKotlinDemo
cd NativeModuleKotlinDemo
二.创建原生模块
- 用
Android Studio打开android工程,等待项目构建成功 - 进入
android/app/src/main/java/com/NativeModuleKotlinDemo/目录,创建一个名为modules的package - 创建
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;
这时我们就发现,已经调用成功了!
五:案例: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;
成功打印!
js去调用kotlin到代码,是不是很神奇?原理可以参考这里!juejin.cn/post/745444…