Android4.4之后休眠状态下Alarm不准时的问题

为了解决Android4.4及以上版本中休眠状态下的Alarm唤醒不准确的问题,本文介绍了如何通过修改内核代码和使用WakefulBroadcastReceiver来实现更精确的Alarm唤醒。包括使用特定的wakeup lock函数和对AlarmManagerService的代码调整。

Android4.4及之后休眠状态下Alarm不准时的问题

为了减轻功耗,延长电池使用时间,Android 4.4及之后的版本采用非精准闹钟机制,以及休眠状态下的wakeup类型的alarm不会实时唤醒设备,而会等到机器被物理唤醒时才触发alarmAndroid 6.0提供了新的api:setExactAndAllowWhileIdle()部分解决这个问题,但依然不能在休眠状态下精准唤醒。

关于alarm api 的支持与使用请参考下图:
alarm api 的支持与使用
(图片来源:https://plus.google.com/+AndroidDevelopers/posts/GdNrQciPwqo)

此外,应用层面不要使用不持有wakelockBroadcastReceiver,而要使用WakefulBroadcastReceiver

为了修复这个问题,以5.0.2版本为例,需要修改内核下的alarm-dev.c以及framework下的AlarmManagerService。

  • 内核 alarm-dev.c:其原因是使用普通的static struct wakeup_source alarm_wake_lock;,而非带有WAKE_LOCK_SUSPEND类别信息的struct wake_lock,并且需要使用带有android_前缀的wakeup lock相关函数。即:

    android_wake_lock_init
    android_wake_lock_destroy
    android_wake_lock
    android_wake_lock_timeout
    android_wake_unlock

    而非普通的wake lock操作函数:

    wake_lock_init
    wake_lock_destroy
    wake_lock
    wake_lock_timeout
    wake_unlock
  • framework AlarmManagerService.java:需要将wakeup类型的alarm特殊处理:即精准闹铃。在setImpl中添加如下代码:

    public boolean isWakeup(int type)
    {
        return (type & TYPE_NONWAKEUP_MASK) == 0;
    }
    
    void setImpl(int type, long triggerAtTime, long windowLength, long interval,
            PendingIntent operation, boolean isStandalone, WorkSource workSource,
            AlarmManager.AlarmClockInfo alarmClock) {
        if (operation == null) {
            Slog.w(TAG, "set/setRepeating ignored because there is no intent");
            return;
        }
    
        if (isWakeup(type)) {
            windowLength = AlarmManager.WINDOW_EXACT;
            isStandalone = true;
        }

    并在alarm被触发时多取几个满足条件的batch做处理:

    boolean triggerAlarmsLocked(ArrayList<Alarm> triggerList, final long nowELAPSED,
            final long nowRTC) {
        boolean hasWakeup = false;
        // batches are temporally sorted, so we need only pull from the
        // start of the list until we either empty it or hit a batch
        // that is not yet deliverable
    
        ArrayList<Alarm> repeatList = new ArrayList<Alarm>();
        ListIterator<Batch> it = mAlarmBatches.listIterator();
        while (it.hasNext()) {
            Batch batch = it.next();
            if (batch.start > nowELAPSED) {
                // Everything else is scheduled for the future
                break;
            }
    
            // We will (re)schedule some alarms now; don't let that interfere
            // with delivery of this current batch
            it.remove();
    
            final int N = batch.size();
            for (int i = 0; i < N; i++) {
                Alarm alarm = batch.get(i);
                alarm.count = 1;
                triggerList.add(alarm);
    
                // Recurring alarms may have passed several alarm intervals while the
                // phone was asleep or off, so pass a trigger count when sending them.
                if (alarm.repeatInterval > 0) {
                    // this adjustment will be zero if we're late by
                    // less than one full repeat interval
                    alarm.count += (nowELAPSED - alarm.whenElapsed) / alarm.repeatInterval;
    
                    // Also schedule its next recurrence
                    repeatList.add(alarm);
                }
    
                if (alarm.wakeup) {
                    hasWakeup = true;
                    mNextWakeup = 0;
                }
    
                // We removed an alarm clock. Let the caller recompute the next alarm clock.
                if (alarm.alarmClock != null) {
                    mNextAlarmClockMayChange = true;
                }
            }
        }
    
        if (repeatList.size() > 0) {
            for (Alarm alarm : repeatList) {
                final long delta = alarm.count * alarm.repeatInterval;
                final long nextElapsed = alarm.whenElapsed + delta;
                setImplLocked(alarm.type, alarm.when + delta, nextElapsed, alarm.windowLength,
                        maxTriggerTime(nowELAPSED, nextElapsed, alarm.repeatInterval),
                        alarm.repeatInterval, alarm.operation, alarm.windowLength == AlarmManager.WINDOW_EXACT, true,
                        alarm.workSource, alarm.alarmClock, alarm.userId);
            }
        }
    
        // This is a new alarm delivery set; bump the sequence number to indicate that
        // all apps' alarm delivery classes should be recalculated.
        mCurrentSeq++;
        calculateDeliveryPriorities(triggerList);
        Collections.sort(triggerList, mAlarmDispatchComparator);
    
        return hasWakeup;
    }
  • 应用层面使用WakefulBroadcastReceiver:

    import android.support.v4.content.WakefulBroadcastReceiver;
    
    public class AutoUpdateAlarmReceiver extends WakefulBroadcastReceiver {
    
        @Override
        public void onReceive(Context context, Intent intent) {
            // Start the service, keeping the device awake while the service is
            // launching. This is the Intent to deliver to the service.
            Intent service = new Intent(context, AutoUpdateIntentService.class);
            service.setAction(intent.getAction());
            startWakefulService(context, service);
        }
    }

    相应的IntentService如下所示:

    public class AutoUpdateIntentService extends IntentService {
        public AutoUpdateIntentService() {
            super("AutoUpdateIntentService");
        }
    
        @Override
        protected void onHandleIntent(Intent intent) {
            String action = intent.getAction();
    
            // do your work here.
            // ...
    
            // Release the wake lock provided by the WakefulBroadcastReceiver.
            AutoUpdateAlarmReceiver.completeWakefulIntent(intent);
        }
    }
<think>我们正在讨论Android闹钟的对齐设置。根据引用[1]和[2],Android系统(特别是4.4及以上)以及一些国内厂商的定制系统(如小米MIUI)为了省电,会对闹钟进行对齐处理。这意味着应用程序设置的闹钟可能会在精确的时间触发,而是被系统延迟一段时间(比如对齐到下一个唤醒窗口)。这可能导致闹钟准确,比如8点的闹钟被延迟到8点10分。引用[1]提到,应用程序可以通过`setWindow`或`setExact`方法来设置闹钟,以要求精确的时间定时。具体来说:-`setExact`:用于设置一个精确的闹钟,系统会尽量在指定的精确时间触发。-`setWindow`:允许设置一个时间窗口,系统可以在这个窗口内选择一个合适的时间(比如对齐到唤醒窗口)来触发闹钟,以节省电量。另外,引用[2]指出,国内一些手机厂商可能会强制对齐所有第三方应用的闹钟,甚至完全忽略第三方应用的闹钟。因此,调整闹钟对齐设置可能需要考虑同厂商的定制系统。###如何在Android中调整闹钟的对齐设置?####1.使用精确闹钟方法在代码中,使用`AlarmManager`的`setExact`或`setExactAndAllowWhileIdle`(需要精确唤醒且设备处于空闲状态时)方法来设置精确闹钟,避免对齐。示例代码(Kotlin):```kotlinvalalarmManager=getSystemService(Context.ALARM_SERVICE)asAlarmManagervalintent=Intent(this,MyAlarmReceiver::class.java)valpendingIntent=PendingIntent.getBroadcast(this,0,intent,PendingIntent.FLAG_UPDATE_CURRENT)//设置精确闹钟(在API19及以上使用setExact)if(Build.VERSION.SDK_INT>=Build.VERSION_CODES.KITKAT){//设置一个精确的闹钟,在指定的时间触发(这里使用当前时间加上10秒)alarmManager.setExact(AlarmManager.RTC_WAKEUP,System.currentTimeMillis()+10000,pendingIntent)}```对于需要在高版本Android(API23及以上)中在Doze模式下唤醒设备,使用`setExactAndAllowWhileIdle`:```kotlinif(Build.VERSION.SDK_INT>=Build.VERSION_CODES.M){alarmManager.setExactAndAllowWhileIdle(AlarmManager.RTC_WAKEUP,System.currentTimeMillis()+10000,pendingIntent)}```####2.使用`setWindow`方法(允许一定的时间窗口)如果需要绝对精确,但希望减少对齐带来的延迟,可以使用`setWindow`方法,并设置一个较小的窗口。这样系统会在指定的时间窗口内触发闹钟,而是任意延迟。```kotlinvalwindowLengthMillis=1000//1秒的窗口alarmManager.setWindow(AlarmManager.RTC_WAKEUP,triggerTime,windowLengthMillis,pendingIntent)```####3.针对同厂商的优化由于国内厂商可能会限制第三方应用的闹钟,我们可以尝试以下方法:-在应用设置中手动授予应用“自启动”权限、允许后台运行等(因厂商而异)。-引导用户将应用加入电池优化的白名单(忽略电池优化)。-对于需要高精度闹钟的应用(如闹钟应用),可以申请使用精确闹钟权限。注意,从Android12(API级别31)开始,使用精确闹钟需要声明`SCHEDULE_EXACT_ALARM`权限。在AndroidManifest.xml中添加权限:```xml<uses-permissionandroid:name="android.permission.SCHEDULE_EXACT_ALARM"/>```并且,在Android12及以上,还需要动态检查是否具有该权限,如果没有则需要请求用户授权。####4.避免使用`AlarmManager`的`set`方法在Android4.4及以上,`set`方法会被对齐。因此,尽量避免使用`set`方法,除非允许对齐。###注意事项-精确闹钟会消耗更多电量,因此应仅在必要时使用。-同的Android版本和同厂商的设备行为可能同,需要进行充分测试。###总结要调整闹钟的对齐设置,主要的方法是使用`setExact`或`setExactAndAllowWhileIdle`来设置精确闹钟。同时,注意处理Android12及以上版本的权限问题。对于国内厂商的设备,可能需要额外的设置来确保闹钟能够正常工作。§§相关问题§§1.如何在Android12及以上版本申请精确闹钟权限?2.如何检测当前设备是否支持精确闹钟?3.如何引导用户将应用加入电池优化白名单?4.同厂商(如小米、华为)对闹钟的限制有哪些?如何适配?</think>### Android闹钟对齐设置调整指南 在Android系统中调整闹钟对齐设置涉及系统级省电机制,以下是关键方法和注意事项: #### 1. 使用精确闹钟API(推荐方案) ```java // 设置精确闹钟(避免系统对齐) AlarmManager alarmManager = (AlarmManager) getSystemService(ALARM_SERVICE); Intent intent = new Intent(this, AlarmReceiver.class); PendingIntent pendingIntent = PendingIntent.getBroadcast(this, 0, intent, PendingIntent.FLAG_IMMUTABLE); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { // 精确到毫秒级触发 alarmManager.setExact(AlarmManager.RTC_WAKEUP, triggerTime, pendingIntent); } // Android 6.0+ 需添加设备休眠支持 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { alarmManager.setExactAndAllowWhileIdle(AlarmManager.RTC_WAKEUP, triggerTime, pendingIntent); } ``` **说明**: - `setExact()` 可绕过系统对齐机制[^1] - `setExactAndAllowWhileIdle()` 允许在Doze模式下触发[^2] - 需要声明权限:`<uses-permission android:name="android.permission.SCHEDULE_EXACT_ALARM"/>` #### 2. 应对厂商限制策略 国内厂商(如小米、华为)的特殊处理: ```xml <!-- 在AndroidManifest.xml中添加 --> <uses-permission android:name="android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS"/> ``` ```java // 检查电池优化状态 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { PowerManager pm = (PowerManager) getSystemService(POWER_SERVICE); if (!pm.isIgnoringBatteryOptimizations(getPackageName())) { // 引导用户手动关闭优化 Intent intent = new Intent(Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS); intent.setData(Uri.parse("package:" + getPackageName())); startActivity(intent); } } ``` #### 3. 兼容性处理方案 ```java // 分级兼容方案 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { // Android 12+ 需要动态权限 if (!alarmManager.canScheduleExactAlarms()) { startActivity(new Intent(Settings.ACTION_REQUEST_SCHEDULE_EXACT_ALARM)); } } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { alarmManager.setExactAndAllowWhileIdle(...); } else { alarmManager.setExact(...); } ``` #### 注意事项: 1. **精确闹钟耗电增加**:频繁使用精确闹钟会显著增加功耗[^1] 2. **厂商兼容性**:部分厂商仍可能限制第三方应用的唤醒(如华为EMUI)[^2] 3. **Android 12+权限**:需要动态申请`SCHEDULE_EXACT_ALARM`权限 4. **最佳实践**: - 非必要场景使用`setWindow()`允许系统对齐 - 重要闹钟(如医疗提醒)才用精确模式 - 在设置中提供"高精度模式"选项供用户选择 > **关键参考**:Android官方建议将`targetSdkVersion`设为14+以获得更好的兼容性[^4]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值