首页 > 解决方案 > 如何使定期后台工作(15 分钟的定期间隔)在奥利奥及以后无限期地运行?

问题描述

在 Android 8 中引入打盹模式后,Android 对后台工作实施了许多限制,这使得应用程序以意想不到的方式运行。

在我的一个应用程序中,我过去每 15 分钟获取一次电池电量,并在电池电量高于用户在我的应用程序中设置的所需电池电量时发出警报。我使用以下代码来完成此任务。

使用 AlarmManagerApi 设置重复警报:

public static void setAlarm(final Context context) {
    final AlarmManager manager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
    if (manager != null) {
        manager.setRepeating(AlarmManager.ELAPSED_REALTIME_WAKEUP, 0, ALARM_INTERVAL,
                getAlarmPendingIntent(context));
    }
}

其中 getAlarmPendingIntent 方法如下:

private static PendingIntent getAlarmPendingIntent(final Context context) {
        return PendingIntent.getBroadcast(context, REQUEST_CODE, new Intent(context, AlarmReceiver.class), PendingIntent.FLAG_UPDATE_CURRENT);
    }

设置一个 BroadcastReciever 来接收警报:它在前台启动一个服务来完成所需的后台工作

public class AlarmReceiver extends BroadcastReceiver {

    @Override
    public void onReceive(final Context context, final Intent intent) {
        if (context == null || intent == null) {
            return;
        }

        if (intent.getAction() == null) {
            Intent monitorIntent = new Intent(context, TaskService.class);
            monitorIntent.putExtra(YourService.BATTERY_UPDATE, true);
            ContextCompat.startForegroundService(context, monitorIntent);
        }
    }

}

TaskService(Service做一些期望的任务)如下

public class TaskService extends Service {
    private static final String CHANNEL_ID = "channel_01";
    private static final int NOTIFICATION_ID = 12345678;

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

    public void createNotificationChannel(){
        // create notification channel for notifications
        NotificationManager mNotificationManager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);

        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            CharSequence name = getString(R.string.app_name);
            NotificationChannel mChannel =
                    new NotificationChannel(CHANNEL_ID, name, NotificationManager.IMPORTANCE_DEFAULT);
            if (mNotificationManager != null) {
                mNotificationManager.createNotificationChannel(mChannel);
            }
        }
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        startForeground(NOTIFICATION_ID, getNotification());    
        BatteryCheckAsync batteryCheckAsync = new BatteryCheckAsync();
        batteryCheckAsync.execute();
        return START_STICKY;
    }

    @Override
    public IBinder onBind(Intent intent) {
        return null;
    }


    private Notification getNotification() {
        NotificationCompat.Builder builder = new NotificationCompat.Builder(this, CHANNEL_ID)
                .setContentText(text)
                .setContentTitle("fetching battery level")
                .setOngoing(true)
                .setPriority(Notification.PRIORITY_LOW)
                .setSmallIcon(R.mipmap.ic_launcher)
                .setWhen(System.currentTimeMillis());

        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            builder.setChannelId(CHANNEL_ID);
        }

        return builder.build();
    }


    private class BatteryCheckAsync extends AsyncTask<Void, Void, Void> {

        @Override
        protected Void doInBackground(Void... arg0) {
            IntentFilter ifilter = new IntentFilter(Intent.ACTION_BATTERY_CHANGED);
            Intent batteryStatus = YourService.this.registerReceiver(null, ifilter);
            int level = batteryStatus.getIntExtra(BatteryManager.EXTRA_LEVEL, -1);
            int scale = batteryStatus.getIntExtra(BatteryManager.EXTRA_SCALE, -1);
            Log.i("Battery Charge Level",String.valueOf((level / (float) scale)));
            return null;

        }

        protected void onPostExecute() {
            YourService.this.stopSelf();
        }
    }
}

BatteryCheckAsync 任务从后台获取电池电量并在日志中显示当前电池电量。

现在,如果我运行该应用程序并将该应用程序从我最近的应用程序列表中滑出并关闭设备的屏幕。

该应用程序在 Android 8 之前的 android 版本中打印带有电池电量的日志。但是在 android 8 之后,在将应用程序从我最近的应用程序列表中滑出并关闭设备屏幕后的几分钟内,它就停止了工作。上述行为是预期的,因为在 android 中引入了打盹模式,并且警报被推迟到下一个维护窗口。

==================================================== ================

https://developer.android.com/training/monitoring-device-state/doze-standby上的 android 文档中,我发现它记录了

标准 AlarmManager 警报(包括 setExact() 和 setWindow())被推迟到下一个维护窗口。

如果您需要设置在打瞌睡时触发的警报,请使用 setAndAllowWhileIdle() 或 setExactAndAllowWhileIdle()。

使用 setAlarmClock() 设置的警报继续正常触发——系统在这些警报触发前不久退出打盹。

所以,我现在修改我的代码如下:

public static void setAlarm(final Context context) {
        final AlarmManager manager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
        if (manager == null) {
            return;
        }

        int SDK_INT = Build.VERSION.SDK_INT;
        if (SDK_INT < Build.VERSION_CODES.KITKAT) {
            manager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, ALARM_INTERVAL, getAlarmPendingIntent(context));
        } else if (SDK_INT < Build.VERSION_CODES.M) {
            manager.setExact(AlarmManager.ELAPSED_REALTIME_WAKEUP, ALARM_INTERVAL, getAlarmPendingIntent(context));
        } else {
            manager.setExactAndAllowWhileIdle(AlarmManager.ELAPSED_REALTIME_WAKEUP, ALARM_INTERVAL, getAlarmPendingIntent(context));
        }
    }

在通过再次运行上述setAlarm()方法在广播接收器的 onRecieve 方法中处理每个警报传递时,我没有设置重复警报,而是设置了一次精确警报并自己安排下一个警报。

现在,如果我运行该应用程序并将该应用程序从 OnePlus 3 上的“最近应用程序列表”中滑出并关闭设备屏幕,该应用程序会打印大约 40-45 分钟的日志,然后突然警报和前台服务停止运行。

有没有人在Android Oreo及更高版本中保证无限期执行周期性后台任务的解决方案?我正在安装了 Android Oreo 或更高版本的 OnePlus 设备上对其进行测试。

标签: androidbackground-processandroid-8.0-oreodoze

解决方案


您还可以利用 WorkManager 即:

 class SampleWorker : Worker(){
     override fun doWork(): Result {
        // do some stuff 
        return Result.SUCCESS
    }
}

并像这样使用它:

val recurringWork: PeriodicWorkRequest = 
PeriodicWorkRequest.Builder(SampleWorker::class.java, 15, TimeUnit.MINUTES).build()
WorkManager.getInstance()?.enqueue(recurringWork)

欲了解更多信息,请访问https://developer.android.com/topic/libraries/architecture/workmanager/basics


推荐阅读