进阶 reactnative.dev 2026-10-11 06:04:21 · 21 阅读

第37章 Fabric 原生模块:Android

版本:0.77 Fabric 原生模块:Android 现在该写一些 Android 平台代码来渲染 web view 了。你需要按以下步骤操作: 运行 Codegen 编写 ReactWebView 代码 编写 ReactWebViewManager 代码 编写 ReactWebViewPackage 代码 在应用中注册 ReactWebViewPackage 1. 通过 Gradle 运行 Codegen​ 先运行一次,生成你所用 IDE 需要的样板代码。 Demo/cd android
./gradlew generateCodegenArtifactsFromSchema
Codegen 会生成你需要实现的 ViewManager 接口,以及 web view 对应的 ViewManager delegate。 2. 编写 ReactWebView​ ReactWebView 是一个封装了 Android 原生视图的组件,React Native 在使用我们的自定义组件时会渲染它。 在 android/src/main/java/com/webview 目录下创建 ReactWebView.java 或 ReactWebView.kt 文件,代码如下: JavaKotlinDemo/android/src/main/java/com/webview/ReactWebView.javapackage com.webview;

import android.content.Context;
import android.util.AttributeSet;
import android.webkit.WebView;
import android.webkit.WebViewClient;

import com.facebook.react.bridge.Arguments;
import com.facebook.react.bridge.WritableMap;
import com.facebook.react.bridge.ReactContext;
import com.facebook.react.uimanager.UIManagerHelper;
import com.facebook.react.uimanager.events.Event;

public class ReactWebView extends WebView {
public ReactWebView(Context context) {
super(context);
configureComponent();
}

public ReactWebView(Context context, AttributeSet attrs) {
super(context, attrs);
configureComponent();
}

public ReactWebView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
configureComponent();
}

private void configureComponent() {
this.setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
this.setWebViewClient(new WebViewClient() {
@Override
public void onPageFinished(WebView view, String url) {
emitOnScriptLoaded(OnScriptLoadedEventResult.success);
}
});
}

public void emitOnScriptLoaded(OnScriptLoadedEventResult result) {
ReactContext reactContext = (ReactContext) context;
int surfaceId = UIManagerHelper.getSurfaceId(reactContext);
该代码片段展示了 `ReactWebView` 组件如何触发 `OnScriptLoaded` 事件。在 Java 部分,它通过 `UIManagerHelper` 获取事件分发器,创建一个包含结果的 `WritableMap` 负载,并实例化 `OnScriptLoadedEvent`。如果分发器可用,则派发该事件。`OnScriptLoadedEvent` 是一个继承自 `Event` 的内部类,它封装了 `surfaceId`、`viewId` 和 payload,并重写了 `getEventName` 以返回事件名称字符串。同时,代码中还定义了 `OnScriptLoadedEventResult` 枚举,包含 `success` 和 `error` 两种状态。 紧随其后的 Kotlin 文件 `ReactWebView.kt` 定义了 `ReactWebView` 类,它继承自 `WebView`。该类提供了多个构造函数,每个都在初始化时调用 `configureComponent` 方法。`configureComponent` 设置布局参数并配置 `WebViewClient`,在 `onPageFinished` 回调中发射 `success` 状态的事件。`emitOnScriptLoaded` 方法负责构建事件对象并将其分发给 React Native 框架。 EventDispatcher eventDispatcher = UIManagerHelper.getEventDispatcherForReactTag(reactContext, getId());
WritableMap payload = Arguments.createMap();
payload.putString("result", result.name());

OnScriptLoadedEvent event = new OnScriptLoadedEvent(surfaceId, getId(), payload);
if (eventDispatcher != null) {
eventDispatcher.dispatchEvent(event);
}
}

public enum OnScriptLoadedEventResult {
success,
error
}

private class OnScriptLoadedEvent extends Event {
private final WritableMap payload;

OnScriptLoadedEvent(int surfaceId, int viewId, WritableMap payload) {
super(surfaceId, viewId);
this.payload = payload;
}

@Override
public String getEventName() {
return "onScriptLoaded";
}

@Override
public WritableMap getEventData() {
return payload;
}
}
}
Demo/android/src/main/java/com/webview/ReactWebView.ktpackage com.webview

import android.content.Context
import android.util.AttributeSet
import android.webkit.WebView
import android.webkit.WebViewClient
import com.facebook.react.bridge.Arguments
import com.facebook.react.bridge.WritableMap
import com.facebook.react.bridge.ReactContext
import com.facebook.react.uimanager.UIManagerHelper
import com.facebook.react.uimanager.events.Event

class ReactWebView: WebView {
constructor(context: Context) : super(context) {
configureComponent()
}

constructor(context: Context, attrs: AttributeSet?) : super(context, attrs) {
configureComponent()
}

constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr) {
configureComponent()
}

private fun configureComponent() {
this.layoutParams = LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT)
this.webViewClient = object : WebViewClient() {
override fun onPageFinished(view: WebView, url: String) {
emitOnScriptLoaded(OnScriptLoadedEventResult.success)
}
}
}

fun emitOnScriptLoaded(result: OnScriptLoadedEventResult) {
val reactContext = context as ReactContext
val surfaceId = UIManagerHelper.getSurfaceId(reactContext)
val eventDispatcher = UIManagerHelper.getEventDispatcherForReactTag(reactContext, id)
val payload =
Arguments.createMap().apply {
putString("result", result.name)
}
val event = OnScriptLoadedEvent(surfaceId, id, payload)

eventDispatcher?.dispatchEvent(event)
}

enum class OnScriptLoadedEventResult {
success,
error;
}

inner class OnScriptLoadedEvent(
surfaceId: Int,
viewId: Int,
private val payload: WritableMap
) : Event(surfaceId, viewId) {
override fun getEventName() = "onScriptLoaded"

override fun getEventData() = payload
}
}
ReactWebView 继承自 Android WebView,因此你可以轻松复用平台已定义的所有属性。 该类定义了三个 Android 构造方法,但将具体实现委托给私有的 configureComponent 函数。这个函数负责初始化所有组件特有属性:在此场景中,你设置 WebView 的布局,并定义用于定制 WebView 行为的 WebClient。在这段代码中,ReactWebView 通过实现 WebClient 的 onPageFinished 方法,在页面加载完成时发出一个事件。 随后,代码定义了一个辅助函数来实际发出事件。要发出事件,你需要: 获取 ReactContext 引用; 获取当前显示视图的 surfaceId; 获取与该视图关联的 eventDispatcher 引用; 使用 WritableMap 对象构建事件负载(payload); 创建需要发送给 JavaScript 的事件对象; 调用 eventDispatcher.dispatchEvent 发送事件。 文件的最后一部分包含了发送事件所需的数据类型定义: OnScriptLoadedEventResult,包含 OnScriptLoaded 事件可能的结果; 实际的 OnScriptLoadedEvent,需要继承 React Native 的 Event 类。 3. 编写 WebViewManager WebViewManager 是连接 React Native 运行时与原生视图的类。 当 React 收到应用渲染特定组件的指令时,React 会使用已注册视图管理器(view manager)来创建视图并传递所有必要属性。 以下是 ReactWebViewManager 的代码。 JavaKotlinDemo/android/src/main/java/com/webview/ReactWebViewManager.javapackage com.webview;

import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.module.annotations.ReactModule;
import com.facebook.react.uimanager.SimpleViewManager;
import com.facebook.react.uimanager.ThemedReactContext;
import com.facebook.react.uimanager.ViewManagerDelegate;
import com.facebook.react.uimanager.annotations.ReactProp;
import com.facebook.react.viewmanagers.CustomWebViewManagerInterface;
import com.facebook.react.viewmanagers.CustomWebViewManagerDelegate;

import java.util.HashMap;
import java.util.Map;

@ReactModule(name = ReactWebViewManager.REACT_CLASS)
class ReactWebViewManager extends SimpleViewManager implements CustomWebViewManagerInterface {
private final CustomWebViewManagerDelegate delegate =
new CustomWebViewManagerDelegate<>(this);

@Override
public ViewManagerDelegate getDelegate() {
return delegate;
}

@Override
public String getName() {
return REACT_CLASS;
}

@Override
public ReactWebView createViewInstance(ThemedReactContext context) {
return new ReactWebView(context);
}

@ReactProp(name = "sourceUrl")
@Override
public void setSourceURL(ReactWebView view, String sourceURL) {
if (sourceURL == null) {
view.emitOnScriptLoaded(ReactWebView.OnScriptLoadedEventResult.error);
return;
}
view.loadUrl(sourceURL, new HashMap<>());
}

public static final String REACT_CLASS = "CustomWebView";

@Override
public Map getExportedCustomBubblingEventTypeConstants() {
Map map = new HashMap<>();
Map bubblingMap = new HashMap<>();
bubblingMap.put("phasedRegistrationNames", new HashMap() {{
put("bubbled", "onScriptLoaded");
put("captured", "onScriptLoadedCapture");
}});
map.put("onScriptLoaded", bubblingMap);
return map;
}
}
Demo/android/src/main/java/com/webview/ReactWebViewManager.ktpackage com.webview

import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.module.annotations.ReactModule;
import com.facebook.react.uimanager.SimpleViewManager;
import com.facebook.react.uimanager.ThemedReactContext;
import com.facebook.react.uimanager.ViewManagerDelegate;
import com.facebook.react.uimanager.annotations.ReactProp;
import com.facebook.react.viewmanagers.CustomWebViewManagerInterface;
import com.facebook.react.viewmanagers.CustomWebViewManagerDelegate;

@ReactModule(name = ReactWebViewManager.REACT_CLASS)
class ReactWebViewManager(context: ReactApplicationContext) : SimpleViewManager(), CustomWebViewManagerInterface {
private val delegate: CustomWebViewManagerDelegate =
CustomWebViewManagerDelegate(this)

override fun getDelegate(): ViewManagerDelegate = delegate

override fun getName(): String = REACT_CLASS

override fun createViewInstance(context: ThemedReactContext): ReactWebView = ReactWebView(context)

@ReactProp(name = "sourceUrl")
override fun setSourceURL(view: ReactWebView, sourceURL: String?) {
if (sourceURL == null) {
view.emitOnScriptLoaded(ReactWebView.OnScriptLoadedEventResult.error)
return;
}
view.loadUrl(sourceURL, emptyMap())
}

companion object {
const val REACT_CLASS = "CustomWebView"
}

override fun getExportedCustomBubblingEventTypeConstants(): Map =
mapOf(
"onScriptLoaded" to
mapOf(
"phasedRegistrationNames" to
mapOf(
"bubbled" to "onScriptLoaded",
"captured" to "onScriptLoadedCapture"
)))
} ReactWebViewManager 继承自 React 的 SimpleViewManager 类,并实现了由 Codegen 生成的 CustomWebViewManagerInterface。 它持有一个 CustomWebViewManagerDelegate 的引用,该 Delegate 同样由 Codegen 生成。 随后,它重写了 getName 函数,该函数必须返回与 spec 中 codegenNativeComponent 函数调用所使用的名称一致的字符串。 createViewInstance 函数负责实例化一个新的 ReactWebView。 接着,ViewManager 需要定义所有 React 组件的 props 将如何更新原生视图。在示例中,你需要决定如何处理 React 设置到 WebView 上的 sourceURL 属性。 最后,如果组件可以发出事件,你需要通过重写 getExportedCustomBubblingEventTypeConstants 来映射冒泡事件的事件名称,或者通过重写 getExportedCustomDirectEventTypeConstants 来映射直接事件。 4. 编写 ReactWebViewPackage​ 与 Native Modules 一样,Native Components 也需要实现 ReactPackage 类。这是一个用于在 React Native 运行时中注册组件的对象。 以下是 ReactWebViewPackage 的代码: JavaKotlinDemo/android/src/main/java/com/webview/ReactWebViewPackage.javapackage com.webview;

import com.facebook.react.BaseReactPackage;
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.uimanager.ViewManager;

import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class ReactWebViewPackage extends BaseReactPackage {
@Override
public List> createViewManagers(ReactApplicationContext reactContext) {
return Collections.singletonList(new ReactWebViewManager(reactContext));
}

@Override
public NativeModule getModule(String s, ReactApplicationContext reactApplicationContext) {
if (ReactWebViewManager.REACT_CLASS.equals(s)) {
return new ReactWebViewManager(reactApplicationContext);
}
return null;
}

@Override
public ReactModuleInfoProvider getReactModuleInfoProvider() {
return new ReactModuleInfoProvider() {
@Override
public Map getReactModuleInfos() {
Map map = new HashMap<>();
map.put(ReactWebViewManager.REACT_CLASS, new ReactModuleInfo(
ReactWebViewManager.REACT_CLASS, // name
ReactWebViewManager.REACT_CLASS, // className
false, // canOverrideExistingModule
false, // needsEagerInit
false, // isCxxModule
true // isTurboModule
));
return map;
}
};
}
}
Demo/android/src/main/java/com/webview/ReactWebViewPackage.ktpackage com.webview

import com.facebook.react.BaseReactPackage
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.uimanager.ViewManager

class ReactWebViewPackage : BaseReactPackage() {
override fun createViewManagers(reactContext: ReactApplicationContext): List> {
return listOf(ReactWebViewManager(reactContext))
}

override fun getModule(s: String, reactApplicationContext: ReactApplicationContext): NativeModule? {
when (s) {
ReactWebViewManager.REACT_CLASS -> ReactWebViewManager(reactApplicationContext)
}
return null
}

override fun getReactModuleInfoProvider(): ReactModuleInfoProvider = ReactModuleInfoProvider {
mapOf(ReactWebViewManager.REACT_CLASS to ReactModuleInfo(
name = ReactWebViewManager.REACT_CLASS,
className = ReactWebViewManager.REACT_CLASS,
canOverrideExistingModule = false,
needsEagerInit = false,
isCxxModule = false,
isTurboModule = true,
)
)
}
}
ReactWebViewPackage 继承自 BaseReactPackage,实现了正确注册组件所需的全部方法。 createViewManagers 方法是工厂方法,用于创建管理自定义视图的 ViewManager。 getModule 方法根据 React Native 需要渲染的 View 类型,返回相应的 ViewManager。 getReactModuleInfoProvider 在运行时注册模块时,提供所需的所有信息。 5. 在应用程序中注册 ReactWebViewPackage 最后,需要在应用程序中注册 ReactWebViewPackage。通过修改 MainApplication 文件来实现:向 getPackages 函数返回的包列表中添加 ReactWebViewPackage。 Demo/app/src/main/java/com/demo/MainApplication.ktpackage com.demo

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.soloader.OpenSourceMergedSoMapping
import com.facebook.soloader.SoLoader
import com.webview.ReactWebViewPackage

class MainApplication : Application(), ReactApplication {

override val reactNativeHost: ReactNativeHost =
object : DefaultReactNativeHost(this) {
override fun getPackages(): List =
PackageList(this).packages.apply {
add(ReactWebViewPackage())
}

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) {
load()
}
}
}

评论 (0)