Android应用如何在非前台状态下每小时播放提示音?
解决方案:实现Android后台每小时播放提示音
现有代码的核心问题
- Doze模式限制:
setInexactRepeating在设备进入Doze模式时会被系统推迟执行,导致休眠时无提示音;RTC_WAKEUP仅唤醒CPU瞬间,未保持唤醒状态完成音频播放。 - 上下文引用错误:
playRingtone中依赖MainActivity.mainActivity,当Activity被销毁时会触发空指针,且存在内存泄漏风险。 - 唤醒缺失:未持有唤醒锁,CPU可能在音频播放前重新进入休眠。
- 权限不全:缺少
WAKE_LOCK(保持CPU唤醒)和Android 13+的POST_NOTIFICATIONS权限。
修正步骤
1. 补充权限配置
在AndroidManifest.xml中添加必要权限:
<uses-permission android:name="android.permission.SCHEDULE_EXACT_ALARM" /> <uses-permission android:name="android.permission.WAKE_LOCK" /> <!-- Android 13+ 需添加通知权限 --> <uses-permission android:name="android.permission.POST_NOTIFICATIONS" /> <receiver android:name="com.example.myapp.AlarmReceiver" />
2. 优化闹钟调度逻辑
使用setExactAndAllowWhileIdle替代setInexactRepeating,确保Doze模式下也能触发;每次触发后重新设置下一次闹钟(适配Android 12+对重复闹钟的限制):
private void setAlarm() { AlarmManager alarmManager = (AlarmManager) getSystemService(Context.ALARM_SERVICE); Intent intent = new Intent(this, AlarmReceiver.class); PendingIntent pendingIntent = PendingIntent.getBroadcast( this, 0, intent, PendingIntent.FLAG_IMMUTABLE | PendingIntent.FLAG_UPDATE_CURRENT); // 设置下一次触发时间(当前时间+1小时) long nextTriggerTime = System.currentTimeMillis() + 3600000L; if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { // Android 12+ 优先使用精确且允许Doze下执行的API if (alarmManager.canScheduleExactAlarms()) { alarmManager.setExactAndAllowWhileIdle( AlarmManager.RTC_WAKEUP, nextTriggerTime, pendingIntent ); } } else { alarmManager.setExact( AlarmManager.RTC_WAKEUP, nextTriggerTime, pendingIntent ); } createNotificationChannel(); }
3. 修复AlarmReceiver逻辑
- 使用唤醒锁保持CPU唤醒直到音频播放完成
- 用Receiver的上下文创建MediaPlayer,避免依赖Activity引用
- 触发后自动设置下一次闹钟
- 调整通知配置,确保划掉通知不影响闹钟调度
public class AlarmReceiver extends BroadcastReceiver { private static final String WAKE_LOCK_TAG = "AlarmWakeLock"; private PowerManager.WakeLock wakeLock; @Override public void onReceive(Context context, Intent intent) { // 持有唤醒锁,最多10秒避免过度耗电 PowerManager powerManager = (PowerManager) context.getSystemService(Context.POWER_SERVICE); wakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, WAKE_LOCK_TAG); wakeLock.acquire(10000); // 播放提示音、显示通知、设置下一次闹钟 playRingtone(context); showNotification(context); setNextAlarm(context); } private void playRingtone(Context context) { try { MediaPlayer mp = MediaPlayer.create(context, R.raw.tock); mp.setOnCompletionListener(mediaPlayer -> { mediaPlayer.release(); // 播放完成后释放唤醒锁 if (wakeLock != null && wakeLock.isHeld()) { wakeLock.release(); } }); mp.start(); } catch (Exception e) { e.printStackTrace(); // 异常时立即释放锁 if (wakeLock != null && wakeLock.isHeld()) { wakeLock.release(); } } } private void showNotification(Context context) { Intent i = new Intent(context, MainActivity.class); i.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK); PendingIntent pendingIntent = PendingIntent.getActivity( context, 0, i, PendingIntent.FLAG_IMMUTABLE); NotificationCompat.Builder builder = new NotificationCompat.Builder(context, "myandroid") .setSmallIcon(R.drawable.add_checklist_item) .setContentTitle("定时提示") .setContentText("已到提示时间") .setAutoCancel(false) // 禁止自动取消通知 .setDefaults(NotificationCompat.DEFAULT_ALL) .setPriority(NotificationCompat.PRIORITY_HIGH) .setContentIntent(pendingIntent); NotificationManagerCompat notificationManagerCompat = NotificationManagerCompat.from(context); notificationManagerCompat.notify(12345, builder.build()); } private void setNextAlarm(Context context) { AlarmManager alarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE); Intent intent = new Intent(context, AlarmReceiver.class); PendingIntent pendingIntent = PendingIntent.getBroadcast( context, 0, intent, PendingIntent.FLAG_IMMUTABLE | PendingIntent.FLAG_UPDATE_CURRENT); long nextTriggerTime = System.currentTimeMillis() + 3600000L; if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { if (alarmManager.canScheduleExactAlarms()) { alarmManager.setExactAndAllowWhileIdle( AlarmManager.RTC_WAKEUP, nextTriggerTime, pendingIntent ); } } else { alarmManager.setExact( AlarmManager.RTC_WAKEUP, nextTriggerTime, pendingIntent ); } } }
4. 处理Android 12+的精确闹钟权限
在设置闹钟前,需检查并请求SCHEDULE_EXACT_ALARM权限:
private void checkExactAlarmPermission() { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { AlarmManager alarmManager = (AlarmManager) getSystemService(Context.ALARM_SERVICE); if (!alarmManager.canScheduleExactAlarms()) { Intent intent = new Intent(Settings.ACTION_REQUEST_SCHEDULE_EXACT_ALARM); startActivity(intent); } } }
省电注意事项
- 仅在音频播放期间持有唤醒锁,播放完成后立即释放
- 使用
setExactAndAllowWhileIdle而非重复闹钟API,避免系统频繁唤醒 - 不要长期持有前台服务,仅在播放瞬间保持唤醒
内容的提问来源于stack exchange,提问作者tm1701
相关产品推荐
相关产品推荐

