Як обробляти AsyncTask під час обертання екрана?


88

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

Здається, можливостей дуже багато, але я не з’ясував, який із них найкраще підходить для отримання результатів AsyncTask.

У мене є деякі AsyncTasks, які просто запускаються знову і викликають isFinishing()метод діяльності, і якщо діяльність закінчується, вони не будуть нічого оновлювати.

Проблема полягає в тому, що у мене є одне завдання, яке робить запит до веб-служби, який може вийти з ладу або досягти успіху, і перезапуск завдання призведе до фінансових втрат для користувача.

Як би ви це вирішили? Які переваги чи недоліки можливих рішень?


1
Дивіться мою відповідь тут . Ви також можете знайти цю інформацію про те, що setRetainInstance(true)насправді робить корисним.
Timmmm

я б просто реалізував локальну службу, яка виконує обробку (у потоці), яку робить ваш asyncTask. Щоб відобразити результати, передайте дані у свою активність. Тепер діяльність відповідає лише за показ даних, і обробка ніколи не переривається обертанням екрана.
Хтось десь

А як щодо використання AsyncTaskLoader замість AsyncTask ??
Sourangshu Biswas

Відповіді:


6

Моя перша пропозиція полягала б у тому, щоб переконатися, що вам дійсно потрібно скинути свою активність при обертанні екрана (поведінка за замовчуванням). Кожного разу, коли у мене виникали проблеми з ротацією, я додавав цей атрибут до свого <activity>тегу в AndroidManifest.xml, і мені було просто добре.

android:configChanges="keyboardHidden|orientation"

Це виглядає дивно, але те, що він робить, передає вашому onConfigurationChanged()методу, якщо ви не надаєте його, він просто не робить нічого іншого, як переміряти макет, що, здається, є цілком адекватним способом обробки обертання більшу частину часу .


5
Але це завадить активності змінювати макет. І тому змушує користувача користуватися своїм пристроєм у певній орієнтації, продиктованій вашим додатком, а не його потребами.
Януш

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

38
Ну ось це, Ромен. "якщо ви хочете, щоб ваш макет або малюнки, рядки чи що-небудь відрізнялися в портретному та альбомному режимах, вам потрібна поведінка за замовчуванням", я вважаю, що це набагато рідше варіант використання, ніж ви передбачаєте. Я вважаю, що ви називаєте "дуже конкретними випадками" більшості розробників. Використання відносних макетів, які працюють у всіх вимірах, є найкращою практикою, і це не так складно. Розмови про лінощі вводяться в оману, ці методи покращують взаємодію з користувачем, а не скорочують час розробки.
Джим Блеклер,

2
Я виявив, що це ідеально підходить для LinearLayout, але при використанні RelativeLayout він не перемальовує макет правильно при переході в альбомний режим (принаймні, не на N1). Дивіться ці питання: stackoverflow.com/questions/2987049 / ...
JohnRock

9
Я згоден з Роменом (він знає, про що говорить, розробляє ОС). Що трапляється, коли ви хочете перенести свою програму на планшет, а ваш інтерфейс виглядає жахливо при розтягуванні? Якщо ви підходите до цієї відповіді, вам потрібно буде перекодувати все своє рішення, тому що ви пішли на цей ледачий хак.
Остін Махоні

46

Ви можете перевірити, як я обробляю AsyncTaskзміни та орієнтацію на сайті code.google.com/p/shelves . Існують різні способи зробити це. Я вибрав у цьому додатку скасувати будь-яке запущене в даний час завдання, зберегти його стан і запустити нове із збереженим станом при створенні нового Activity. Це легко зробити, це працює добре, і в якості бонусу він піклується про зупинку ваших завдань, коли користувач залишає програму.

Ви також можете використовувати, onRetainNonConfigurationInstance()щоб передати свій AsyncTaskновий Activity(будьте обережні, щоб не пропустити попередній Activityтаким чином.)


1
я спробував, і обертання під час переривання пошуку книги дає мені менше результатів, ніж коли не обертається, дуже погано
max4ever

1
Я не зміг знайти жодного використання AsyncTask у цьому коді. Існує клас UserTask, який виглядає схожим. Цей проект передує AsyncTask?
devconsole

7
AsyncTask надійшов від UserTask. Спочатку я написав UserTask для власних програм, а згодом перетворив його на AsyncTask. Вибачте, що я забув, що його перейменовано.
Ромен Гай

@RomainGuy Привіт, сподіваюся, ти добре. Відповідно до вашого коду, 2 запити надсилаються на сервер, хоча спочатку завдання скасовується, але воно успішно не скасовується. Не знаю чому. Скажіть, будь ласка, чи є спосіб вирішити це.
iamcrypticcoder

10

Це найцікавіше питання, яке я бачив щодо Android !!! Власне, я вже шукав рішення протягом останніх місяців. Досі не вирішено.

Будьте обережні, просто перевизначивши

android:configChanges="keyboardHidden|orientation"

речей недостатньо.

Розглянемо випадок, коли користувач отримує телефонний дзвінок під час роботи AsyncTask. Ваш запит вже обробляється сервером, тому AsyncTask чекає відповіді. У цей момент ваш додаток переходить у фоновий режим, оскільки додаток Телефон щойно вийшов на перший план. ОС може вбити вашу активність, оскільки вона знаходиться у фоновому режимі.


6

Чому б вам не завжди залишати посилання на поточний AsyncTask в Singleton, наданому Android?

Щоразу, коли завдання починається, на PreExecute або на конструкторі, ви визначаєте:

((Application) getApplication()).setCurrentTask(asyncTask);

Щоразу, коли він закінчується, ви встановлюєте для нього значення null.

Таким чином, у вас завжди є посилання, яке дозволяє вам робити щось на зразок onCreate або onResume відповідно до вашої конкретної логіки:

this.asyncTaskReference = ((Application) getApplication()).getCurrentTask();

Якщо це нуль, ви знаєте, що на даний момент жоден не працює!

:-)


Чи вдасться це? Хтось це тестував? Чи все одно завдання буде вбито системою, якщо трапиться переривання телефонного дзвінка або якщо ми перейдемо до нової діяльності, а потім повернемося назад?
Роберт

6
Applicationекземпляр має власний життєвий цикл - його також може вбити ОС, тому це рішення може спричинити важко відтворювану помилку.
Віт Худенко

7
Я подумав: якщо додаток буде вбито, буде вбито весь додаток (і, отже, всі AsyncTasks теж)?
manmal

Я думаю, що додаток можна вбити, не будучи всіма асинктасками (дуже рідко). Але @Arhimed за допомогою декількох простих перевірок на початку та в кінці кожного асинктаску ви можете уникнути помилок.
neteinstein

5

Найбільш правильним способом цього є використання фрагмента для збереження екземпляра асинхронного завдання над обертаннями.

Ось посилання на дуже простий приклад, що полегшує наслідування інтеграції цієї техніки у ваші програми.

https://gist.github.com/daichan4649/2480065


Ось ще один підручник, який використовує збережені фрагменти: blogactivity.wordpress.com/2011/09/01/proper-use-of-asynctask
devconsole


3

З моєї точки зору, краще зберігати асинктаск, onRetainNonConfigurationInstanceвід'єднавши його від поточного об'єкта Activity та прив'язавши до нового об'єкта Activity після зміни орієнтації. Тут я знайшов дуже гарний приклад роботи з AsyncTask та ProgressDialog.


2

Android: фонова обробка / Async Opeartion зі зміною конфігурації

Щоб підтримувати стан асинхронної роботи під час фонового процесу: ви можете взяти за допомогою фрагменти.

Дивіться наступні кроки:

Крок 1: Створіть фрагмент без заголовка, скажімо, фонове завдання, і додайте приватний клас асинхронного завдання з ним.

Крок 2 (Необов’язковий крок): якщо ви хочете встановити курсор завантаження поверх своєї діяльності, використовуйте код нижче:

Крок 3: У вашій основній діяльності реалізуйте інтерфейс BackgroundTaskCallbacks, визначений у кроці 1

class BackgroundTask extends Fragment {
public BackgroundTask() {

}

// Add a static interface 

static interface BackgroundTaskCallbacks {
    void onPreExecute();

    void onCancelled();

    void onPostExecute();

    void doInBackground();
}

private BackgroundTaskCallbacks callbacks;
private PerformAsyncOpeation asyncOperation;
private boolean isRunning;
private final String TAG = BackgroundTask.class.getSimpleName();

/**
 * Start the async operation.
 */
public void start() {
    Log.d(TAG, "********* BACKGROUND TASK START OPERATION ENTER *********");
    if (!isRunning) {
        asyncOperation = new PerformAsyncOpeation();
        asyncOperation.execute();
        isRunning = true;
    }
    Log.d(TAG, "********* BACKGROUND TASK START OPERATION EXIT *********");
}

/**
 * Cancel the background task.
 */
public void cancel() {
    Log.d(TAG, "********* BACKGROUND TASK CANCEL OPERATION ENTER *********");
    if (isRunning) {
        asyncOperation.cancel(false);
        asyncOperation = null;
        isRunning = false;
    }
    Log.d(TAG, "********* BACKGROUND TASK CANCEL OPERATION EXIT *********");
}

/**
 * Returns the current state of the background task.
 */
public boolean isRunning() {
    return isRunning;
}

/**
 * Android passes us a reference to the newly created Activity by calling
 * this method after each configuration change.
 */
public void onAttach(Activity activity) {
    Log.d(TAG, "********* BACKGROUND TASK ON ATTACH ENTER *********");
    super.onAttach(activity);
    if (!(activity instanceof BackgroundTaskCallbacks)) {
        throw new IllegalStateException(
                "Activity must implement the LoginCallbacks interface.");
    }

    // Hold a reference to the parent Activity so we can report back the
    // task's
    // current progress and results.
    callbacks = (BackgroundTaskCallbacks) activity;
    Log.d(TAG, "********* BACKGROUND TASK ON ATTACH EXIT *********");
}

public void onCreate(Bundle savedInstanceState) {
    Log.d(TAG, "********* BACKGROUND TASK ON CREATE ENTER *********");
    super.onCreate(savedInstanceState);
    // Retain this fragment across configuration changes.
    setRetainInstance(true);
    Log.d(TAG, "********* BACKGROUND TASK ON CREATE EXIT *********");
}

public void onDetach() {
    super.onDetach();
    callbacks = null;
}

private class PerformAsyncOpeation extends AsyncTask<Void, Void, Void> {
    protected void onPreExecute() {
        Log.d(TAG,
                "********* BACKGROUND TASK :-> ASYNC OPERATION :- > ON PRE EXECUTE ENTER *********");
        if (callbacks != null) {
            callbacks.onPreExecute();
        }
        isRunning = true;
        Log.d(TAG,
                "********* BACKGROUND TASK :-> ASYNC OPERATION :- > ON PRE EXECUTE EXIT *********");
    }

    protected Void doInBackground(Void... params) {
        Log.d(TAG,
                "********* BACKGROUND TASK :-> ASYNC OPERATION :- > DO IN BACKGROUND ENTER *********");
        if (callbacks != null) {
            callbacks.doInBackground();
        }
        Log.d(TAG,
                "********* BACKGROUND TASK :-> ASYNC OPERATION :- > DO IN BACKGROUND EXIT *********");
        return null;
    }

    protected void onCancelled() {
        Log.d(TAG,
                "********* BACKGROUND TASK :-> ASYNC OPERATION :- > ON CANCEL ENTER *********");
        if (callbacks != null) {
            callbacks.onCancelled();
        }
        isRunning = false;
        Log.d(TAG,
                "********* BACKGROUND TASK :-> ASYNC OPERATION :- > ON CANCEL EXIT *********");
    }

    protected void onPostExecute(Void ignore) {
        Log.d(TAG,
                "********* BACKGROUND TASK :-> ASYNC OPERATION :- > ON POST EXECUTE ENTER *********");
        if (callbacks != null) {
            callbacks.onPostExecute();
        }
        isRunning = false;
        Log.d(TAG,
                "********* BACKGROUND TASK :-> ASYNC OPERATION :- > ON POST EXECUTE EXIT *********");
    }
}

public void onActivityCreated(Bundle savedInstanceState) {
    super.onActivityCreated(savedInstanceState);
    setRetainInstance(true);
}

public void onStart() {
    super.onStart();
}

public void onResume() {
    super.onResume();
}

public void onPause() {
    super.onPause();
}

public void onStop() {
    super.onStop();
}

public class ProgressIndicator extends Dialog {

public ProgressIndicator(Context context, int theme) {
    super(context, theme);
}

private ProgressBar progressBar;

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    requestWindowFeature(Window.FEATURE_NO_TITLE);
    setContentView(R.layout.progress_indicator);
    this.setCancelable(false);
    progressBar = (ProgressBar) findViewById(R.id.progressBar);
    progressBar.getIndeterminateDrawable().setColorFilter(R.color.DarkBlue, android.graphics.PorterDuff.Mode.SCREEN);
}

@Override
public void show() {
    super.show();
}

@Override
public void dismiss() {
    super.dismiss();
}

@Override
public void cancel() {
    super.cancel();
}

public class MyActivity extends FragmentActivity implements BackgroundTaskCallbacks,{

private static final String KEY_CURRENT_PROGRESS = "current_progress";

ProgressIndicator progressIndicator = null;

private final static String TAG = MyActivity.class.getSimpleName();

private BackgroundTask task = null;

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(//"set your layout here");
    initialize your views and widget here .............



    FragmentManager fm = getSupportFragmentManager();
    task = (BackgroundTask) fm.findFragmentByTag("login");

    // If the Fragment is non-null, then it is currently being
    // retained across a configuration change.
    if (task == null) {
        task = new BackgroundTask();
        fm.beginTransaction().add(task, "login").commit();
    }

    // Restore saved state
    if (savedInstanceState != null) {
        Log.i(TAG, "KEY_CURRENT_PROGRESS_VALUE ON CREATE :: "
                + task.isRunning());
        if (task.isRunning()) {
            progressIndicator = new ProgressIndicator(this,
                    R.style.TransparentDialog);
            if (progressIndicator != null) {
                progressIndicator.show();
            }
        }
    }
}

@Override
protected void onPause() {
    // TODO Auto-generated method stub
    super.onPause();

}

@Override
protected void onSaveInstanceState(Bundle outState) {
    // save the current state of your operation here by saying this 

    super.onSaveInstanceState(outState);
    Log.i(TAG, "KEY_CURRENT_PROGRESS_VALUE ON SAVE INSTANCE :: "
            + task.isRunning());
    outState.putBoolean(KEY_CURRENT_PROGRESS, task.isRunning());
    if (progressIndicator != null) {
        progressIndicator.dismiss();
        progressIndicator.cancel();
    }
    progressIndicator = null;
}


private void performOperation() {

            if (!task.isRunning() && progressIndicator == null) {
                progressIndicator = new ProgressIndicator(this,
                        R.style.TransparentDialog);
                progressIndicator.show();
            }
            if (task.isRunning()) {
                task.cancel();
            } else {
                task.start();
            }
        }


@Override
protected void onDestroy() {
    super.onDestroy();
    if (progressIndicator != null) {
        progressIndicator.dismiss();
        progressIndicator.cancel();
    }
    progressIndicator = null;
}

@Override
public void onPreExecute() {
    Log.i(TAG, "CALLING ON PRE EXECUTE");
}

@Override
public void onCancelled() {
    Log.i(TAG, "CALLING ON CANCELLED");
    if (progressIndicator != null) {
        progressIndicator.dismiss();
        progressIndicator.cancel();

}

public void onPostExecute() {
    Log.i(TAG, "CALLING ON POST EXECUTE");
    if (progressIndicator != null) {
        progressIndicator.dismiss();
        progressIndicator.cancel();
        progressIndicator = null;
    }
}

@Override
public void doInBackground() {
    // put your code here for background operation
}

}


1

Одне слід врахувати, чи результат AsyncTask повинен бути доступним лише для діяльності, яка розпочала завдання. Якщо так, тоді відповідь Ромена Гая найкраща. Якщо він повинен бути доступний для інших видів діяльності вашої програми, тоді onPostExecuteви можете використовувати LocalBroadcastManager.

LocalBroadcastManager.getInstance(getContext()).sendBroadcast(new Intent("finished"));

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


1

Погляньте на цю публікацію . Ця публікація передбачає виконання AsyncTask тривалої роботи та витоку пам’яті, коли обертання екрана відбувається як в одному прикладі програми. Зразок програми доступний у вихідній підробці


0

Моє рішення.

У моєму випадку у мене є ланцюжок AsyncTasks з однаковим контекстом. Діяльність мала доступ лише до першої. Щоб скасувати будь-яке запущене завдання, я зробив наступне:

public final class TaskLoader {

private static AsyncTask task;

     private TaskLoader() {
         throw new UnsupportedOperationException();
     }

     public static void setTask(AsyncTask task) {
         TaskLoader.task = task;
     }

    public static void cancel() {
         TaskLoader.task.cancel(true);
     }
}

Завдання doInBackground():

protected Void doInBackground(Params... params) {
    TaskLoader.setTask(this);
    ....
}

Діяльність onStop()або onPause():

protected void onStop() {
    super.onStop();
    TaskLoader.cancel();
}

0
@Override
protected void onSaveInstanceState(Bundle outState) {
    super.onSaveInstanceState(outState);
    final AddTask task = mAddTask;
    if (task != null && task.getStatus() != UserTask.Status.FINISHED) {
        final String bookId = task.getBookId();
        task.cancel(true);

        if (bookId != null) {
            outState.putBoolean(STATE_ADD_IN_PROGRESS, true);
            outState.putString(STATE_ADD_BOOK, bookId);
        }

        mAddTask = null;
    }
}

@Override
protected void onRestoreInstanceState(Bundle savedInstanceState) {
        super.onRestoreInstanceState(savedInstanceState);
    if (savedInstanceState.getBoolean(STATE_ADD_IN_PROGRESS)) {
        final String id = savedInstanceState.getString(STATE_ADD_BOOK);
        if (!BooksManager.bookExists(getContentResolver(), id)) {
            mAddTask = (AddTask) new AddTask().execute(id);
        }
    }
}

0

ви також можете додати android: configChanges = "клавіатура прихована | орієнтація | screenSize"

до вашого маніфестного прикладу, сподіваюся, це допоможе

 <application
    android:name=".AppController"
    android:allowBackup="true"
    android:icon="@mipmap/ic_launcher"
    android:label="@string/app_name"
    android:roundIcon="@mipmap/ic_launcher_round"
    android:supportsRtl="true"
    android:configChanges="keyboardHidden|orientation|screenSize"
    android:theme="@style/AppTheme">
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.