Основне завдання, діалог прогресу, зміна орієнтації - чи є 100% робоче рішення?


235

Я завантажую деякі дані з Інтернету у фонову нитку (я використовую AsyncTask) і відображаю діалог прогрес під час завантаження. Змінюється орієнтація, активність перезапускається, після чого мій AsyncTask завершено - я хочу відхилити діалогове вікно програм і розпочати нову діяльність. Але виклик dismissDialog іноді кидає виняток (можливо, тому що Діяльність була знищена, а нова Активність ще не запущена).

Який найкращий спосіб вирішити подібну проблему (оновлення інтерфейсу користувача з фонового потоку, який працює, навіть якщо користувач змінює орієнтацію)? Хтось із Google надав якесь "офіційне рішення"?


4
Моя дописка на цю тему може допомогти. Йдеться про збереження тривалих завдань під час зміни конфігурації.
Алекс Локвуд

1
Це питання також пов'язане.
Алекс Локвуд

Тільки FTR тут пов’язана загадка .. stackoverflow.com/q/23742412/294884
Fattie

Відповіді:


336

Крок 1: Зробіть AsyncTaskна staticвкладений клас, або зовсім окремий клас, тільки не внутрішній (не статичною вкладений) клас.

Крок №2: Встановіть AsyncTaskутримування за Activityдопомогою елемента даних, встановленого через конструктор і сеттер.

Крок №3: Створюючи AsyncTaskструм, подайте струм Activityна конструктор.

Крок №4: onRetainNonConfigurationInstance()Поверніться назад AsyncTask, від'єднавши його від початкової активності, що вже відходить.

Крок №5: onCreate()Якщо getLastNonConfigurationInstance()цього немає null, відведіть його до свого AsyncTaskкласу та зателефонуйте своєму сетеві, щоб пов’язати вашу нову діяльність із завданням.

Крок № 6: Не посилайтеся на члена даних про діяльність від doInBackground().

Якщо ви будете слідувати вищенаведеному рецепту, то все спрацює. onProgressUpdate()і onPostExecute()призупиняються між початком onRetainNonConfigurationInstance()і кінцем наступного onCreate().

Ось зразок проекту, що демонструє техніку.

Інший підхід полягає в канавах AsyncTaskі перемістити свою роботу в IntentService. Це особливо корисно, якщо робота, яку потрібно виконати, може бути тривалою і повинна тривати незалежно від того, чим користувач займається в частині діяльності (наприклад, завантаження великого файлу). Ви можете використовувати впорядковану трансляцію Intentдля того, щоб активність реагувала на виконану роботу (якщо вона все ще знаходиться на передньому плані), або підняти а, Notificationщоб сповістити користувача, чи виконано роботу. Ось допис у блозі з детальнішою інформацією про цю схему.


8
Велике спасибі за чудову відповідь на це поширене питання! Для того, щоб бути ретельним, ви можете додати до кроку №4, що ми повинні від'єднати (встановити нуль) активність у AsyncTask. Це добре продемонстровано у зразковому проекті.
Кевін Гаудін

3
Але що робити, якщо мені потрібно мати доступ до учасників діяльності?
Євген

3
@Andrew: Створіть статичний внутрішній клас або щось, що містить декілька об'єктів, і поверніть його.
CommonsWare

11
onRetainNonConfigurationInstance()застаріла і запропонована альтернатива - використовувати setRetainInstance(), але об’єкт не повертає. Чи можна впоратися asyncTaskзі зміною конфігурації setRetainInstance()?
Індрек Кюе

10
@SYLARRR: Абсолютно. Мати Fragmentутримувати AsyncTask. Мають Fragmentвиклик setRetainInstance(true)на себе. Майте AsyncTaskєдину розмову з Fragment. Тепер, при зміні конфігурації, значення Fragmentне знищується та відтворюється (навіть незважаючи на активність), і таким чином AsyncTask, зберігається протягом зміни конфігурації.
CommonsWare

13

Прийнята відповідь була дуже корисною, але вона не має діалогового прогресу.

На щастя для вас, читачу, я створив надзвичайно вичерпний і робочий приклад AsyncTask з діалогом прогресу !

  1. Обертання працює, і діалог виживає.
  2. Ви можете скасувати завдання та діалогове вікно, натиснувши кнопку "назад" (якщо ви хочете такої поведінки).
  3. Тут використовуються фрагменти.
  4. Схема фрагмента під активністю змінюється належним чином при обертанні пристрою.

Прийнята відповідь стосується статичних класів (не членів). І це необхідно, щоб уникнути того, що AsyncTask має (прихований) вказівник на екземпляр зовнішнього класу, який стає витоком пам'яті при знищенні активності.
Bananeweizen

Так, не впевнений, чому я ставлю це щодо статичних членів, оскільки я насправді також використовував їх ... дивно. Відредагована відповідь.
Timmmm

Чи можете ви оновити своє посилання? Мені це справді потрібно.
Ромен Пеллерін

Вибачте, не вдалося відновити мій веб-сайт - я це зроблю незабаром! Але в той же час це в основному так само , як код в цій відповіді: stackoverflow.com/questions/8417885 / ...
Timmmm

1
Посилання хибна; просто призводить до марного індексу без вказівки, де знаходиться код.
FractalBob

9

Я протягом тижня намагався знайти рішення цієї дилеми, не вдаючись до редагування файлу маніфесту. Припущення для цього рішення:

  1. Завжди потрібно використовувати діалог прогресу
  2. За один раз виконується лише одне завдання
  3. Вам потрібно зберегти завдання, коли телефон повертається і діалогове вікно просування буде автоматично відхилено.

Впровадження

Вам потрібно буде скопіювати два файли, знайдені внизу цієї публікації, у робочу область. Просто переконайтесь, що:

  1. Усі ваші Activitys повинні поширюватисяBaseActivity

  2. В onCreate(), super.onCreate()повинна викликатися після ініціалізації будь-яких членів , які потрібно отримати доступ до ваших ASyncTaskс. Крім того, замініть, getContentViewId()щоб вказати ідентифікатор макета форми.

  3. Перейдіть onCreateDialog() як звичайно, щоб створити діалоги, якими керує діяльність.

  4. Дивіться код нижче для зразка статичного внутрішнього класу, щоб скласти AsyncTasks. Ви можете зберегти свій результат у mResult, щоб отримати доступ пізніше.


final static class MyTask extends SuperAsyncTask<Void, Void, Void> {

    public OpenDatabaseTask(BaseActivity activity) {
        super(activity, MY_DIALOG_ID); // change your dialog ID here...
                                       // and your dialog will be managed automatically!
    }

    @Override
    protected Void doInBackground(Void... params) {

        // your task code

        return null;
    }

    @Override
    public boolean onAfterExecute() {
        // your after execute code
    }
}

І, нарешті, розпочати нове завдання:

mCurrentTask = new MyTask(this);
((MyTask) mCurrentTask).execute();

Це воно! Я сподіваюся, що це надійне рішення комусь допоможе.

BaseActivity.java (організуйте імпорт самостійно)

protected abstract int getContentViewId();

public abstract class BaseActivity extends Activity {
    protected SuperAsyncTask<?, ?, ?> mCurrentTask;
    public HashMap<Integer, Boolean> mDialogMap = new HashMap<Integer, Boolean>();

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        setContentView(getContentViewId());

        mCurrentTask = (SuperAsyncTask<?, ?, ?>) getLastNonConfigurationInstance();
        if (mCurrentTask != null) {
            mCurrentTask.attach(this);
            if (mDialogMap.get((Integer) mCurrentTask.dialogId) != null
                && mDialogMap.get((Integer) mCurrentTask.dialogId)) {
        mCurrentTask.postExecution();
            }
        }
    }

    @Override
    protected void onPrepareDialog(int id, Dialog dialog) {
    super.onPrepareDialog(id, dialog);

        mDialogMap.put(id, true);
    }

    @Override
    public Object onRetainNonConfigurationInstance() {
        if (mCurrentTask != null) {
            mCurrentTask.detach();

            if (mDialogMap.get((Integer) mCurrentTask.dialogId) != null
                && mDialogMap.get((Integer) mCurrentTask.dialogId)) {
                return mCurrentTask;
            }
        }

        return super.onRetainNonConfigurationInstance();
    }

    public void cleanupTask() {
        if (mCurrentTask != null) {
            mCurrentTask = null;
            System.gc();
        }
    }
}

SuperAsyncTask.java

public abstract class SuperAsyncTask<Params, Progress, Result> extends AsyncTask<Params, Progress, Result> {
    protected BaseActivity mActivity = null;
    protected Result mResult;
    public int dialogId = -1;

    protected abstract void onAfterExecute();

    public SuperAsyncTask(BaseActivity activity, int dialogId) {
        super();
        this.dialogId = dialogId;
        attach(activity);
    }

    @Override
    protected void onPreExecute() {
        super.onPreExecute();
        mActivity.showDialog(dialogId); // go polymorphism!
    }    

    protected void onPostExecute(Result result) {
        super.onPostExecute(result);
        mResult = result;

        if (mActivity != null &&
                mActivity.mDialogMap.get((Integer) dialogId) != null
                && mActivity.mDialogMap.get((Integer) dialogId)) {
            postExecution();
        }
    };

    public void attach(BaseActivity activity) {
        this.mActivity = activity;
    }

    public void detach() {
        this.mActivity = null;
    }

    public synchronized boolean postExecution() {
        Boolean dialogExists = mActivity.mDialogMap.get((Integer) dialogId);
        if (dialogExists != null || dialogExists) {
            onAfterExecute();
            cleanUp();
    }

    public boolean cleanUp() {
        mActivity.removeDialog(dialogId);
        mActivity.mDialogMap.remove((Integer) dialogId);
        mActivity.cleanupTask();
        detach();
        return true;
    }
}

4

Хтось із Google надав якесь "офіційне рішення"?

Так.

Рішення - це скоріше пропозиція щодо архітектури додатків, а не лише якийсь код .

Вони запропонували 3 шаблони дизайну, які дозволяють додатку працювати в синхронізації з сервером, незалежно від стану програми (він буде працювати, навіть якщо користувач закінчує додаток, користувач змінює екран, додаток припиняється, кожен інший можливий стан, де фонова інформація може бути перервана, вона охоплює її)

Пропозиція пояснюється у виступі програм для клієнтів Android REST під час вводу / виводу Google у Вергілія Добянчі. Це тривалість 1 годину, але її надзвичайно варто переглянути.

Основою цього є абстрагування мережевих операцій до функції, Serviceяка працює незалежно від будь-якої Activityпрограми. Якщо ви працюєте з базами даних, використання ContentResolverта Cursorнадасть вам вичерпний шаблон спостерігача, який зручно оновлювати інтерфейс користувача без будь-якої додаткової логіки, як тільки ви оновили свою локальну базу даних із отриманими віддаленими даними. Будь-який інший код після операції буде виконуватися за допомогою зворотного дзвінка, переданого в ServiceResultReceiverдля цього використовую підклас).

У будь-якому випадку, моє пояснення насправді досить розпливчасте, вам слід безперечно стежити за промовою.


2

Хоча відповідь Марка (CommonsWare) дійсно працює для зміни орієнтації, вона не вдається, якщо Діяльність буде знищена безпосередньо (як у випадку телефонного дзвінка).

Ви можете керувати змінами орієнтації І рідкісними зруйнованими подіями діяльності, використовуючи об’єкт Application для посилання на ваш ASyncTask.

Там відмінне пояснення проблеми і рішення тут :

Кредит повністю йде на Райана за те, що він розібрався.


1

Через 4 роки Google вирішив проблему, просто зателефонувавши setRetainInstance (true) в Activity onCreate. Він збереже ваш екземпляр активності під час обертання пристрою. У мене також є просте рішення для старих Android.


1
Проблема, яка спостерігається за людьми, трапляється через те, що Android знищує клас активності при повороті, розширенні клавіатури та інших подіях, але завдання асинхронізації все ще зберігає посилання на знищений екземпляр і намагається використовувати його для оновлення інтерфейсу користувача. Ви можете доручити Android не руйнувати діяльність ні в маніфесті, ні в прагматиці. У цьому випадку посилання завдання асинхронізації залишається дійсною і жодних проблем не спостерігається. Оскільки обертання може вимагати додаткової роботи, як перезавантаження переглядів тощо, Google не рекомендує зберігати активність. Тож ти вирішиш.
Singagirl

Дякую, я знав про ситуацію, але не про setRetainInstance (). Чого я не розумію, це ваше твердження, що Google використовував це для вирішення питань, поставлених у питанні. Чи можете ви зв’язати джерело інформації? Дякую.
jj_


onRetainNonConfigurationInstance () Ця функція називається виключно оптимізацією, і ви не повинні покладатися на її виклик. <З того самого джерела: developer.android.com/reference/android/app/…
Dhananjay M

0

ви повинні викликати всі дії, використовуючи обробник активності. Отже, якщо ви знаходитесь у якійсь темі, вам слід створити Runnable та розмістити за допомогою обробника Activitie. В іншому випадку ваш додаток може вийти з ладу іноді, за винятком фатальних випадків.


0

Це моє рішення: https://github.com/Gotchamoh/Android-AsyncTask-ProgressDialog

В основному кроки:

  1. Я використовую onSaveInstanceStateдля збереження завдання, якщо воно ще обробляється.
  2. У onCreateI отримати завдання , якщо воно було збережено.
  3. У onPauseя відкидаю, ProgressDialogякщо це показано.
  4. У onResumeI показати , ProgressDialogякщо завдання все ще обробляється.
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.