AsyncTask не остановится, даже если активность была уничтожена - PullRequest
67 голосов
/ 28 марта 2010

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

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

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

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

Спасибо

Ответы [ 7 ]

141 голосов
/ 31 октября 2012

Ответ от @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.

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

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

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

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

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

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

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


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

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


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

14 голосов
/ 04 июня 2011

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

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

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

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

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

1 голос
/ 13 января 2015

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

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

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

-cheers

1 голос
/ 15 октября 2013

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

    <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
    }
0 голосов
/ 26 октября 2015

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

0 голосов
/ 31 января 2013

С точки зрения MVC , активность - это контроллер ; неправильно, что Controller выполняет операции, которые переживают View (полученный из android.view.View, обычно вы просто повторно используете существующие классы). Следовательно, *1001* Model должен отвечать за запуск AsyncTasks.

0 голосов
/ 30 января 2013

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

Я пробовал, и, похоже, работает. Тем не менее, в настоящий момент я планирую использовать «более цивилизованные» методы, такие как слабые ссылки из класса 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
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...