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)
疑问:
- 如何保证显示悬浮窗时拥有有效的
ReactApplicationContext? - 在集成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.

