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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.12 10:05:23