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

Android通话状态变更时悬浮窗显示失败及RN集成问题排查

问题描述

我正在开发一款集成React Native的Android应用,需求是通话状态变更时显示悬浮窗并向React Native发送事件。不显示悬浮窗时事件发送正常,但尝试显示悬浮窗时触发空指针错误:

FATAL EXCEPTION: main
Process: com.arunkavale.test, PID: 19533
java.lang.RuntimeException: Unable to start receiver com.arunkavale.test.CallReceiver: java.lang.NullPointerException: Attempt to invoke virtual method 'java.lang.Object com.facebook.react.bridge.ReactApplicationContext.getSystemService(java.lang.String)' on a null object reference
    at android.app.ActivityThread.handleReceiver(ActivityThread.java:4905)
    at android.app.ActivityThread.-$$Nest$mhandleReceiver(Unknown Source:0)
    at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2498)
    at android.os.Handler.dispatchMessage(Handler.java:106)
    at android.os.Looper.loopOnce(Looper.java:230)
    at android.os.Looper.loop(Looper.java:319)
    at android.app.ActivityThread.main(ActivityThread.java:8893)
    at java.lang.reflect.Method.invoke(Native Method)
    at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:608)
    at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1103)
Caused by: java.lang.NullPointerException: Attempt to invoke virtual method 'java.lang.Object com.facebook.react.bridge.ReactApplicationContext.getSystemService(java.lang.String)' on a null object reference
    at com.arunkavale.test.CallModule.showFloatingWindow(CallModule.java:51)
    at com.arunkavale.test.CallModule.sendEvent(CallModule.java:40)
    at com.arunkavale.test.CallReceiver.sendEventToReactNative(CallReceiver.java:60)
    at com.arunkavale.test.CallReceiver.onReceive(CallReceiver.java:48)
    at android.app.ActivityThread.handleReceiver(ActivityThread.java:4896)

疑问:

  1. 如何保证显示悬浮窗时拥有有效的ReactApplicationContext?
  2. 在集成React Native的Android应用中,处理通话状态变更并显示悬浮窗有没有更优的实现方案?

解决方案

一、解决ReactApplicationContext为空的问题

1. 全局持有ReactApplicationContext(弱引用防泄漏)

在CallModule初始化时,把ReactApplicationContext存入弱引用静态变量,使用前先检查有效性:

private static WeakReference<ReactApplicationContext> sReactContextRef;

public CallModule(ReactApplicationContext reactContext) {
    super(reactContext);
    sReactContextRef = new WeakReference<>(reactContext);
}

public static void showFloatingWindow() {
    ReactApplicationContext context = sReactContextRef.get();
    if (context == null || context.isDestroyed()) {
        // 上下文失效,可启动后台服务或暂存事件后续处理
        return;
    }
    WindowManager windowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
    // 执行悬浮窗创建逻辑
}

2. 改用Application Context获取系统服务

悬浮窗只需要系统服务,可直接用Application Context替代ReactApplicationContext:
在CallReceiver的onReceive里拿全局上下文:

@Override
public void onReceive(Context context, Intent intent) {
    Context appContext = context.getApplicationContext();
    showFloatingWindow(appContext);
    // 发送RN事件时再确保ReactContext有效
    sendEventToReactNative();
}

修改showFloatingWindow方法接收外部Context:

public void showFloatingWindow(Context context) {
    WindowManager windowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
    // 悬浮窗逻辑
}

3. 避免在BroadcastReceiver中直接操作RN模块

BroadcastReceiver生命周期极短,可能在RN上下文未初始化时触发。把事件转发到后台服务处理:
在CallReceiver中启动服务:

@Override
public void onReceive(Context context, Intent intent) {
    Intent serviceIntent = new Intent(context, CallBackgroundService.class);
    serviceIntent.putExtras(intent);
    ContextCompat.startForegroundService(context, serviceIntent);
}

在CallBackgroundService中持有有效RN上下文:

public class CallBackgroundService extends Service {
    private ReactApplicationContext mReactContext;

    @Override
    public void onCreate() {
        super.onCreate();
        // 从Application获取RN上下文
        mReactContext = ((MainApplication) getApplication()).getReactNativeHost().getReactInstanceManager().getCurrentReactContext();
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        if (mReactContext != null && !mReactContext.isDestroyed()) {
            showFloatingWindow(mReactContext);
            sendEventToRN(mReactContext, "CallStatusChanged", params);
        }
        return START_STICKY;
    }
}

二、更优实现方案

1. 用后台服务监听通话状态

放弃静态BroadcastReceiver,在后台服务中注册PhoneStateListener,稳定性更高:

public class CallBackgroundService extends Service {
    private TelephonyManager mTelephonyManager;
    private PhoneStateListener mPhoneStateListener;

    @Override
    public void onCreate() {
        super.onCreate();
        mTelephonyManager = (TelephonyManager) getSystemService(TELEPHONY_SERVICE);
        mPhoneStateListener = new PhoneStateListener() {
            @Override
            public void onCallStateChanged(int state, String phoneNumber) {
                super.onCallStateChanged(state, phoneNumber);
                handleCallStateChange(state, phoneNumber);
            }
        };
        mTelephonyManager.listen(mPhoneStateListener, PhoneStateListener.LISTEN_CALL_STATE);
        // 启动为前台服务,避免被系统回收
        startForeground(1, new Notification());
    }

    private void handleCallStateChange(int state, String phoneNumber) {
        ReactApplicationContext reactContext = ((MainApplication) getApplication()).getReactNativeHost().getReactInstanceManager().getCurrentReactContext();
        if (reactContext != null) {
            showFloatingWindow(reactContext);
            sendEventToRN(reactContext, "CallStatusChanged", buildEventParams(state, phoneNumber));
        }
    }
}

在Manifest中注册服务并声明权限:

<service android:name=".CallBackgroundService" android:foregroundServiceType="phoneCall" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />

2. 悬浮窗与RN模块解耦

悬浮窗的创建、管理完全由原生层负责,不依赖RN上下文。需要和RN交互时,再通过有效ReactApplicationContext发送事件。如果RN未初始化,可暂存事件,待RN初始化完成后再发送(监听ReactInstanceManager的生命周期实现)。

3. 动态权限处理

Android 6.0+需动态申请READ_PHONE_STATE和SYSTEM_ALERT_WINDOW权限,确保功能正常运行。


内容的提问来源于stack exchange,提问作者A.K.

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.03 04:33:09