AsyncTask не останавливается, даже когда активность уничтожена

У меня есть объект AsyncTask, который начинает выполняться при создании активности и делает материал в фоновом режиме (загружает до 100 изображений). Все работает отлично, но есть такое своеобразное поведение, которое я не могу понять.

Например, когда меняется ориентация экрана Android, активность снова уничтожается и создается. Поэтому я переопределяю метод onRetainNonConfigurationInstance() и сохраняю все загруженные данные, выполненные в AsyncTask. Моя цель заключается в том, чтобы не запускать AsyncTask каждый раз, когда активность уничтожается - создается во время изменений ориентации, но, как я вижу в своих журналах, предыдущая AsyncTask все еще выполняется. (Данные сохраняются правильно, хотя)

Я даже пытался отменить AsyncTask в методе onDestroy(), но журналы все еще показывают, что AsyncTask работает.

Это действительно странное поведение и было бы действительно благодарно, если кто-то может сказать мне правильную процедуру, чтобы остановить/отменить AsyncTask.

Спасибо

Ответ 1

AFAIK, вы не можете остановить AsyncTask. Я не верю, что метод cancel() будет работать, если задача уже выполняется - это будет работать, если задача ожидает очереди в потоке для освобождения.

Ответ 2

Ответ, данный @Romain Guy, верен. Тем не менее, я хотел бы добавить дополнение к информации и дать указатель на библиотеку или 2, которые можно использовать для длительной работы AsyncTask и даже больше для сетевых асинхронных задач.

AsyncTasks предназначены для работы в фоновом режиме. И да, вы можете остановить его, используя метод cancel. Когда вы загружаете материал из Интернета, я настоятельно рекомендую вам позаботиться о своем потоке, когда это состояние блокировки ввода-вывода. Вы должны упорядочить загрузку следующим образом:

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
}

Использование флага Thread.interrupted поможет вашему потоку правильно выйти из состояния блокировки io. Ваш поток будет более восприимчивым к вызову метода cancel.

Ошибка дизайна AsyncTask

Но если ваша AsyncTask длится слишком долго, вы столкнетесь с двумя разными проблемами:

  • Мероприятия плохо привязаны к жизненному циклу активности, и вы не получите результат своей AsyncTask, если ваша деятельность умирает. Действительно, да, вы можете, но это будет грубый путь.
  • AsyncTask не очень хорошо документирован. Наивная, хотя и интуитивно понятная, реализация и использование асинтакты может быстро привести к утечкам памяти.

RoboSpice, библиотеку, которую я хотел бы представить, использует фоновый сервис для выполнения таких запросов. Он был разработан для сетевых запросов. Он предоставляет дополнительные функции, такие как автоматическое кэширование результатов запросов.

Вот почему AsyncTasks вредны для длительных задач. Следующее обоснование - это адаптация из примеров мотивации RoboSpice: приложение, объясняющее, почему использование RoboSpice заполняет потребность на платформе Android.

Жизненный цикл AsyncTask и Activity

AsyncTasks не соответствуют жизненному циклу экземпляров действий. Если вы запустите AsyncTask внутри Activity и вы повернете устройство, действие будет уничтожено, и будет создан новый экземпляр. Но AsyncTask не умрет. Он будет продолжаться до тех пор, пока он не завершится.

И когда он завершится, AsyncTask не будет обновлять пользовательский интерфейс нового действия. Действительно, он обновляет прежний экземпляр деятельности, которая больше не отображается. Это может привести к исключению типа java.lang.IllegalArgumentException: View не подключен к оконному менеджеру, если вы используйте, например, findViewById, чтобы получить представление внутри Activity.

Проблема утечки памяти

Очень удобно создавать AsyncTasks как внутренние классы вашей деятельности. Поскольку AsyncTask необходимо будет манипулировать представлениями деятельности, когда задача завершена или выполняется, использование внутреннего класса Activity представляется удобным: внутренние классы могут доступ непосредственно к любому полю внешнего класса.

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

В долгосрочной перспективе это приводит к утечке памяти: если AsyncTask длится долго, он сохраняет активность "живым", в то время как Android хочет избавиться от него, поскольку он больше не может отображаться. Активность не может быть собрана мусором и что центральная механизм Android для сохранения ресурсов на устройстве.

Прогресс вашей задачи будет потерян

Вы можете использовать некоторые обходные пути для создания длительной асинтезы и управлять ее жизненным циклом в соответствии с жизненным циклом деятельности. Вы можете либо отменить AsyncTask в методе onStop вашей деятельности, либо вы можете завершить свою задачу async и не потерять ее прогресс и привязать его к следующему экземпляру вашей деятельности.

Это возможно, и мы показываем, как в мотивах RobopSpice, но он становится сложным и код не является общим. Более того, вы все равно потеряете ход своей задачи, если пользователь покинет эту деятельность и вернется. Эта же проблема возникает с загрузчиками, хотя это будет более простой эквивалент AsyncTask с повторным обходным решением, упомянутым выше.

Использование службы Android

Лучшим вариантом является использование службы для выполнения ваших длинных фоновых задач. И это именно то решение, которое предлагает RoboSpice. Опять же, он предназначен для сетей, но может быть распространен на не связанные с сетью вещи. Эта библиотека имеет большое количество функций.

Вы даже можете получить представление об этом менее чем за 30 секунд благодаря infographics.


На самом деле очень плохая идея использовать AsyncTasks для длительных операций. Тем не менее, они подходят для коротких живых, таких как обновление представления через 1 или 2 секунды.

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


Если вы ищете альтернативу RoboSpice для не связанных с сетью задач (например, без кеширования), вы также можете посмотреть Tape.

Ответ 3

Ромен Гай прав. На самом деле задача async отвечает за завершение своей работы в любом случае. Прерывание - это не лучший способ, поэтому вы должны постоянно проверять, хочет ли кто-нибудь отменить или остановить вашу задачу.

Скажем, ваш AsyncTask делает что-то в цикле много раз. Затем вы должны проверить isCancelled() в каждом цикле.

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

doTheTask() - это ваша настоящая работа, и, прежде чем делать это в каждом цикле, вы проверяете, должна ли ваша задача быть отменена.

Как правило, вы должны установить флаг в классе AsyncTask или вернуть соответствующий результат из вашего doInBackground(), чтобы в onPostExecute() вы могли проверить, можете ли вы закончить то, что хотите, или если ваша работа была отменена в середине.

Ответ 4

Ниже вы не решите свою проблему, но предотвратите ее: В манифесте приложения выполните следующее:

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

Когда вы добавляете это, ваша активность не перезагружается при изменении конфигурации, и если вы хотите внести некоторые изменения при изменении ориентации, вы просто переопределите следующий метод активности:

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

        //your code here
    }

Ответ 5

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

вы проверяете его на

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

-cheers

Ответ 6

Если asynctask не находится в пуле потоков (параллельная обработка), вы не можете прекратить выполнение asynctask, поскольку он уже выполняется CPU, и ваша команда stop или cancel будет выполнена после того, как CPU свободен (cpu выполняется с помощью asynctask). Однако он находится в пуле потоков, задачи будут поставлены в очередь и будут выполняться один за другим. Поэтому, если ваша команда отмены поставлена ​​в очередь при выполнении задачи async, она может остановить вашу асинхронную задачу.

Ответ 7

С точки зрения MVC Activity является контроллером; неверно, чтобы контроллер выполнял операции, которые переживают представление (производное от android.view.View, обычно вы просто повторно используете существующие классы). Поэтому для запуска AsyncTasks должна быть ответственность Модели.

Ответ 8

Вы можете использовать class MagicAppRestart из этого сообщения убить процесс вместе со всеми AsyncTasks; Android восстановит стек активности (пользователь ничего не заметит). Важно отметить, что только уведомление перед перезагрузкой процесса вызывает onPause(); в соответствии с логикой жизненного цикла приложения Android, ваше приложение должно быть готово к такому завершению в любом случае.

Я пробовал это, и, похоже, он работает. Тем не менее, на данный момент я планирую использовать "более цивилизованные" методы, такие как слабые ссылки из класса Application (мои AsyncTasks довольно короткоживущие и, надеюсь, не так много памяти).

Вот код, с которым вы можете играть:

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));
    }
}

Остальное - это то, что Eclipse создал для нового Android-проекта для com.xyz.AsyncTaskTestActivity:

AsyncTaskTestActivity.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>

и соответствующую часть журналов (обратите внимание, что только onPause называется):

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 ~~~ [email protected]
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 ~~~ [email protected]
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 ~~~ [email protected]