Planowanie powtarzających się zadań w systemie Android

122

Projektuję aplikację, która ma cykliczne zadanie wysyłania informacji o obecności do serwera dedykowanego, o ile aplikacja jest na pierwszym planie.

W moich poszukiwaniach w Internecie widziałem kilka różnych podejść i chciałem wiedzieć, jaki jest najlepszy sposób zrobienia tego.

Jaki jest najlepszy sposób zaplanowania połączenia z serwerem?

Opcje, które zobaczyłem, to:

  1. Timer .

  2. ScheduledThreadPoolExecutor .

  3. Serwis .

  4. BroadcastReciever z AlarmManager .

Jakie jest Twoje zdanie?

EDYCJA:
Powodem, dla którego potrzebuję tego jest aplikacja oparta na czacie, która wysyła wszystkie działania użytkownika na zdalny serwer.
tj. użytkownik pisze wiadomość, użytkownik czyta wiadomość, użytkownik jest online, użytkownik jest offline itp.

Oznacza to, że raz na interwał muszę wysyłać serwerowi to, co robię, ponieważ otwieram pokój rozmów z innymi ludźmi, oni muszą wiedzieć, co robię.

Podobnie jak w przypadku mechanizmu informacji zwrotnej o wiadomościach WhatsApp: wiadomość wygląda na dostarczoną

EDYCJA NR 2:
Powtarzające się zadania powinny być teraz planowane prawie zawsze za pośrednictwem JobSchedulerinterfejsu API (lub FirebaseJobDispatcherdla niższych interfejsów API), aby zapobiec problemom z rozładowywaniem baterii, co można przeczytać w sekcji dotyczącej najważniejszych elementów szkolenia na temat Androida

EDYCJA NR 3:
FirebaseJobDispatcher został wycofany i zastąpiony przez Workmanager , który zawiera również funkcje JobScheduler.

łapa
źródło
2
BroaccastReceiver z AlarmManager jest dość prosty w użyciu. To jedyna z powyższych alternatyw, którą wypróbowałem.
1
Nie ma powodu, aby używać Timera zamiast ScheduledThreadPoolExecutor, który jest bardziej elastyczny, ponieważ zezwala na więcej niż jeden wątek w tle i ma lepszą rozdzielczość (przydatne tylko w przypadku rozdzielczości ms) i umożliwia obsługę wyjątków. Jeśli chodzi o AlarmManager, ten post zawiera informacje o różnicy.
assylias
W przypadku krótkiego cyklu życia, tj. Wykonywania jakiegoś zadania co 30 sekund w działaniu, które jest obecnie na pierwszym planie, użycie ScheduledThreadPoolExecutor (lub Timer) jest bardziej wydajne. W przypadku długiego cyklu życia, tj. Wykonywania niektórych zadań co 1 godzinę w usłudze w tle, użycie AlarmManager zapewnia większą niezawodność.
yorkw
Dlaczego w ogóle musisz zaplanować wysyłkę? Z opisu swojej aplikacji dlaczego nie wyślesz jej po prostu w czasie rzeczywistym?
iTech
ponieważ użytkownik zakłada, że ​​jesteś online, używając limitu czasu. co oznacza, że ​​jeśli nie otrzymałem wiadomości o obecności lub wpisywaniu wiadomości w ciągu ostatnich X czasu, automatycznie zakładam, że tego nie robisz
thepoosh

Odpowiedzi:

164

Nie jestem pewien, ale zgodnie ze swoją wiedzą podzielam swoje poglądy. Zawsze przyjmuję najlepszą odpowiedź, jeśli się mylę.

Menedżer alarmów

Menedżer alarmów utrzymuje blokadę wybudzenia CPU tak długo, jak długo onReceive()wykonywana jest metoda odbiornika alarmów . Gwarantuje to, że telefon nie będzie spał, dopóki nie zakończysz obsługi transmisji. Po onReceive()powrocie Menedżer alarmów zwalnia blokadę wybudzenia. Oznacza to, że w niektórych przypadkach telefon będzie spać zaraz po zakończeniu onReceive()metody. Jeśli zadzwonił Twój odbiornik alarmu Context.startService(), możliwe jest, że telefon zostanie uśpiony przed uruchomieniem żądanej usługi. Aby temu zapobiec, swoje BroadcastReceiveri Servicebędą musiały wdrożyć oddzielną politykę blokady obudzić, aby zapewnić, że telefon nadal działa aż usługa będzie dostępna.

Uwaga: Menedżer alarmów jest przeznaczony dla przypadków, w których chcesz, aby kod aplikacji był uruchamiany o określonej godzinie, nawet jeśli aplikacja nie jest aktualnie uruchomiona. W przypadku normalnych operacji związanych z synchronizacją (takty, przekroczenia limitu czasu itp.) Korzystanie z programu Handler jest łatwiejsze i bardziej wydajne.

Regulator czasowy

timer = new Timer();

    timer.scheduleAtFixedRate(new TimerTask() {

        synchronized public void run() {

            \\ here your todo;
            }

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

Timerma pewne wady, które rozwiązuje ScheduledThreadPoolExecutor. Więc to nie jest najlepszy wybór

ScheduledThreadPoolExecutor .

Możesz użyć java.util.Timerlub ScheduledThreadPoolExecutor(preferowane), aby zaplanować akcję wykonywaną w regularnych odstępach czasu w wątku w tle.

Oto próbka wykorzystująca to drugie:

ScheduledExecutorService scheduler =
    Executors.newSingleThreadScheduledExecutor();

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

Więc wolałem ScheduledExecutorService

Ale pomyśl również o tym, że jeśli aktualizacje pojawią się, gdy aplikacja jest uruchomiona, możesz użyć Timer, jak sugerowano w innych odpowiedziach, lub nowszej ScheduledThreadPoolExecutor. Jeśli Twoja aplikacja będzie aktualizowana nawet wtedy, gdy nie jest uruchomiona, powinieneś użyć rozszerzenia AlarmManager.

Menedżer alarmów jest przeznaczony do przypadków, w których chcesz, aby kod aplikacji był uruchamiany o określonej godzinie, nawet jeśli aplikacja nie jest aktualnie uruchomiona.

Zwróć uwagę, że jeśli planujesz aktualizację, gdy aplikacja jest wyłączona, raz na dziesięć minut jest dość częste, a zatem może być trochę zbyt energochłonne.

Md Maidul Islam
źródło
Staram się na tę metodę okresowo zadania, ale nie wydają się działać stackoverflow.com/questions/27872016/...
dowjones123
Do prostych rzeczy - takich jak sprawdzanie stanu co n-sekund - wystarczy Timer.
IgorGanapolsky
1
@ Maid786 Czego powinniśmy użyć, jeśli chcemy wykonać jakieś zadanie (np. Wysyłać powiadomienia) w odstępie tygodnia lub w czasie w dniach? Czy program Alarm Manager będzie wymagał zbyt wielu obliczeń lub przetwarzania w tle?
Chintan Shah
30

Regulator czasowy

Jak wspomniano w javadocs , lepiej jest używać ScheduledThreadPoolExecutor.

ScheduledThreadPoolExecutor

Użyj tej klasy, gdy Twój przypadek użycia wymaga wielu wątków roboczych, a interwał uśpienia jest mały. Jaki mały ? Cóż, powiedziałbym, że około 15 minut. W AlarmManagertym czasie rozpoczyna się planowanie odstępów czasu i wydaje się sugerować, że w przypadku mniejszych okresów snu można zastosować tę klasę. Nie mam danych, aby poprzeć ostatnie oświadczenie. To jest przeczucie.

Usługa

Twoja usługa może zostać zamknięta w dowolnym momencie przez maszynę wirtualną. Nie używaj usług do zadań cyklicznych. Cykliczne zadanie może uruchomić usługę, co jest zupełnie inną sprawą.

BroadcastReciever z AlarmManager

W przypadku dłuższych przerw w spaniu (> 15 minut) jest to właściwy sposób. AlarmManagerma już stałe ( AlarmManager.INTERVAL_DAY) sugerujące, że może wyzwalać zadania kilka dni po ich pierwotnym zaplanowaniu. Może również obudzić procesor, aby uruchomić kod.

Należy użyć jednego z tych rozwiązań w oparciu o czas i potrzeby wątku roboczego.

Deepak Bala
źródło
1
A co gdybym chciał skorzystać z aplikacji i co pół godziny chciałbym tworzyć kopię zapasową. Ale nie chcę tworzyć kopii zapasowej, gdy aplikacja nie jest używana (to byłaby całkowita strata). Alarmmanager będzie stale powtarzał akcję do ponownego uruchomienia (przynajmniej tak słyszałem). Co byś polecił? ScheduledThreadPoolExecutor czy Alarmmanager?
hasdrubal
13

Zdaję sobie sprawę, że to stare pytanie i na które udzielono odpowiedzi, ale może to komuś pomóc. W Twoimactivity

private ScheduledExecutorService scheduleTaskExecutor;

W 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
źródło
2

Cytowanie harmonogramu powtarzających się alarmów - Zapoznaj się z dokumentami dotyczącymi kompromisów :

Typowym scenariuszem wyzwalania operacji poza okresem istnienia aplikacji jest synchronizacja danych z serwerem. Jest to przypadek, w którym możesz ulec pokusie użycia powtarzającego się alarmu. Ale jeśli jesteś właścicielem serwera, na którym znajdują się dane Twojej aplikacji, użycie Google Cloud Messaging (GCM) w połączeniu z adapterem synchronizacji jest lepszym rozwiązaniem niż AlarmManager. Adapter synchronizacji zapewnia te same opcje planowania, co AlarmManager, ale zapewnia znacznie większą elastyczność.

Na tej podstawie najlepszym sposobem zaplanowania połączenia z serwerem jest użycie Google Cloud Messaging (GCM) w połączeniu z adapterem synchronizacji .

tato.rodrigo
źródło
1

Utworzyłem na czas zadanie, w którym zadanie, które użytkownik chce powtarzać, dodaję w metodzie Custom TimeTask run (). powtarza się ponownie.

 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
źródło