You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.19 17:29:50