如何进行定期后台工作(以15分钟为间隔),使其在Oreo及更高版本上无限期运行?

时间:2018-09-24 11:23:52

标签: android background-process android-8.0-oreo doze

在Android 8中引入“打Mode模式”后,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);
    }

设置广播接收器以接收警报:它在前台启动服务以执行所需的后台工作

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(服务执行所需的任务)如下

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中引入了打ze模式,并且将警报推迟到下一个维护窗口。

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

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

  

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

     

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

     

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

因此,我现在如下修改我的代码:

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设备上进行测试。

2 个答案:

答案 0 :(得分:0)

您还可以利用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

答案 1 :(得分:0)

尝试更改

public class AlarmReceiver extends WakefulBroadcastReceiver {
    //logic
}

还更改警报管理器的事件 alarmManager.set(AlarmManager.RTC_WAKEUP,...)

因此,您将从休眠模式唤醒设备并执行您的工作。