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

StackOverflow https://stackoverflow.com/questions/2531336

  •  22-09-2019
  •  | 
  •  

Вопрос

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

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

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

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

Спасибо

Это было полезно?

Решение

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

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

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 Design Flaw

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

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

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

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

Жизненный цикл Asynctask и активности

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

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

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

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

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

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

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

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

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

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

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

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


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

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


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

Другие советы

Как правило, вы можете положить предупредительные экземпляры и данные с использованием чего-то похожего на ниже:

<Elements xmlns="http://schemas.microsoft.com/sharepoint/">
    <ListInstance Title="My Survey"
                OnQuickLaunch="TRUE"
                TemplateType="102"
                FeatureId="00bfea71-2062-426c-90bf-714c59600103"
                Url="Lists/Survey"
                Description="">
    <Data>
      <Rows>
        <Row>
      <Field Name='Title'>My title field value</Field>
      </Row>
      </Rows>
      </Data>
</ListInstance>
</Elements>
.

Тем не менее, как опрос более «специальный», если вы хотите, чтобы фактические вопросы были столбцами в ваш список - и наиболее недокументированы (с точки зрения типов полей - список на MSDN доступны в http://msdn.microsoft.com/en-us/library/ms948164.aspx )

Так что вам нужно будет определить вопросы в столбцах, тогда как фактические ответы находятся в элементе «Данные». Поскольку вопросы определены как пользовательские поля с конкретным именем, поэтому необходимо содержать поле вопросов, и это поле должно совпадать с схемой определения поля - множество работ! Одна альтернатива может быть экспортировать список как STP и заглянуть внутрь в манифесте.

Попробуйте что-то следующее (не смог найти эквивалент по шкале Лайкерта, но простые вопросы должны быть как обычные столбцы). Также для столбцов сепаратора я обнаружил, что поле типа «PageParatorator» в основном вставляет разрыв страницы в вашем опросе:

<Field Type="Choice" DisplayName="Question 1" Required="FALSE" Format="RadioButtons" FillInChoice="FALSE" ID="YOUR_GUID_MUST_GO_HERE" StaticName="Question_x0020_1" Name="Question_x0020_1" ColName="nvarchar3" RowOrdinal="0"><CHOICES><CHOICE>Enter Choice #1</CHOICE><CHOICE>Enter Choice #2</CHOICE><CHOICE>Enter Choice #3</CHOICE></CHOICES></Field>
.

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

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

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

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

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

-ваше здоровье

От MVC точка зрения, активность - это Контроллер; это неправильно для Контроллер выполнять операции, которые переживают Вид (Получен из Android.View.View, обычно вы просто повторно используете существующие классы). Следовательно, это должно быть МодельОтветственность за начинание асинктас.

Если Asynctask не находится в пуле потоков (параллельная обработка), вы не можете прекратить выполнение Asynctask, поскольку она уже выполняется CPU, а ваша команда остановки или отмены будет выполнена после того, как ЦП будет бесплатным (ЦП выполняется с помощью Asynctask). Однако в пуле потоков задачи будут в очереди и будут выполнены один за другим. Так что, если ваша команда отмены в очереди при выполнении Async Tought, она может остановить вашу асинхронную задачу.

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

Я попробовал это, и, кажется, работает. Тем не менее, в тот момент, когда я планирую использовать «более цивилизованные» методы, такие как слабые ссылки из класса приложений (мои 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
Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top