Как заставить периодическую фоновую работу (15-минутный периодический интервал) работать бесконечно на Oreo и позже? - PullRequest
0 голосов
/ 24 сентября 2018

После того, как в 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);
    }

Настройка aBroadcastReciever для получения аварийных сигналов: Запускает службу на переднем плане для выполнения необходимой фоновой работы

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 до Android 8.Но после android 8 это перестало работать в течение нескольких минут после удаления приложения из моего недавнего списка приложений и выключения экрана устройства.Вышеуказанное поведение было ожидаемым, так как режим Doze был введен в Android, и тревоги отложены до следующего окна обслуживания.

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

ИзДокументация Android на https://developer.android.com/training/monitoring-device-state/doze-standby, Я обнаружил, что задокументировано, что

Стандартные тревоги AlarmManager (включая setExact () и setWindow ()) откладываются до следующего окна обслуживания.

Если вам нужно установить будильник, который срабатывает в режиме Doze, используйте 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));
        }
    }

вместо того, чтобы устанавливать повторяющуюся тревогу, я установил точную однократную тревогу и сам планировал следующую, обрабатывая каждую доставку тревоги в методе onRecieve для широковещательного приемника, выполнив выше setAlarm () метод снова.

Теперь, если я запустил приложение и смахнул приложение с моего списка недавних приложений на OnePlus 3 и выключил экран устройства, приложение печатает журналы в течение 40-45 минут, а затем внезапнослужба аварийной сигнализации и переднего плана перестает работать.

Есть ли у кого-нибудь решение для гарантированного выполнения периодической фоновой задачи в течение неопределенного периода времени в Android Oreo и выше?Я тестирую его на устройствах OnePlus с установленным Android Oreo или выше.

Ответы [ 2 ]

0 голосов
/ 24 сентября 2018

Попробуйте изменить

public class AlarmReceiver extends WakefulBroadcastReceiver {
    //logic
}

Также измените событие для диспетчера тревоги alarmManager.set(AlarmManager.RTC_WAKEUP,...)

В результате вы выйдете из режима ожидания и выполните свою работу.

0 голосов
/ 24 сентября 2018

Вы также можете воспользоваться 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

...