Планирование повторяющейся задачи в Android

122

Я разрабатываю приложение, которое имеет повторяющуюся задачу отправки информации о присутствии на выделенный сервер, пока приложение находится на переднем плане.

При поиске в Интернете я увидел несколько различных подходов и хотел узнать, как лучше всего это сделать.

Как лучше всего запланировать вызов сервера?

Я видел следующие варианты:

  1. Таймер .

  2. ScheduledThreadPoolExecutor .

  3. Сервис .

  4. BroadcastReciever с AlarmManager .

Каково ваше мнение?

EDIT:
причина, по которой мне это нужно, - это приложение на основе чата, которое отправляет все действия пользователя на удаленный сервер.
т.е. пользователь набирает сообщение, пользователь читает сообщение, пользователь в сети, пользователь не в сети и т. д.

Это означает, что один раз в каждом интервале мне нужно отправлять серверу, что я делаю, поскольку я открываю чат с другими людьми, они должны знать, что я делаю.

Подобно механизму обратной связи сообщений WhatsApp: сообщение выглядит доставленным

РЕДАКТИРОВАТЬ № 2:
повторяющиеся задачи теперь должны планироваться почти всегда через JobSchedulerAPI (или FirebaseJobDispatcherдля более низких API), чтобы предотвратить проблемы с разрядкой батареи, как можно прочитать в разделе жизненно важных функций обучения Android.

РЕДАКТИРОВАТЬ № 3:
FirebaseJobDispatcher устарел и заменен Workmanager , который также включает в себя функции JobScheduler.

thepoosh
источник
2
BroaccastReceiver с AlarmManager довольно прост в использовании. Это единственная из вышеперечисленных альтернатив, которые я пробовал.
1
Нет особых причин использовать таймер вместо ScheduledThreadPoolExecutor, который является более гибким, поскольку он позволяет использовать более одного фонового потока, имеет лучшее разрешение (полезно только для разрешения ms) и позволяет обрабатывать исключения. Что касается AlarmManager, этот пост дает некоторую информацию о различиях.
assylias
Для короткого жизненного цикла, то есть для выполнения некоторой задачи каждые 30 секунд в действии, которое в данный момент находится на переднем плане, использование ScheduledThreadPoolExecutor (или Timer) более эффективно. Для длительного жизненного цикла, т.е. выполнения некоторых задач каждый час в фоновом режиме, использование AlarmManager обеспечивает большую надежность.
Йорк,
Зачем вообще нужно планировать отправку? Из описания вашего приложения, почему бы вам просто не отправить его в режиме реального времени?
iTech
потому что пользователь предполагает, что вы в сети, используя тайм-аут. это означает, что если я не получал сообщения "присутствие" или "ввод" за прошедшее X количество времени, я автоматически предполагаю, что вы этого не делаете
thepoosh

Ответы:

164

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

Менеджер по тревоге

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

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

таймер

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.

Диспетчер сигналов тревоги предназначен для случаев, когда вы хотите, чтобы код вашего приложения запускался в определенное время, даже если ваше приложение в настоящее время не запущено.

Обратите внимание, что если вы планируете обновлять, когда ваше приложение выключено, раз в десять минут это довольно часто и, следовательно, может быть слишком энергоемким.

Md Maidul Islam
источник
Я пробую этот метод для периодической задачи, но, похоже, он не работает stackoverflow.com/questions/27872016/…
dowjones123
Для простых вещей - например, проверки состояния каждые n секунд - подойдет Timer.
Игорь Ганапольский
1
@ Maid786 Что мы должны использовать, если мы хотим выполнять какую-либо задачу (например, отправлять уведомления) с интервалом в неделю или продолжительностью в днях? Не потребуется ли для этого Alarm Manager слишком много фоновых вычислений или обработки?
Чинтан Шах
30

таймер

Как упоминалось в javadocs, вам лучше использовать ScheduledThreadPoolExecutor.

ScheduledThreadPoolExecutor

Используйте этот класс, когда ваш вариант использования требует нескольких рабочих потоков и интервал ожидания небольшой. Насколько маленький? Ну, я бы сказал минут 15. AlarmManager начинает планировать интервалы в это время , и это позволяет предположить , что для небольших интервалов сна можно использовать этот класс. У меня нет данных, подтверждающих последнее утверждение. Это догадка.

обслуживание

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

BroadcastReciever с AlarmManager

Это лучший способ для более длительных интервалов сна (> 15 минут). AlarmManagerуже есть constants ( 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.
Emzor
источник
2

Цитата из Планирования повторяющихся сигналов тревоги - документы о компромиссах :

Распространенный сценарий запуска операции за пределами срока службы вашего приложения - это синхронизация данных с сервером. Это тот случай, когда у вас может возникнуть соблазн использовать повторяющийся будильник. Но если у вас есть сервер, на котором размещены данные вашего приложения, использование Google Cloud Messaging (GCM) в сочетании с адаптером синхронизации - лучшее решение, чем AlarmManager. Адаптер синхронизации предоставляет те же параметры планирования, что и AlarmManager, но предлагает значительно большую гибкость.

Итак, исходя из этого, лучший способ запланировать вызов сервера - использовать Google Cloud Messaging (GCM) в сочетании с адаптером синхронизации .

tato.rodrigo
источник
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);
            }});
    }
}

}

hitesh141
источник