AlarmManager在多个设备上无法正常工作

93
我的应用程序使用AlarmManager,自4年前以来一直在运行。但我注意到它开始在一些设备上失效。
我非常确定代码是正确的(我正在使用WakefulBroadcastReceiver,并且对于具有Doze的设备使用setExactAndAllowWhileIdle),因为它在Nexus设备上完美地工作,但在某些制造商的设备(华为,小米等)上失败了。
例如,华为设备有一种电池管理器会杀死应用程序,当应用程序被杀死时,计划的闹钟也会被取消。因此,在华为电池管理器中将应用程序设置为“受保护”可以解决问题。
但最近我发现它在更多设备中不起作用:小米,三星(也许与新的“智能管理器”有关?)...似乎这种行为正在成为标准:杀死后台应用程序。
有人知道任何相关信息吗?有什么方法可以确保闹钟被触发吗?
编辑:这个问题是由不同制造商添加的“省电模式”引起的。 更多信息请参见:https://dontkillmyapp.com/

9
制造商将功耗归咎于应用程序,并继续推销八核处理器,而与少数核心的CPU相比,这些处理器会消耗更多电池电量。他们认为仅仅增加一个核心就能加快手机的速度吗? - FrozenFire
1
@AviLevinshtein 或许我误解了你的问题。我是在我的活动中创建闹钟。然后,当闹钟响起时,会执行广播接收器,最后执行 WakefulIntentService(来自 @commonsware 的类)。 - Sergio Viudes
2
@JFValdes 我还在寻找解决方案。AlarmManager 在原生 Android 设备上运行得非常完美。问题是制造商试图“增强” Android 功能,却破坏了 AlarmManager...制造商不应该实现自己的“电池节省器”,如果他们使用标准的 Doze 模式,那么 AlarmManager 就会完美地工作...仍在寻找解决方案... - Sergio Viudes
1
@SergioViudes 我也遇到了在小米设备上进行跟踪的同样问题。如果我将我的应用程序从电池节能限制中排除,那么通过以下设置,在4个设备中有3个可以正常工作 ---> 进入电池 -> 电源 --> 应用程序电池优化 --> 选择你的应用程序 现在选择无限制(用于后台设置),然后允许后台位置选项。 - Imran Khan Saifi
1
有一个网站可以解释如何处理不同制造商的这个问题:https://dontkillmyapp.com - Sergio Viudes
显示剩余15条评论
14个回答

24

我已经试了好几周,但没有找到解决方法。华为会在一段时间后禁用所有闹钟。如果将我的应用程序加入其电池节能保护中,也无济于事。

但是,如果我更改应用程序的包名称以包含像alarm、clock或calendar等单词,它就可以像在其他设备上一样正常运行。我不明白Google如何能够给这种垃圾认证。我认为OEM不应该以这种方式修改核心平台。我理解他们有自己的电池节能保护,可以在用户不使用时关闭应用程序,但是这也会禁用受保护应用程序的闹钟。

此外,对于精确定时闹钟,setAlarmClock()可帮助解决问题。但是,这不适用于诸如小部件更新之类的任务。

更新:在当前的华为设备上,通过包名称关键字进行的保护已不起作用,这在2017年是正确的。


和你一样,我也尝试过,但是在一些品牌的小米、OPPO、华为手机上没有解决这个问题的方法。它们有时会杀死后台进程和闹钟以节省电池。 - Andi Susilo
1
我有华为手机,将包名更改为alarm/calendar没有任何作用。唯一的方法是通过从手机管理器中添加您的应用程序到保护的应用程序列表来绕过此问题。 - Ashish Pardhiye

10

问题出在Smart Manager上。三星有一个电池管理器,有时会禁用某些应用程序在后台运行。当返回应用程序时,它会尝试“恢复”,但完全禁用该应用程序,或者每5分钟左右恢复一次(取决于三星的设置)。

这在原始版本的Android上可以工作,因为没有三星管理器。您还可以安装自定义版本的Android,其中具有启用SM的某些功能(根据ROM而定)。


我现在很烦恼,因为我没有三星设备来测试我的应用程序。我只能依靠用户的反馈来了解问题所在。你知道这个问题是由于AlarmManager在应用程序被杀死时无法工作吗?还是由于设备无法在闹钟响起时被唤醒,因为那个管理器的原因? - Sergio Viudes
@SergioViudes 最近很多公司都在实施自己的应用程序,例如LG有一个类似于三星的应用程序,也许你的手机也有一个?问题不在于闹钟,而是闹钟应用程序被推到了完全不活动的状态。智能管理器认为它只是一个无关紧要的应用程序。我注意到某些应用程序可以绕过它,也许一些应用程序被智能管理器接受了。 - S A
1
@SergioViudes 我有一台三星手机可以测试,我可以告诉你从中获取的信息不多。当智能管理器优化您的应用程序时,没有错误或任何提示,它只是停止运行,类似于强制停止。但它仍然在最近使用的应用程序列表中。 - Tim
谢谢Tim。能够在不需要从“智能”管理器中排除应用的情况下解决这个问题将是很棒的。 - Sergio Viudes
1
像小米(MIUI)、vivo和HTC这样的设备默认将许多权限设置为false,除非它是“受信任”的应用程序列表中的应用程序,而它们似乎自行确定(WhatsApp、Truecaller等默认受信任)。这正在成为程序员的噩梦。 - desidigitalnomad

4
现代Android设备大多配有一个应用程序或机制,自动尝试找出如何节省电池电量,并因此可能关闭某些第三方应用程序。这可能导致删除预定任务和作业(例如,闹钟未响起,推送通知未能工作等)。在许多情况下,这完全独立于Android的电池节省机制,对于我来说,当我检测到某些设备型号时,我无法进行更多的电池优化,我会将用户重定向至启动管理器以将我的应用程序列入白名单。
您可以在此链接中找到每个型号需要调用的intent: https://android-arsenal.com/details/1/6771

3
这可能有些晚了,但我希望它能帮助到某个人。
我曾陷入同样的问题之中很久,但现在我知道如何解决此问题。这是为那些可能遇到同样问题的人准备的。 人们一直说你必须启用AutoStart,但我成功地不使用自动启动来解决此问题。
首先,WakeFullBroadcastaReceiver现已弃用,你应该使用BroadcastReceiver。 其次,你必须使用前台服务而不是后台服务。
以下是我的示例:IntentService.class
public class NotificationService extends IntentService {


//In order to send notification when the app is close
//we use a foreground service, background service doesn't do the work.



public NotificationService() {
    super("NotificationService");
}

@Override
public void onCreate() {
    super.onCreate();

}

@Override
public int onStartCommand(@Nullable Intent intent, int flags, int startId) {
    super.onStartCommand(intent, flags, startId);

    //There is no difference in the result between start_sticky or start_not_sticky at the moment
    return START_NOT_STICKY;
}

@Override
protected void onHandleIntent(@Nullable Intent intent) {

    //TODO check if the app is in foreground or not, we can use activity lifecyclecallbacks for this

    startForegroundServiceT();
    sendNotification(intent);
    stopSelf();
}


/***
 * you have to show the notification to the user when running foreground service
 * otherwise it will throw an exception
 */
private void startForegroundServiceT(){

    if (Build.VERSION.SDK_INT >= 26) {
        String CHANNEL_ID = "my_channel_01";
        NotificationChannel channel = new NotificationChannel(CHANNEL_ID,
                "Channel human readable title",
                NotificationManager.IMPORTANCE_DEFAULT);

        ((NotificationManager) 
   getSystemService(Context.NOTIFICATION_SERVICE)).createNotificationChannel(channel);

        Notification notification = new Notification.Builder(this, CHANNEL_ID)
                .setContentTitle("")
                .setContentText("").build();

        startForeground(1, notification);
    }
}

private void sendNotification(Intent intent){

    //Send notification
    //Use notification channle for android O+
}
}

BroadcastReceiver.class 中启动前台服务。

public class AlarmReceiver extends BroadcastReceiver {


@Override
public void onReceive(Context context, Intent intent) {


    Intent service = new Intent(context, NotificationService.class);
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
        context.startForegroundService(service);
    } else {
        context.startService(service);
    }

}
}

设置闹钟的方式如下:

 public static void setAlarm(Context context, int requestCode, int hour, int minute){


    AlarmManager alarmManager =( AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
    Intent intent = new Intent(context//same activity should be used when canceling the alarm
            , AlarmReceiver.class);
    intent.setAction("android.intent.action.NOTIFY");

    //setting FLAG_CANCEL_CURRENT makes some problems. and doest allow the cancelAlarm to work properly
    PendingIntent pendingIntent = PendingIntent.getBroadcast(context, 1001, intent, 0);

    Calendar time = getTime(hour, minute);

    //set Alarm for different API levels
    if (Build.VERSION.SDK_INT >= 23){
        alarmManager.setExactAndAllowWhileIdle(AlarmManager.RTC_WAKEUP,time.getTimeInMillis(),pendingIntent);
    }
    else{
        alarmManager.set(AlarmManager.RTC_WAKEUP,time.getTimeInMillis(),pendingIntent);
    }

然后您需要在清单文件中声明接收器和前台服务。

       <receiver android:name=".AlarmReceiver"
        android:enabled="true"
        android:exported="true">
        <intent-filter>
            <action android:name="android.intent.action.NOTIFY">

            </action>
        </intent-filter>
    </receiver>
    <service
        android:name=".NotificationService"
        android:enabled="true"
        android:exported="true"></service>

我希望这能对某些人有所帮助。

但是没有人点赞吗?@keivan.k - gumuruh
1
它不起作用。setExactAndAllowWhileIdle也可以被延迟。我使用这个功能时会出现长达50分钟的延迟。请改用setAlarmClock。然而,即使是在华为设备上,这个功能也存在问题,我已经没有更多的选择了... - Євген Гарастович

2

我还有一个设置闹钟的应用程序。解决方案是在api >= 21上使用AlarmManager.setAlarmClock()。据我所知,这不受doze影响,并且还额外添加了一个闹钟图标到系统托盘中。


谢谢你的回答。有没有办法去掉闹钟图标? - Sergio Viudes
1
很不幸,setAlarmClock有时候会出现问题。我在内存较低的奥利奥设备上进行了测试。 - Boris Salimov
有人找到了华为设备上无法工作的解决方案吗? - Євген Гарастович
请尝试使用 WorkManager。 - Tyler Pfaff

2
对于Android 5.0以下的设备,请使用AlarmManager,对于Android 5.0及以上的设备,请使用JobScheduler。我不能确定制造商的恶作剧是否会影响JobScheduler,但在我看来这种可能性要小得多,因为Android正在努力将人们从AlarmManager转移到JobScheduler。

编辑:Google推出了一个名为WorkManager的第一方解决方案来解决这个问题。它抽象了多个调度框架,并使用最适合设备的框架。


4
很遗憾,与AlarmManager类不同,使用JobScheduler时时间并不是精确的。在我的应用程序中,时间应该是精确的 :( - Sergio Viudes
我尝试了一下,发现一些优化程序(至少三星)在屏幕关闭时会杀死JobScheduler中的所有挂起任务。所以它也是有问题的。 这种情况发生在5.0上。更新到6.0后,它就正常工作了,我想他们修复了这个问题。 我还没有测试其他制造商的设备。 - Sloy
1
对于精确的定时,您不能使用后台服务或计划服务。您可以尝试前台服务,但这将为用户创建一个持久通知(可能不受欢迎),而且一些手机内置了任务管理器,会自动销毁前台服务。WorkManager是最好的解决方案,但不幸的是它无法提供精确的定时。 - Tom

0

现在大多数新手机都附带了某种电池/节能管理器,它们执行您所描述的相同操作。不包括duboosters和clean masters。

我认为您需要在应用程序/Play商店列表中放置免责声明或常见问题解答,说明此应用程序需要被排除在您的电池管理器应用程序之外才能正常工作。


应该有另一种方法来做这件事...用户不会阅读免责声明。我无法想象三星手机不允许应用程序使用AlarmManager... - Sergio Viudes
警报不会“准时”触发,但它们最终会触发。 - Gavriel
这是(可悲地)最有帮助的答案,我想说。我希望有更好的解决方案,但硬件制造商正在破坏完全正常工作的原生安卓系统。 - caw

0

我们需要在应用程序管理器中启用自启动管理器,例如vivo v5等一些手机。

在vivo v5中,我们可以在iManager->应用程序管理器->自启动管理器中找到此菜单。在这里启用我们的应用程序。

这样,即使应用程序被杀死或关闭,您的闹钟/闹钟管理器也会触发闹钟。


0

我认为杀掉应用程序并不会阻止闹钟管理器唤醒您的应用程序。

只有当您“强制停止”或禁用该应用程序时,才不会从闹钟管理器接收回调。

根本原因可能是其他问题。

在M上使用 setExactAndAllowWhileIdle 会进行限流……也就是说,如果您每2分钟安排一个闹钟,则不会触发它。需要有15分钟的窗口期。


1
谢谢您的回答。但是,如果不是这个原因,为什么当在智能管理器中禁用“电池优化”时,应用程序可以完美地工作呢? - Sergio Viudes
你是否在root设备上运行应用程序?如果是,应用程序管理器也可以禁用该应用程序。 - rupesh jain
不,我没有在已经取得 root 权限的设备上运行它。 - Sergio Viudes
@rupeshjain “如果您每2分钟安排一次闹钟,它不会被触发。..需要有15分钟的窗口期。”这并不完全正确,如果是真的话,这将是一个真正的问题。您可以在Android文档中精确阅读setExactAndAllowWhileIdle方法的调度时间限制。对于特定应用程序,这些警报的触发频率受到限制。在正常系统操作下,当处于低功耗空闲模式时,它不会分派这些警报超过每分钟一次,此持续时间可能会显着长达15分钟。 - eyadMhanna

0

我一段时间前停止使用AlarmManager了...有一个更好、更稳定的替代方案

  1. 创建一个服务
  2. 为BOOT_COMPLETED注册一个BroadcastReceiver
  3. 从接收器中启动您的服务
  4. 在您的服务内部启动一个新的Handler,每X分钟循环一次(Android - 使用postDelayed()调用定期运行方法
  5. 检查是否到达执行任务的时间:现在-执行时间>0(如何在Java中找到两个日期之间的差异持续时间?
  6. 如果是这样...执行任务并停止处理程序

是的..这很麻烦..但工作总能完成


4
谢谢您的建议,但我希望避免采用那种方式,因为使用AlarmManager不会消耗RAM或其他资源。而且,如果您的应用程序被杀死,服务将停止,对吧? - Sergio Viudes
我并没有说这种方法是十分牢靠的,但它至少能够在不同的API版本中保持一致 :) - ymz
4
为了保证这个解决方案的可靠性,可能还需要使用唤醒锁,但那会大量消耗电池。 - Paweł Nadolski
我猜你这一点说得对。唯一的问题是:哪个更糟糕 - 不可靠的代码还是性能差?不管怎样,我个人认为在某些情况下有替代的加锁方式可能更适用(例如:https://dev59.com/am435IYBdhLWcg3wjw3S)。 - ymz
但是,主要的机制 - 广播接收器 - 没有被称为 \。 - Noor Hossain

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接