Android Telephony Manager来电监听数小时后失效问题求助
解决Android Telephony Manager广播监听数小时失效的思路
我之前也碰到过类似的Telephony广播失效问题,结合你的场景和代码,给你几个针对性的解决方向:
1. 改用「前台服务+动态广播注册」替代静态注册
Android 8.0+对后台广播和服务的限制非常严格,静态注册的广播很容易被系统回收,普通后台服务也会被优先查杀。把广播接收器放在前台服务里动态注册是最有效的解决方案——前台服务有系统优先级加持,只要显示必要的通知,就很难被系统杀死。
示例代码框架:
public class CallRecorderService extends Service { private TelephonyBroadcastReceiver receiver; private PowerManager.WakeLock wakeLock; @Override public void onCreate() { super.onCreate(); // 必须启动前台通知(Android强制要求) Notification notification = createForegroundNotification(); startForeground(1001, notification); // 动态注册通话广播 receiver = new TelephonyBroadcastReceiver(); IntentFilter filter = new IntentFilter(); filter.addAction(TelephonyManager.ACTION_PHONE_STATE_CHANGED); filter.addAction("android.intent.action.NEW_OUTGOING_CALL"); registerReceiver(receiver, filter); // 初始化唤醒锁(后续优化用) PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE); wakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "CallRecorder:WakeLock"); } // 自定义前台通知(简单示例) private Notification createForegroundNotification() { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { NotificationChannel channel = new NotificationChannel("call_recorder", "通话录音服务", NotificationManager.IMPORTANCE_LOW); NotificationManager manager = getSystemService(NotificationManager.class); manager.createNotificationChannel(channel); } return new NotificationCompat.Builder(this, "call_recorder") .setSmallIcon(R.drawable.ic_notification) .setContentTitle("通话录音服务运行中") .setPriority(NotificationCompat.PRIORITY_LOW) .build(); } @Override public void onDestroy() { super.onDestroy(); // 销毁时释放资源 if (receiver != null) unregisterReceiver(receiver); if (wakeLock != null && wakeLock.isHeld()) wakeLock.release(); } @Nullable @Override public IBinder onBind(Intent intent) { return null; } }
2. 优化唤醒锁的使用逻辑
你之前用唤醒锁没效果,大概率是锁的类型不对或者生命周期管理有问题:
- 必须使用
PARTIAL_WAKE_LOCK(保持CPU唤醒,屏幕可以关闭),其他类型的锁在通话场景下不适用; - 不要在广播接收器里持有锁(Receiver生命周期极短,锁还没生效就被释放了),应该在Service里,当通话进入
OFFHOOK状态时获取锁,回到IDLE状态时释放锁。
示例代码片段:
// 在Service的通话状态处理方法中调用 private void handleCallState(int state) { if (state == TelephonyManager.CALL_STATE_OFFHOOK) { // 通话开始,获取唤醒锁防止CPU休眠 if (!wakeLock.isHeld()) wakeLock.acquire(); } else if (state == TelephonyManager.CALL_STATE_IDLE) { // 通话结束,释放唤醒锁 if (wakeLock.isHeld()) wakeLock.release(); } }
3. 用WorkManager替代Timer/AlarmManager做健康检查
Timer和普通AlarmManager在Doze模式下会被系统限制,改用WorkManager来周期性检查服务状态更可靠——它会自动适配系统的省电策略,在设备退出Doze、有电量时执行任务。
比如每1小时检查一次录音服务是否在运行,如果失效就重启:
public class CheckServiceWorker extends Worker { public CheckServiceWorker(@NonNull Context context, @NonNull WorkerParameters params) { super(context, params); } @NonNull @Override public Result doWork() { boolean isServiceRunning = isMyServiceRunning(CallRecorderService.class); if (!isServiceRunning) { Intent intent = new Intent(getApplicationContext(), CallRecorderService.class); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { getApplicationContext().startForegroundService(intent); } else { getApplicationContext().startService(intent); } } return Result.success(); } private boolean isMyServiceRunning(Class<?> serviceClass) { ActivityManager manager = (ActivityManager) getApplicationContext().getSystemService(Context.ACTIVITY_SERVICE); for (ActivityManager.RunningServiceInfo service : manager.getRunningServices(Integer.MAX_VALUE)) { if (serviceClass.getName().equals(service.service.getClassName())) { return true; } } return false; } }
在应用启动时调度这个任务:
PeriodicWorkRequest checkServiceRequest = new PeriodicWorkRequest.Builder(CheckServiceWorker.class, 1, TimeUnit.HOURS) .setConstraints(new Constraints.Builder() .setRequiresBatteryNotLow(true) .build()) .build(); WorkManager.getInstance(context).enqueueUniquePeriodicWork("CheckCallRecorderService", ExistingPeriodicWorkPolicy.REPLACE, checkServiceRequest);
4. 适配厂商后台限制
国内小米、华为、OPPO等厂商有自己的后台管理机制,会主动杀死无权限的后台应用。你需要引导用户做以下设置:
- 给应用开启自启动权限;
- 在省电策略中设置为「无限制」;
- 将应用加入系统后台白名单。
这一步是刚需,很多时候代码没问题,就是厂商的限制导致服务被查杀。
5. 轻量化广播接收器逻辑
看你贴的代码,每次收到广播都初始化RoPlantPrefs和DatabaseHelper,还做数据库操作——Receiver的生命周期只有几毫秒,复杂操作很容易触发ANR,进而导致进程被系统杀死。
建议把Receiver简化成「事件转发器」,只负责把通话事件传递给Service处理,所有复杂逻辑(数据库、状态判断)都放在Service中:
@Override public void onReceive(Context context, Intent intent) { RoPlantPrefs prefs = RoPlantPrefs.getInstance(context); if (!prefs.getLogin()) return; // 转发事件到Service Intent serviceIntent = new Intent(context, CallRecorderService.class); serviceIntent.putExtras(intent.getExtras()); serviceIntent.setAction(intent.getAction()); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { context.startForegroundService(serviceIntent); } else { context.startService(serviceIntent); } }
内容的提问来源于stack exchange,提问作者Jhaman Das
相关产品推荐
相关产品推荐

