Widget点击时Native向React Native传实时数据报错求助
问题解决与实现步骤
报错原因分析
java.lang.IllegalStateException 报错的核心原因是:Android AppWidget(你的SingleCounterWidget)运行在独立于React Native主应用的进程中(通常是系统的AppWidgetHost进程或应用的单独进程),直接在Widget的点击事件中调用ReactContext#getJSModule时,React实例要么未初始化,要么根本不在当前进程中,导致无法访问JS模块。
解决方案:跨进程通信 + RN事件转发
我们需要通过SharedPreferences存储数据 + 本地广播通知RN进程,再由RN侧的Native Module监听广播并将数据转发给JS层,实现实时数据传递。
步骤1:修改Widget点击逻辑(SingleCounterWidget.kt)
点击Widget按钮时,只更新SharedPreferences并发送本地广播,不要直接操作RN的JS模块:
import android.app.PendingIntent import android.appwidget.AppWidgetManager import android.appwidget.AppWidgetProvider import android.content.Context import android.content.Intent import android.widget.RemoteViews class SingleCounterWidget : AppWidgetProvider() { companion object { const val ACTION_INCREMENT = "com.yourpackage.ACTION_INCREMENT" const val BROADCAST_RN_UPDATE = "com.yourpackage.BROADCAST_RN_UPDATE" } override fun onUpdate( context: Context, appWidgetManager: AppWidgetManager, appWidgetIds: IntArray ) { appWidgetIds.forEach { appWidgetId -> val views = RemoteViews(context.packageName, R.layout.single_counter_widget) // 点击按钮的PendingIntent,触发自增逻辑 val intent = Intent(context, SingleCounterWidget::class.java).apply { action = ACTION_INCREMENT putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId) } val pendingIntent = PendingIntent.getBroadcast( context, appWidgetId, intent, PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE ) views.setOnClickPendingIntent(R.id.btn_increment, pendingIntent) appWidgetManager.updateAppWidget(appWidgetId, views) } } override fun onReceive(context: Context, intent: Intent) { super.onReceive(context, intent) if (intent.action == ACTION_INCREMENT) { // 1. 更新SharedPreferences数据 val prefs = MySharedPreferencesModule.getSharedPreferences(context) val currentCount = prefs.getInt("counter", 0) prefs.edit().putInt("counter", currentCount + 1).apply() // 2. 发送本地广播,通知RN进程数据更新 val broadcastIntent = Intent(BROADCAST_RN_UPDATE) context.sendBroadcast(broadcastIntent) } } }
步骤2:修改Native Module(MySharedPreferencesModule.kt)
添加广播监听,并用EventEmitter将数据转发给JS层,确保只在React实例初始化完成后操作:
import android.content.BroadcastReceiver import android.content.Context import android.content.Intent import android.content.IntentFilter import com.facebook.react.bridge.* import com.facebook.react.modules.core.DeviceEventManagerModule class MySharedPreferencesModule(reactContext: ReactApplicationContext) : ReactContextBaseJavaModule(reactContext) { private lateinit var broadcastReceiver: BroadcastReceiver private val EVENT_NAME = "CounterUpdateEvent" companion object { fun getSharedPreferences(context: Context) = context.getSharedPreferences("CounterPrefs", Context.MODE_PRIVATE) } override fun getName() = "MySharedPreferencesModule" override fun initialize() { super.initialize() // 只有在initialize()之后(React实例就绪)才注册广播监听 broadcastReceiver = object : BroadcastReceiver() { override fun onReceive(context: Context?, intent: Intent?) { if (intent?.action == SingleCounterWidget.BROADCAST_RN_UPDATE) { // 获取最新数据 val currentCount = getSharedPreferences(reactApplicationContext) .getInt("counter", 0) // 发送事件给JS层 sendEvent(reactApplicationContext, EVENT_NAME, currentCount) } } } val filter = IntentFilter(SingleCounterWidget.BROADCAST_RN_UPDATE) reactApplicationContext.registerReceiver(broadcastReceiver, filter) } override fun onCatalystInstanceDestroy() { super.onCatalystInstanceDestroy() // 销毁时注销广播,避免内存泄漏 reactApplicationContext.unregisterReceiver(broadcastReceiver) } private fun sendEvent(reactContext: ReactApplicationContext, eventName: String, data: Int) { reactContext .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter::class.java) .emit(eventName, data) } // 提供JS调用的方法,获取初始计数 @ReactMethod fun getCurrentCount(promise: Promise) { val count = getSharedPreferences(reactApplicationContext).getInt("counter", 0) promise.resolve(count) } } // 注册Module class MySharedPreferencesPackage : ReactPackage { override fun createNativeModules(reactContext: ReactApplicationContext): List<NativeModule> { return listOf(MySharedPreferencesModule(reactContext)) } override fun createViewManagers(reactContext: ReactApplicationContext): List<ViewManager<*, *>> { return emptyList() } }
步骤3:React Native侧监听事件(WeeklyCounter.jsx)
在JS层监听Native Module发送的事件,实时更新UI:
import React, { useState, useEffect } from 'react'; import { View, Text, NativeModules, DeviceEventEmitter } from 'react-native'; const { MySharedPreferencesModule } = NativeModules; const WeeklyCounter = () => { const [counter, setCounter] = useState(0); useEffect(() => { // 初始获取计数 const fetchInitialCount = async () => { const initialCount = await MySharedPreferencesModule.getCurrentCount(); setCounter(initialCount); }; fetchInitialCount(); // 监听Native发送的更新事件 const subscription = DeviceEventEmitter.addListener( 'CounterUpdateEvent', (newCount) => { setCounter(newCount); } ); // 清理监听 return () => { subscription.remove(); }; }, []); return ( <View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}> <Text>当前计数:{counter}</Text> </View> ); }; export default WeeklyCounter;
关键注意事项
- 确保Widget的广播action和Native Module的监听action完全一致,避免拼写错误。
initialize()方法是Native Module中React实例就绪后才会调用的方法,所有依赖ReactContext的操作必须放在这里面,这是解决原报错的核心。- 广播注册后必须在
onCatalystInstanceDestroy()中注销,防止内存泄漏。 - PendingIntent要添加
FLAG_IMMUTABLE(Android 12+要求),避免兼容性问题。
内容的提问来源于stack exchange,提问作者Salman Hossein
相关产品推荐
相关产品推荐

