Планування повторюваних завдань в Android


122

Я розробляю додаток, у якого повторюється завдання надсилати присутність на виділений сервер, доки додаток стоїть на передньому плані.

Під час пошуку в Інтернеті я побачив декілька різних підходів і хотів дізнатися, який найкращий спосіб зробити це.

Який найкращий спосіб запланувати виклик на сервері?

Я бачив такі варіанти:

  1. Таймер .

  2. ScheduledThreadPoolExecutor .

  3. Сервіс .

  4. Трансляція ресивера з AlarmManager .

Яка твоя думка?

РЕДАКТУВАННЯ:
Причина мені потрібна в додатку на основі чату, який надсилає всі дії користувача на віддалений сервер.
тобто користувач набирає повідомлення, користувач читає повідомлення, користувач перебуває в мережі, користувач перебуває в автономному режимі і т.д.

Це означає, що раз на інтервал мені потрібно надсилати серверу те, що я роблю, оскільки я відкриваю кімнату чату з іншими людьми, вони повинні знати, що я роблю.

Подібно до механізму зворотного зв’язку повідомлення WhatsApp: повідомлення виглядає доставленим

РЕДАКТИКА №2:
Повторювані завдання тепер слід планувати майже завжди через JobSchedulerAPI (або FirebaseJobDispatcherдля нижчих API), щоб уникнути проблем із розряджанням акумулятора, як це можна прочитати в розділі « Життя» в навчанні Android

EDIT № 3:
FirebaseJobDispatcher був застарілим і замінений Workmanager , який також містить функції JobScheduler.


2
BroaccastReceiver з AlarmManager досить просто використовувати. Це єдиний із перерахованих вище альтернатив, які я спробував.

1
Існує мало причин використовувати таймер над ScheduledThreadPoolExecutor, який є більш гнучким, оскільки дозволяє більш ніж один фоновий потік і має кращу роздільну здатність (корисний лише для роздільної здатності мс) та дозволяє обробляти винятки. Що стосується AlarmManager, цей пост дає деяку інформацію про різницю.
assylias

Для короткого запущеного життєвого циклу, тобто виконайте якесь завдання кожні 30 секунд у діяльності, яка зараз знаходиться на передньому плані, використання ScheduledThreadPoolExecutor (або Таймер) є більш ефективним. Для тривалого життєвого циклу, тобто виконання певних завдань кожні 1 години у фоновому режимі, використання AlarmManager дає більшу надійність.
yorkw

Чому вам навіть потрібно запланувати відправлення? З опису програми, чому ви просто не відправляєте його в режимі реального часу?
iTech

оскільки користувач припускає, що ви перебуваєте в мережі, використовуючи тайм-аут. значить, якщо я не отримав повідомлення про "присутність" або "набір тексту" за останні X кількість часу, я автоматично припускаю, що ви цього не робите
thepoosh

Відповіді:


164

Я не впевнений, але, наскільки мені відомо, я поділяю свої погляди. Я завжди приймаю найкращу відповідь, якщо помиляюсь.

Диспетчер тривоги

Менеджер сигналізації тримає блокування процесорного блокування до тих пір, onReceive()поки виконується метод приймача сигналу тривоги . Це гарантує, що телефон не буде спати, поки ви не закінчите керувати трансляцією. Після onReceive()повернення диспетчер тривог відпускає цю функцію блокування. Це означає, що телефон у деяких випадках спить, як тільки ваш onReceive()метод завершиться. Якщо ваш приймач сигналу тривоги зателефонував Context.startService(), можливо, телефон заснує перед запуском потрібної послуги. Щоб цього не допустити, вам BroadcastReceiverі Serviceпотрібно буде застосувати окрему політику блокування пробудження, щоб телефон продовжував працювати, поки послуга не стане доступною.

Примітка. Менеджер сигналізації призначений для випадків, коли ви хочете, щоб ваш додаток працював у визначений час, навіть якщо ваша програма наразі не працює. Для звичайних операцій із синхронізацією (тики, тайм-аути тощо) використовувати простіше та набагато ефективніше.

Таймер

timer = new Timer();

    timer.scheduleAtFixedRate(new TimerTask() {

        synchronized public void run() {

            \\ here your todo;
            }

        }}, TimeUnit.MINUTES.toMillis(1), TimeUnit.MINUTES.toMillis(1));

Timerмає деякі недоліки, які вирішує проблема ScheduledThreadPoolExecutor. Тож це не найкращий вибір

ScheduledThreadPoolExecutor .

Ви можете використовувати java.util.Timerабо ScheduledThreadPoolExecutor(бажано) для планування дії, що має відбуватися через рівні проміжки часу на фоновому потоці.

Ось зразок із використанням останнього:

ScheduledExecutorService scheduler =
    Executors.newSingleThreadScheduledExecutor();

scheduler.scheduleAtFixedRate
      (new Runnable() {
         public void run() {
            // call service
         }
      }, 0, 10, TimeUnit.MINUTES);

Тому я віддав перевагу ScheduledExecutorService

Але також подумайте про те, що якщо оновлення відбуватимуться під час запуску вашої програми, ви можете використовувати Timer, як запропоновано в інших відповідях, або новішу ScheduledThreadPoolExecutor. Якщо ваша програма оновлюватиметься навіть тоді, коли вона не працює, вам слід перейти до AlarmManager.

Менеджер сигналізації призначений для випадків, коли ви хочете, щоб ваш додаток працював у визначений час, навіть якщо ваша програма наразі не працює.

Зауважте, що якщо ви плануєте оновлення, коли ваш додаток вимкнено, раз на десять хвилин це досить часто, і, можливо, трохи затрат енергії.


Я намагаюся цей метод за періодичну задачу, але це не схоже на роботу stackoverflow.com/questions/27872016 / ...
dowjones123

Для простих речей - наприклад, перевірка стану кожні п ять секунд -Timer буде робити.
ІгорГанапольський

1
@ Maid786 Що нам використовувати, якщо ми хочемо виконати якесь завдання (наприклад, надсилання сповіщень) з інтервалом тиждень або тривалістю в днях? Чи прийме менеджер тривоги занадто багато фонового обчислення або обробки для цього?
Чінтан Шах

30

Таймер

Як зазначалося на javadocs, вам краще використовувати ScheduledThreadPoolExecutor.

ScheduledThreadPoolExecutor

Використовуйте цей клас, коли у випадку використання потрібні кілька робочих ниток, а інтервал сну невеликий. Як маленький? Ну, я б сказав, хвилин 15. В AlarmManagerпочинає планувати інтервали в цей час , і це дозволяє припустити , що для невеликих інтервалів сну можна використовувати цей клас. У мене немає даних для підтвердження останнього твердження. Це примха.

Сервіс

Ваша послуга може бути закрита будь-коли. Не використовуйте сервіси для повторюваних завдань. Повторне завдання може запустити послугу, що зовсім інша справа.

Трансляція ресивера з AlarmManager

Для більш тривалих інтервалів сну (> 15 хвилин) це шлях. AlarmManagerвже має константи ( AlarmManager.INTERVAL_DAY), що говорить про те, що він може запускати завдання через кілька днів після того, як його було призначено спочатку. Це також може розбудити процесор для запуску вашого коду.

Ви повинні використовувати одне з таких рішень, виходячи з ваших потреб та часу роботи.


1
Що робити, якщо я хотів би скористатися додатком, і кожні півгодини я хотів би зробити резервну копію. Але я не хочу створювати резервні копії, поки додаток не використовується (це було б загальною тратою). Alarmmanager буде постійно повторювати дію до перезавантаження (це принаймні те, що я чув). Що б ти порадив? ScheduledThreadPoolExecutor або Alarmmanager?
hasdrubal

13

Я усвідомлюю, що це давнє запитання і на нього відповіли, але це може комусь допомогти. У вашомуactivity

private ScheduledExecutorService scheduleTaskExecutor;

В onCreate

  scheduleTaskExecutor = Executors.newScheduledThreadPool(5);

    //Schedule a task to run every 5 seconds (or however long you want)
    scheduleTaskExecutor.scheduleAtFixedRate(new Runnable() {
        @Override
        public void run() {
            // Do stuff here!

            runOnUiThread(new Runnable() {
                @Override
                public void run() {
                    // Do stuff to update UI here!
                    Toast.makeText(MainActivity.this, "Its been 5 seconds", Toast.LENGTH_SHORT).show();
                }
            });

        }
    }, 0, 5, TimeUnit.SECONDS); // or .MINUTES, .HOURS etc.

2

Цитуючи планування повторюваних сигналів тривоги - зрозумійте документи про компроміси :

Поширеним сценарієм запуску операції поза життя вашого додатка є синхронізація даних із сервером. Це випадок, коли вас може спокусити використовувати повторюваний сигнал тривоги. Але якщо у вас є сервер, на якому розміщені дані вашого додатка, використання Google Cloud Messaging (GCM) спільно з адаптером синхронізації є кращим рішенням, ніж AlarmManager. Адаптер синхронізації дає всі ті ж параметри планування, що і AlarmManager, але він пропонує вам значно більшу гнучкість.

Отже, виходячи з цього, найкращим способом запланувати виклик на сервері є використання Google Cloud Messaging (GCM) спільно з адаптером для синхронізації .


1

Я створив за часом завдання, в якому завдання, яке користувач хоче повторити, додати в метод Custom TimeTask run (). вона успішно повторюється.

 import java.text.SimpleDateFormat;
 import java.util.Calendar;
 import java.util.Timer;
 import java.util.TimerTask;

 import android.os.Bundle;
 import android.view.View;
 import android.view.View.OnClickListener;
 import android.widget.Button;
 import android.widget.CheckBox;
 import android.widget.TextView;
 import android.app.Activity;
 import android.content.Intent;

 public class MainActivity extends Activity {

     CheckBox optSingleShot;
     Button btnStart, btnCancel;
     TextView textCounter;

     Timer timer;
     MyTimerTask myTimerTask;

     int tobeShown = 0  ;

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    optSingleShot = (CheckBox)findViewById(R.id.singleshot);
    btnStart = (Button)findViewById(R.id.start);
    btnCancel = (Button)findViewById(R.id.cancel);
    textCounter = (TextView)findViewById(R.id.counter);
    tobeShown = 1;

    if(timer != null){
        timer.cancel();
    }

    //re-schedule timer here
    //otherwise, IllegalStateException of
    //"TimerTask is scheduled already" 
    //will be thrown
    timer = new Timer();
    myTimerTask = new MyTimerTask();

    if(optSingleShot.isChecked()){
        //singleshot delay 1000 ms
        timer.schedule(myTimerTask, 1000);
    }else{
        //delay 1000ms, repeat in 5000ms
        timer.schedule(myTimerTask, 1000, 1000);
    }

    btnStart.setOnClickListener(new OnClickListener(){

        @Override
        public void onClick(View arg0) {


            Intent i = new Intent(MainActivity.this, ActivityB.class);
            startActivity(i);

            /*if(timer != null){
                timer.cancel();
            }

            //re-schedule timer here
            //otherwise, IllegalStateException of
            //"TimerTask is scheduled already" 
            //will be thrown
            timer = new Timer();
            myTimerTask = new MyTimerTask();

            if(optSingleShot.isChecked()){
                //singleshot delay 1000 ms
                timer.schedule(myTimerTask, 1000);
            }else{
                //delay 1000ms, repeat in 5000ms
                timer.schedule(myTimerTask, 1000, 1000);
            }*/
        }});

    btnCancel.setOnClickListener(new OnClickListener(){

        @Override
        public void onClick(View v) {
            if (timer!=null){
                timer.cancel();
                timer = null;
            }
        }
    });

}

@Override
protected void onResume() {
    super.onResume();

    if(timer != null){
        timer.cancel();
    }

    //re-schedule timer here
    //otherwise, IllegalStateException of
    //"TimerTask is scheduled already" 
    //will be thrown
    timer = new Timer();
    myTimerTask = new MyTimerTask();

    if(optSingleShot.isChecked()){
        //singleshot delay 1000 ms
        timer.schedule(myTimerTask, 1000);
    }else{
        //delay 1000ms, repeat in 5000ms
        timer.schedule(myTimerTask, 1000, 1000);
    }
}


@Override
protected void onPause() {
    super.onPause();

    if (timer!=null){
        timer.cancel();
        timer = null;
    }

}

@Override
protected void onStop() {
    super.onStop();

    if (timer!=null){
        timer.cancel();
        timer = null;
    }

}

class MyTimerTask extends TimerTask {

    @Override
    public void run() {

        Calendar calendar = Calendar.getInstance();
        SimpleDateFormat simpleDateFormat = 
                new SimpleDateFormat("dd:MMMM:yyyy HH:mm:ss a");
        final String strDate = simpleDateFormat.format(calendar.getTime());

        runOnUiThread(new Runnable(){

            @Override
            public void run() {
                textCounter.setText(strDate);
            }});
    }
}

}

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.