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

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

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

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

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

Спасибо

7 ответов

Ответ, данный @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 длится слишком долго, вы столкнетесь с 2 различными проблемами:

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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


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

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


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

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

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

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

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

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

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

Вы проверяете это на

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

-cheers

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

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

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

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

Ты можешь использовать 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 ~~~ 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
Другие вопросы по тегам