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

him*_*eam 11 service android alarmmanager

Service以30分钟的间隔调用背景来读取当前位置的纬度/经度并通过POST API将其发送到服务器.

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

当app start 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();
        }
}
Run Code Online (Sandbox Code Playgroud)

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();
    }
}
Run Code Online (Sandbox Code Playgroud)

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();
        }
}
Run Code Online (Sandbox Code Playgroud)

如果按照上述代码每隔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按下

ear*_*jim 9

问题可能是你的PendingIntent呼唤Service.在Service完成(甚至开始)执行之前,设备可以返回睡眠状态.

我建议你BroadcastReceiver改用(因为a WakeLock期间保证onReceive()).

获取WakeLockin onReceive(),Service从那里开始并在适当的时候WakeLock从中释放Service.

要简化此过程,您可以使用WakefulBroadcastReceiver帮助程序类:

  1. 打电话PendingIntent.getBroadcast()而不是PendingIntent.getService().
  2. 启动一个IntentServiceonReceive()调用WakefulBroadcastReceiver.startWakefulService().
  3. 做完你的东西,onHandleIntent()然后打电话WakefulBroadcastReceiver.completeWakefulIntent().

例如,BroadcastReceivera唤醒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);
    }
}
Run Code Online (Sandbox Code Playgroud)

而且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);
    }
}
Run Code Online (Sandbox Code Playgroud)

另外,请查看开发人员指南中关于保持设备唤醒的这篇文章.

API级别23+,您必须处理Doze.

文档:

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

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

  • 设置准确的警报(使用适当的方法,具体取决于设备的API级别,查看此答案的示例)并在每次触发时重新安排它们.
  • 您的应用列入白名单(由于Google的严格修订政策,不推荐使用).