问题 当手机不使用时,AlarmManager重复警报随机丢失


我叫一个背景 Service 以30分钟的间隔读取当前位置的纬度/经度并通过POST API将其发送到服务器。

我在用 setRepeating() 的方法 AlarmManager 每30分钟安排一次警报的课程。但有时警报会被遗漏而服务没有被调用。为了监控是否每30分钟调用一次警报,我在SD卡中生成了Log.txt文件。对于每次报警当前时间的报警都将写入Log.txt文件中。但是在比较4到5个设备的Log.txt文件之后,我注意到对于某些设备,警报没有调用 onCreate() 的方法 UserTrackingReceiver.java (背景服务)。下面提到的完整代码块。

当应用程序启动时 registerUserTrackingReceiver() 方法已被调用,如下所示:

public static void registerUserTrackingReceiver(Context context) {
        try {
            Intent intent = new Intent(context, UserTrackingReceiver.class);

            boolean alarmUp = (PendingIntent.getService(context, 1001, intent, PendingIntent.FLAG_NO_CREATE) == null);

            if (alarmUp) {
                Calendar calendar = Calendar.getInstance();

                if (calendar.get(Calendar.MINUTE) > 0 && calendar.get(Calendar.MINUTE) <= 30) {
                    calendar.set(Calendar.HOUR_OF_DAY, calendar.get(Calendar.HOUR_OF_DAY));
                    calendar.set(Calendar.MINUTE, 30);
                    calendar.set(Calendar.SECOND, 0);
                } else if (calendar.get(Calendar.MINUTE) > 30) {
                    if (calendar.get(Calendar.HOUR_OF_DAY) == 23) {
                        calendar.set(Calendar.HOUR_OF_DAY, 0);
                    } else {
                        calendar.set(Calendar.HOUR_OF_DAY, calendar.get(Calendar.HOUR_OF_DAY) + 1);
                    }
                    calendar.set(Calendar.MINUTE, 0);
                    calendar.set(Calendar.SECOND, 0);
                } else {
                    calendar.set(Calendar.HOUR_OF_DAY, calendar.get(Calendar.HOUR_OF_DAY));
                    calendar.set(Calendar.MINUTE, 0);
                    calendar.set(Calendar.SECOND, 0);
                }

                PendingIntent sender = PendingIntent.getService(context, 1001, intent, 0);
                AlarmManager alarmManager = (AlarmManager) context.getSystemService(context.ALARM_SERVICE);
                alarmManager.setRepeating(AlarmManager.RTC_WAKEUP, calendar.getTimeInMillis(),
                        AlarmManager.INTERVAL_HALF_HOUR, sender);
            }

        } catch (Exception e) {
            e.printStackTrace();
        }
}

UserTrackingReceiver.java 在下面:

public class UserTrackingReceiver extends Service
        implements LocationListener,
        GoogleApiClient.ConnectionCallbacks,
        GoogleApiClient.OnConnectionFailedListener {

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

        Calendar calendar = Calendar.getInstance();
        Util.appendLog("Tracking Alarm Called on: " + calendar.get(Calendar.HOUR_OF_DAY) + " : " + calendar.get(Calendar.MINUTE) + " : " + calendar.get(Calendar.SECOND));
        stopSelf();
    }
}

Util.java 有 appendLog() 方法如下:

public static void appendLog(String text) {

        String baseDir = Environment.getExternalStorageDirectory().getAbsolutePath();

        File logFile = new File(baseDir + "/" + Constant.AppNameSuper + "/log.txt");
        if (!logFile.exists()) {
            try {
                logFile.createNewFile();
            } catch (IOException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }
        try {
            //BufferedWriter for performance, true to set append to file flag
            BufferedWriter buf = new BufferedWriter(new FileWriter(logFile, true));
            buf.append(text);
            buf.newLine();
            buf.close();
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
}

如果按照上述代码每隔30分钟调用一次警报,则应将其写入SDCARD中的Log.txt文件中。但问题是它不能每30分钟写一次日志文件,这意味着警报丢失。根据两天的阅读情况,我注意到,由于用户不断使用手机,白天不会丢失警报,但是当手机不使用时,它会在晚上错过。

带有不同设备的输出日志文件如下:

设备A Log.txt

  • 跟踪警报呼叫:0:0:31(从12:00开始)
  • 跟踪警报呼叫:1:10:27
  • 跟踪警报呼叫:3:5:25
  • 跟踪警报呼叫:6:55:31
  • 跟踪警报调用:7:0:6
  • 跟踪警报呼叫:7:30:0
  • 跟踪警报调用:8:0:6
  • 跟踪警报调用:8:30:0
  • 跟踪警报调用:9:0:6
  • 跟踪警报呼叫:9:30:0
  • 跟踪警报调用:10:0:0

Device B Log.txt

  • 跟踪警报呼叫:0:0:27(从12:00开始)
  • 跟踪警报调用:0:30:1
  • 跟踪警报调用:1:0:1
  • 跟踪警报呼叫:1:30:2
  • 跟踪警报调用:2:0:1
  • 跟踪警报呼叫:2:30:1
  • 跟踪警报调用:3:0:1
  • 跟踪警报呼叫:3:30:1
  • 跟踪警报调用:4:0:1
  • 跟踪警报呼叫:4:30:29
  • 跟踪警报调用:5:0:1
  • 跟踪警报呼叫:5:30:2
  • 跟踪警报呼叫:6:0:30
  • 跟踪警报呼叫:6:30:1
  • 跟踪警报调用:7:0:1
  • 跟踪警报呼叫:7:30:1
  • 跟踪警报调用:8:0:1
  • 跟踪警报呼叫:8:30:1
  • 跟踪警报调用:9:0:32
  • 跟踪警报呼叫:9:30:1

Device C Log.txt

  • 跟踪警报呼叫:0:0:7(从12:00开始)
  • 跟踪警报呼叫:0:30:3
  • 跟踪警报调用:1:0:6
  • 跟踪警报调用:1:30:1
  • 跟踪警报调用:2:0:32
  • 跟踪警报呼叫:2:30:3
  • 跟踪警报调用:3:1:50
  • 跟踪警报呼叫:3:30:5
  • 跟踪警报呼叫:4:1:58
  • 跟踪警报呼叫:4:31:14
  • 跟踪警报调用:5:0:1
  • 跟踪警报调用:5:30:1
  • 跟踪警报调用:6:2:1
  • 跟踪警报呼叫:6:30:1
  • 跟踪警报调用:7:0:1
  • 跟踪警报呼叫:7:30:1
  • 跟踪警报调用:8:0:1
  • 跟踪警报呼叫:8:30:4
  • 跟踪警报呼叫:9:1:44
  • 跟踪警报呼叫:9:30:1

Device D Log.txt

  • 跟踪警报呼叫:0:1:25(从12:00开始)
  • 跟踪警报调用:0:30:0
  • 跟踪警报调用:1:31:41
  • 跟踪警报呼叫:2:39:52
  • 跟踪警报调用:3:0:25
  • 跟踪警报呼叫:3:30:58
  • 跟踪警报呼叫:4:0:25
  • 跟踪警报呼叫:4:30:56
  • 跟踪警报呼叫:5:30:51
  • 跟踪警报呼叫:7:18:55
  • 跟踪警报呼叫:7:30:0
  • 跟踪警报调用:8:0:25
  • 跟踪警报呼叫:8:30:43
  • 跟踪警报调用:9:0:3
  • 跟踪警报呼叫:9:30:25
  • 跟踪警报调用:10:0:25
  • 跟踪警报呼叫:10:3​​0:4
  • 跟踪警报调用:11:1:52
  • 跟踪警报呼叫:11:30:27
  • 跟踪警报呼叫:12:1:6⁠+

7912
2018-06-21 09:18


起源

这听起来像Android MM中的打盹模式限制。您必须确保您的应用已列入白名单并使用 setAndAllowWhileIdle() 在API23中添加.... - Opiatefuchs
感谢edit @ user13 - himCream


答案:


问题可能是你的问题 PendingIntent 打电话给 Service。该设备可以在你之前重新入睡 Service 完成(甚至开始)执行。

我建议你用一个 BroadcastReceiver 相反(因为a WakeLock 保证期间 onReceive())。

获得一个 WakeLock 在 onReceive(),开始你的 Service 从那里发布 WakeLock 来自 Service, 在适当的时候。

要简化此过程,您可以使用 WakefulBroadcastReceiver 助手班:

  1. 呼叫 PendingIntent.getBroadcast() 代替 PendingIntent.getService()
  2. 开始吧 IntentService 从 onReceive() 通过电话 WakefulBroadcastReceiver.startWakefulService()
  3. 做你的东西 onHandleIntent() 并打电话 WakefulBroadcastReceiver.completeWakefulIntent() 等结束了。

例如,a BroadcastReceiver 开始觉醒 Service

public class ExampleReceiver extends BroadcastReceiver {
    @Override
    public void onReceive(Context context, Intent intent) {
        Intent wakefulServiceIntent = new Intent(context,
            ExampleWakefulService.class);

        WakefulBroadcastReceiver.startWakefulService(context,
            wakefulServiceIntent);
    }
}

而且 Service

public class ExampleWakefulService extends IntentService {

    private static final String NAME = "com.example.ExampleWakefulService";

    public ExampleWakefulService() {
        super(NAME);
    }

    @Override
    protected void onHandleIntent(Intent intent) {

        // doing stuff

        WakefulBroadcastReceiver.completeWakefulIntent(intent);
    }
}

另外,退房 本文 从开发人员的指南中保持设备清醒。

API等级23+ 你必须处理 瞌睡

来自 文件

为了帮助安排警报,Android 6.0(API级别23)介绍了   两个新的 AlarmManager 方法: setAndAllowWhileIdle() 和    setExactAndAllowWhileIdle()。使用这些方法,您可以设置警报   即使设备处于打盹状态,它也会触发。

不幸的是,没有其他选择 setRepeating(),所以你有两个选择:

  • 设置准确的警报(使用适当的方法,具体取决于设备的API级别,请检查 这个答案 例如)并在每次开火时重新安排它们。
  • 白名单 您的应用(由于Google的严格修订政策,因此不推荐)。

8
2018-06-21 09:29





你需要使用一个 BroadcastReceiver 还有一个唤醒锁,可以在设备空闲时可靠地实现这一点。此外,请注意,默认情况下从API 19警报开始是不准确的,这将发挥作用。如果您的目标是API 21或更新,请考虑使用 JobScheduler。与此帖相似 具有2个待处理意图的报警管理器只有1个工作?


1
2018-06-21 09:26





根据Android开发人员 文件 注意:

注意:从API 19开始,所有重复警报都不准确。如果你的   应用程序需要精确的交付时间,然后必须使用一次性   确切的警报,每次重新安排,如上所述。遗产   targetSdkVersion早于API 19的应用程序将   继续拥有所有警报,包括重复警报,   确切地对待。


0
2018-06-22 06:30