Подтвердить что ты не робот

Шаблон WeakReference/AsyncTask в android

У меня есть вопрос относительно этой простой часто встречающейся ситуации в android.

У нас есть основное действие, мы вызываем AsyncTask вместе со ссылкой на mainactivity, так что AsyncTask может обновлять представления в MainActivity.

Я разбиваю событие на шаги

  • MainActivity создает AyncTask, передает его ссылку на него.
  • AysncTask, запускает его работу, загружая десять файлов, например
  • Пользователь изменил ориентацию устройства. Это приводит к указанию сироты в AsyncTask
  • Когда AsyncTask завершает работу и пытается получить доступ к активности для обновления состояния, он сбой из-за нулевого указателя.

Решение для вышеперечисленного - сохранить WeakReference в AsyncTask, как рекомендовано в книге "Pro Android 4"

WeakReference<Activity> weakActivity;

in method onPostExecute

Activity activity = weakActivity.get();
if (activity != null) {
   // do your stuff with activity here
}

Как это разрешает ситуацию?

Мой вопрос, если моя асинтеза загружает десять файлов, а по завершении 5 активность перезапускается (из-за изменения ориентации), будет ли снова вызвана моя FileDownloadingTask?.

Что произойдет с предыдущей AsyncTask, которая была изначально вызвана?

Спасибо, и я прошу прощения за вопрос.

4b9b3361

Ответ 1

Как это разрешает ситуацию?

WeakReference позволяет Activity собирать мусор, поэтому у вас нет утечки памяти.

Нулевая ссылка означает, что AsyncTask не может вслепую попытаться обновить пользовательский интерфейс, который больше не привязан, что приведет к возникновению исключений (например, представление не привязано к диспетчеру окон). Конечно, вы должны проверить нулевое значение, чтобы избежать NPE.

если моя асинтеза загружает десять файлов, а после завершения 5 активность перезапускается (из-за изменения ориентации), будет ли снова вызвана моя FileDownloadingTask?

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

Что произойдет с предыдущим AsyncTask, который изначально был вызван?

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

В новом Android я подозрительно, что AsyncTask погибает вместе с Activity, который их запускал, но моя основа для подозрения заключается только в том, что демонстрация утечки памяти для RoboSpice (см. ниже) фактически не течет на моих устройствах JellyBean.

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

IntentService является лучшим (и все же относительно простым) подходом, если для вас приемлема одна рабочая нить. Используйте (local) Service, если вы хотите контролировать пул потоков, и будьте осторожны, чтобы не работать над основным потоком!

RoboSpice кажется хорошим, если вы ищете способ надежно выполнять сетевое взаимодействие в фоновом режиме (отказ от ответственности: я не пробовал, Я не являюсь аффилированным лицом). В игровом магазине есть демонстрационное приложение RoboSpice Motivations, в котором объясняется, почему вы должны использовать его, демонтируя все, что может пойти не так с AsyncTask - включая обходное решение WeakReference.

Смотрите также эту тему: Является ли AsyncTask концептуально ошибочным или я просто что-то пропустил?

Обновление:

Я создал проект github с примером загрузки с помощью IntentService для другого вопроса SO (Как исправить android.os.NetworkOnMainThreadException?), но это также актуально здесь, я думаю. У этого есть дополнительное преимущество, что, возвращая результат через onActivityResult, загрузка, которая находится в полете, когда вы поворачиваете устройство, будет доставляться к перезапущенному Activity.

Ответ 2

Класс WeakReference в основном просто запрещает JRE увеличивать опорный счетчик для данного экземпляра.

Я не буду вдаваться в управление памятью Java и ответить на ваш вопрос напрямую: WeakReference устраняет ситуацию, предоставляя AsyncTask способ узнать, сохраняет ли его родительский актив.

Само изменение ориентации автоматически не перезапустит AsyncTask. Вы должны закодировать желаемое поведение с помощью известных механизмов (onCreate/onDestroy, onSave/RestoreInstanceState).

Что касается оригинала AsyncTask, я не уверен на 100%, какой из этих вариантов произойдет:

  • Любая Java останавливает поток и предоставляет AsyncTask, потому что уничтожен единственный объект, содержащий ссылку на него (исходный Activity)
  • Или какой-то внутренний объект Java поддерживает ссылку на объект AsyncTask, блокируя сборку мусора, фактически оставляя AsyncTask законченным в фоновом режиме

В любом случае было бы хорошей практикой прервать/приостановить и перезапустить/возобновить AsyncTask вручную (или передать его новому Activity) или вместо этого использовать Service.

Ответ 3

Как это разрешает ситуацию?

Это не так.

Референту WeakReference присваивается значение null, когда сборщик мусора определяет, что референт слабо доступен. Этого не происходит, когда действие приостанавливается и не обязательно происходит немедленно, когда действие уничтожается, а среда отбрасывает все ссылки на него. Если GC не запущен, вполне возможно, что AsyncTask завершится, пока его WeakReference все еще содержит ссылку на мертвую активность.

Не только это, но этот подход не делает ничего, чтобы предотвратить AsyncTask от бесполезного потребления процессора.

Лучший подход заключается в том, чтобы Activity поддерживать сильную ссылку на AsyncTask и cancel(...) в соответствующем метод продолжительного цикла разрывов. AsyncTask должен отслеживать isCancelled() и перестать работать, если он больше не нужен.

Если вы хотите, чтобы AsyncTask выдержал изменения конфигурации (но не другие формы уничтожения активности), вы можете разместить его в сохраненном фрагменте.