AsyncTask nie zatrzyma się nawet wtedy, gdy aktywność zostanie zniszczona

Mam obiekt AsyncTask, który rozpoczyna wykonywanie podczas tworzenia aktywności i robi rzeczy w tle (pobiera do 100 obrazów). Wszystko działa dobrze, ale jest takie dziwne zachowanie, którego nie jestem w stanie zrozumieć.

Na przykład: gdy zmienia się orientacja ekranu Androida, aktywność jest niszczona i tworzona ponownie. Nadpisuję więc metodę onRetainNonConfigurationInstance () i zapisuję wszystkie pobrane dane wykonane w Asynctasku. My celem tego jest nie uruchamianie AsyncTask za każdym razem, gdy aktywność jest niszczona-tworzona podczas zmian orientacji, ale jak widzę w moich dziennikach poprzednia AsyncTask jest nadal wykonywana. (Dane są zapisywane poprawnie)

Próbowałem nawet anulować AsyncTask w metodzie ondestroy () aktywności, ale dzienniki nadal pokazują AsyncTask jako uruchomiony.

To jest naprawdę dziwne zachowanie i naprawdę byłabym wdzięczna, gdyby ktoś mógł mi powiedzieć poprawną procedurę zatrzymania/anulowania AsyncTask.

Thanks

Author: Alex Bitek, 2010-03-27

6 answers

Odpowiedź udzielona przez @ Romain Guy jest prawidłowa. Niemniej jednak, chciałbym dodać uzupełnienie informacji i dać wskaźnik do biblioteki lub 2, które mogą być używane do długotrwałego działania AsyncTask i jeszcze więcej dla asynchronicznych zorientowanych sieciowo.

Asynchroniczne zadania zostały zaprojektowane do robienia rzeczy w tle. I tak, możesz to zatrzymać za pomocą metody cancel. Gdy pobierasz rzeczy z Internetu, zdecydowanie sugeruję, abyś zadbał o swój wątek, gdy jest to stan blokowania IO . Należy zorganizować pobieranie w następujący sposób:

public void download() {
    //get the InputStream from HttpUrlConnection or any other
    //network related stuff
    while( inputStream.read(buffer) != -1 && !Thread.interrupted() ) {
      //copy data to your destination, a file for instance
    }
    //close the stream and other resources
}

Użycie flagi Thread.interrupted pomoże Twojemu wątkowi poprawnie zamknąć blokujący stan io. Twój wątek będzie bardziej responsywny na wywołanie metody cancel.

Wada projektu AsyncTask

Ale jeśli Twoja AsyncTask trwa zbyt długo, wtedy napotkasz 2 różne problemy:

  1. aktywności są słabo powiązane z cyklem życia aktywności i nie uzyskasz wyniku asynchronicznego zadania, jeśli Twoja aktywność umrze. W rzeczy samej, tak, możesz, ale to będzie trudna droga.
  2. AsyncTask nie są zbyt dobrze udokumentowane. Naiwna, choć intuicyjna implementacja i użycie asynctasku może szybko doprowadzić do wycieków pamięci.

RoboSpice , biblioteka, którą chciałbym przedstawić, używa usługi w tle do wykonywania tego rodzaju żądań. Został zaprojektowany dla żądań sieciowych. Zapewnia dodatkowe funkcje, takie jak automatyczne buforowanie wyników żądań.

Oto powód, dla którego Asynchroniczne zadania są złe dla długotrwałych zadań. Poniższy powód jest adaptacją z exerpts of RoboSpice motivations : aplikacja, która wyjaśnia, dlaczego korzystanie z RoboSpice jest wypełnienie potrzeby na platformie Android.

Cykl życia Asynktasku i aktywności

Asynchroniczne zadania nie podążają za cyklem życia instancji aktywności. Jeśli uruchomisz Asynktask wewnątrz aktywności i obrócisz urządzenie, aktywność zostanie zniszczona i zostanie utworzona nowa instancja. Ale AsyncTask nie umrze. Będzie żył, dopóki się nie skończy.

A po jego zakończeniu, AsyncTask nie zaktualizuje interfejsu nowej aktywności. Faktycznie aktualizuje poprzednią instancję czynności, która nie jest już wyświetlany. Może to prowadzić do wyjątku typu java.lang.IllegalArgumentException: Widok Nie dołączony do menedżera okien, jeśli użyj na przykład findViewById, aby odzyskać widok wewnątrz aktywności.

Problem z wyciekiem pamięci

Bardzo wygodne jest tworzenie Asynchroniczne zadania jako wewnętrzne klasy Twoich działań. AsyncTask będzie musiał manipulować widokami aktywności, gdy zadanie jest ukończone lub w toku, korzystanie z wewnętrznej klasy aktywności wydaje się wygodne : wewnętrzne klasy mogą dostęp bezpośrednio do dowolnego pola klasy zewnętrznej.

niemniej jednak, oznacza to, że Klasa wewnętrzna będzie posiadać niewidoczne odniesienie na zewnętrznej instancji klasy: aktywność .

Na dłuższą metę powoduje to wyciek pamięci: jeśli AsyncTask trwa długo, utrzymuje aktywność " żywą" podczas gdy Android chciałby się go pozbyć, ponieważ nie może być już wyświetlany. Działalności nie można zbierać śmieci, a to Centralne mechanizm dla Androida do zachowania zasobów na urządzeniu.

Postęp twojego zadania zostanie utracony

Możesz użyć niektórych obejść, aby utworzyć długotrwałą funkcję asynchroniczną i zarządzać jej cyklem życia zgodnie z cyklem życia aktywności. Możesz albo anulować AsyncTask w onstopie metoda Twojej aktywności lub możesz pozwolić, aby Twoje zadanie asynchroniczne zakończyło się, a nie straciło jego postęp i podłącz je ponownie do następnej instancji Twojej aktywności.

Jest to możliwe i pokazujemy jak w RobopSpice motywacje, ale staje się to skomplikowane i Kod nie jest tak naprawdę ogólny. Co więcej, nadal stracisz postęp zadania, jeśli użytkownik opuści aktywność i wróci. Ten sam problem pojawia się w przypadku Loaderów, chociaż byłby prostszym odpowiednikiem AsyncTask z obejściem ponownego łączenia, o którym wspomniano powyżej.

Korzystanie z usługi Android

Najlepszą opcją jest użycie usługi do wykonywania długich zadań w tle. I to jest dokładnie rozwiązanie proponowane przez RoboSpice. Ponownie, jest przeznaczony do sieci, ale może być rozszerzony na rzeczy niezwiązane z siecią. Biblioteka ta posiada dużą liczbę funkcji .

Możesz nawet zorientować się w czasie krótszym niż 30 sekund dzięki infografiki .

To naprawdę bardzo zły pomysł, aby używać asynchronicznych zadań do długotrwałych operacji. Niemniej jednak, są one w porządku dla krótkich żywych, takich jak aktualizowanie widoku po 1 lub 2 sekundach.

Zachęcam do pobrania RoboSpice Motivations app , to naprawdę wyjaśnia to dogłębnie i dostarcza próbek i demonstracji różnych sposobów, aby zrobić pewne rzeczy związane z siecią.


Jeśli szukasz alternatywy dla RoboSpice w przypadku zadań niezwiązanych z siecią (na przykład bez buforowania) można również spojrzeć na Tape .
 144
Author: Snicolas,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 12:34:41

Romain ma rację. W rzeczywistości zadanie asynchroniczne jest odpowiedzialne za ukończenie własnej pracy w każdym przypadku. Przerywanie nie jest najlepszym sposobem, więc powinieneś stale sprawdzać, czy ktoś chce, abyś anulował lub przerwał zadanie.

Powiedzmy, że twój AsyncTask robi coś w pętli wiele razy. Następnie należy sprawdzić isCancelled() w każdej pętli.

while ( true ) {
    if ( isCancelled())
        break;
    doTheTask();
}

doTheTask() jest twoim prawdziwym zadaniem i zanim zrobisz to w każdej pętli, sprawdzasz, czy Twoje zadanie powinno zostać anulowane.

Ogólnie należy ustawić flagę w swojej klasie AsyncTask lub zwróć odpowiedni wynik z doInBackground(), aby w swojej klasie onPostExecute() sprawdzić, czy możesz skończyć to, co chcesz, lub czy Twoja praca została anulowana w środku.

 16
Author: Cagatay Kalan,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-02-24 11:31:14

Poniższe nie rozwiązuje Twojego problemu, ale uniemożliwia: W manifeście aplikacji Zrób to:

    <activity
        android:name=".(your activity name)"
        android:label="@string/app_name"
        android:configChanges="orientation|keyboardHidden|screenSize" > //this line here
    </activity>

Po dodaniu tego, Twoja aktywność nie ładuje się ponownie przy zmianie konfiguracji, a jeśli chcesz wprowadzić pewne zmiany, gdy zmieni się orientacja, po prostu Nadpisz następującą metodę aktywności:

@Override
public void onConfigurationChanged(Configuration newConfig) {
    super.onConfigurationChanged(newConfig);

        //your code here
    }
 1
Author: Frane Poljak,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-10-15 12:36:59

Aktywność jest odtwarzana po zmianie orientacji, tak, to prawda. ale możesz kontynuować asynchronizację zawsze, gdy to wydarzenie się wydarzy.

You check it on

@Override
protected void onCreate(Bundle savedInstanceState) { 
     if ( savedInstanceState == null ) {
           startAsyncTask()
     } else {
           // ** Do Nothing async task will just continue.
     }
}

-zdrówko

 1
Author: ralphgabb,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-01-13 02:23:51

Z punktu widzenia MVC Activity jest kontrolerem ; źle jest, gdy kontroler wykonuje operacje, które przetrwają Widok (pochodzący z Androida.widok.Widok, zwykle po prostu ponownie używasz istniejących klas). W związku z tym, to Model powinien być odpowiedzialny za uruchamianie asynchronicznych Zadań.

 -1
Author: 18446744073709551615,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-01-31 05:46:16

Możesz użyć class MagicAppRestart z tego postu na Zabij proces wraz ze wszystkimi asynchronicznymi zadaniami; Android przywróci stos aktywności (użytkownik o niczym nie wspomni). Ważne jest, aby pamiętać, że jedynym powiadomieniem przed ponownym uruchomieniem procesu jest wywołanie onPause(); zgodnie z logiką cyklu życia aplikacji na Androida , Aplikacja musi być i tak gotowa do takiego zakończenia.

Próbowałem i wydaje się, że działa. Niemniej jednak, w tej chwili planuję użyć "bardziej cywilizowanych" metod, takich jak słabe odniesienia z klasy aplikacji(Moje Asynktaski są raczej krótkotrwałe i mam nadzieję, że nie tyle pochłaniają pamięć).

Oto kod, którym możesz się pobawić:

MagicAppRestart.java

package com.xyz;

import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;

/** This activity shows nothing; instead, it restarts the android process */
public class MagicAppRestart extends Activity {
    // Do not forget to add it to AndroidManifest.xml
    // <activity android:name="your.package.name.MagicAppRestart"/>
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        System.exit(0);
    }
    public static void doRestart(Activity anyActivity) {
        anyActivity.startActivity(new Intent(anyActivity.getApplicationContext(), MagicAppRestart.class));
    }
}

Reszta jest tym, co Eclipse stworzył dla nowego projektu Androida dla com.xyz.Asynctaskestactivity :

Asynctaskestactivity.java

package com.xyz;

import android.app.Activity;
import android.os.AsyncTask;
import android.os.Bundle;
import android.util.Log;
import android.view.View;

public class AsyncTaskTestActivity extends Activity {
    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        Log.d("~~~~","~~~onCreate ~~~ "+this);
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
    }
    public void onStartButton(View view) {
        Log.d("~~~~","~~~onStartButton {");
        class MyTask extends AsyncTask<Void, Void, Void> {

            @Override
            protected Void doInBackground(Void... params) {
                // TODO Auto-generated method stub
                Log.d("~~~~","~~~doInBackground started");
                try {
                    for (int i=0; i<10; i++) {
                        Log.d("~~~~","~~~sleep#"+i);
                        Thread.sleep(200);
                    }
                    Log.d("~~~~","~~~sleeping over");
                } catch (InterruptedException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
                Log.d("~~~~","~~~doInBackground ended");
                return null;
            }
            @Override
            protected void onPostExecute(Void result) {
                super.onPostExecute(result);
                taskDone();
            }
        }
        MyTask task = new MyTask();
        task.execute(null);
        Log.d("~~~~","~~~onStartButton }");
    }
    private void taskDone() {
        Log.d("~~~~","\n\n~~~taskDone ~~~ "+this+"\n\n");
    }
    public void onStopButton(View view) {
        Log.d("~~~~","~~~onStopButton {");
        MagicAppRestart.doRestart(this);
        Log.d("~~~~","~~~onStopButton }");
    }
    public void onPause() {   Log.d("~~~~","~~~onPause ~~~ "+this);   super.onPause(); }
    public void onStop() {    Log.d("~~~~","~~~onStop ~~~ "+this);    super.onPause(); }
    public void onDestroy() { Log.d("~~~~","~~~onDestroy ~~~ "+this); super.onDestroy(); }
}

main.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:orientation="vertical" >

    <Button android:text="Start" android:onClick="onStartButton" android:layout_width="fill_parent" android:layout_height="wrap_content"/>
    <Button android:text="Stop" android:onClick="onStopButton" android:layout_width="fill_parent" android:layout_height="wrap_content"/>
    <TextView
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:text="@string/hello" />

</LinearLayout>

AndroidManifest.xml

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.xyz"
    android:versionCode="1"
    android:versionName="1.0" >
    <uses-sdk android:minSdkVersion="7" />
    <application
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name" >
        <activity
            android:name=".AsyncTaskTestActivity"
            android:label="@string/app_name" >
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
        <activity android:name=".MagicAppRestart"/>
    </application>
</manifest>

Oraz odpowiednią część dzienników (zwróć uwagę, że tylko onPause nazywa się):

D/~~~~    (13667): ~~~onStartButton {
D/~~~~    (13667): ~~~onStartButton }
D/~~~~    (13667): ~~~doInBackground started
D/~~~~    (13667): ~~~sleep#0
D/~~~~    (13667): ~~~sleep#1
D/~~~~    (13667): ~~~sleep#2
D/~~~~    (13667): ~~~sleep#3
D/~~~~    (13667): ~~~sleep#4
D/~~~~    (13667): ~~~sleep#5
D/~~~~    (13667): ~~~sleep#6
D/~~~~    (13667): ~~~sleep#7
D/~~~~    (13667): ~~~sleep#8
D/~~~~    (13667): ~~~sleep#9
D/~~~~    (13667): ~~~sleeping over
D/~~~~    (13667): ~~~doInBackground ended
D/~~~~    (13667): 
D/~~~~    (13667): 
D/~~~~    (13667): ~~~taskDone ~~~ com.xyz.AsyncTaskTestActivity@40516988
D/~~~~    (13667): 




D/~~~~    (13667): ~~~onStartButton {
D/~~~~    (13667): ~~~onStartButton }
D/~~~~    (13667): ~~~doInBackground started
D/~~~~    (13667): ~~~sleep#0
D/~~~~    (13667): ~~~sleep#1
D/~~~~    (13667): ~~~sleep#2
D/~~~~    (13667): ~~~sleep#3
D/~~~~    (13667): ~~~sleep#4
D/~~~~    (13667): ~~~sleep#5
D/~~~~    (13667): ~~~onStopButton {
I/ActivityManager(   81): Starting: Intent { cmp=com.xyz/.MagicAppRestart } from pid 13667
D/~~~~    (13667): ~~~onStopButton }
D/~~~~    (13667): ~~~onPause ~~~ com.xyz.AsyncTaskTestActivity@40516988
I/ActivityManager(   81): Process com.xyz (pid 13667) has died.
I/WindowManager(   81): WIN DEATH: Window{4073ceb8 com.xyz/com.xyz.AsyncTaskTestActivity paused=false}
I/ActivityManager(   81): Start proc com.xyz for activity com.xyz/.AsyncTaskTestActivity: pid=13698 uid=10101 gids={}
I/ActivityManager(   81): Displayed com.xyz/.AsyncTaskTestActivity: +44ms (total +65ms)
D/~~~~    (13698): ~~~onCreate ~~~ com.xyz.AsyncTaskTestActivity@40517238
 -4
Author: 18446744073709551615,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 12:10:44