Я уже несколько месяцев исследую эту проблему, придумаю разные решения, которые меня не устраивают, так как все они массивные хаки. Я до сих пор не могу поверить, что класс с ошибками в дизайне превратил его в фреймворк, и никто об этом не говорит, так что, наверное, я что-то упустил.
Проблема с AsyncTask
. Согласно документации это
«позволяет выполнять фоновые операции и публиковать результаты в потоке пользовательского интерфейса без необходимости манипулировать потоками и / или обработчиками».
Затем пример продолжает показывать, как showDialog()
вызывается какой-то примерный метод onPostExecute()
. Это, однако, кажется мне полностью надуманным , потому что для отображения диалога всегда нужна ссылка на допустимое Context
, а AsyncTask никогда не должен содержать сильную ссылку на объект контекста .
Причина очевидна: что, если действие будет уничтожено, что вызвало задачу? Это может происходить постоянно, например, потому что вы перевернули экран. Если задача будет содержать ссылку на контекст, который ее создал, вы не только держитесь за бесполезный объект контекста (окно будет разрушено и любое взаимодействие с пользовательским интерфейсом завершится ошибкой!), Вы даже рискуете создать утечка памяти.
Если моя логика здесь не лишена недостатков, это будет означать: onPostExecute()
совершенно бесполезно, потому что какая польза для этого метода в потоке пользовательского интерфейса, если у вас нет доступа к какому-либо контексту? Вы не можете сделать ничего значимого здесь.
Одним из обходных путей может быть не передача экземпляров контекста в AsyncTask, а Handler
экземпляр. Это работает: поскольку обработчик свободно связывает контекст и задачу, вы можете обмениваться сообщениями между ними, не рискуя утечкой (верно?). Но это будет означать, что предпосылка AsyncTask, а именно то, что вам не нужно беспокоиться о обработчиках, неверна. Это также похоже на злоупотребление Handler, поскольку вы отправляете и получаете сообщения в одном потоке (вы создаете его в потоке пользовательского интерфейса и отправляете через него в onPostExecute (), который также выполняется в потоке пользовательского интерфейса).
Чтобы завершить все это, даже с этим обходным приемом, у вас все еще есть проблема, что, когда контекст разрушается, у вас нет записи о задачах, которые он выполнял. Это означает, что вам нужно перезапускать любые задачи при воссоздании контекста, например, после изменения ориентации экрана. Это медленно и расточительно.
Мое решение этого (как реализовано в библиотеке Droid-Fu ) состоит в том, чтобы поддерживать отображение WeakReference
s из имен компонентов в их текущие экземпляры в уникальном объекте приложения. Всякий раз, когда AsyncTask запускается, он записывает вызывающий контекст в этой карте и при каждом обратном вызове извлекает текущий экземпляр контекста из этого сопоставления. Это гарантирует, что вы никогда не будете ссылаться на экземпляр устаревшего контекста, и у вас всегда будет доступ к действительному контексту в обратных вызовах, чтобы вы могли выполнять там значимую работу пользовательского интерфейса. Он также не пропускает, потому что ссылки слабые и очищаются, когда больше нет экземпляров данного компонента.
Тем не менее, это сложный обходной путь и требует подкласса некоторых классов библиотеки Droid-Fu, что делает этот подход довольно навязчивым.
Теперь я просто хочу знать: я просто что-то упускаю или AsyncTask действительно полностью испорчен? Как ваш опыт работы с ним? Как вы решили эту проблему?
Спасибо за ваш вклад.
Ответы:
Как насчет чего-то вроде этого:
источник
Вручную отсоединить деятельность от
AsyncTask
вonDestroy()
. Вручную повторно связать новую активность наAsyncTask
вonCreate()
. Для этого требуется либо статический внутренний класс, либо стандартный класс Java, а также, возможно, 10 строк кода.источник
Похоже,
AsyncTask
это нечто большее, чем просто концептуальные недостатки . Это также невозможно использовать из-за проблем совместимости. Документы для Android гласят:При первом представлении AsyncTasks выполнялись последовательно в одном фоновом потоке. Начиная с DONUT, это было изменено на пул потоков, позволяющий нескольким задачам работать параллельно. При запуске HONEYCOMB задачи возвращаются к выполнению в одном потоке, чтобы избежать распространенных ошибок приложения, вызванных параллельным выполнением. Если вы действительно хотите параллельное выполнение, вы можете использовать
executeOnExecutor(Executor, Params...)
версию этого метода сTHREAD_POOL_EXECUTOR
; однако, см. комментарий там для предупреждений относительно его использования.Оба
executeOnExecutor()
иTHREAD_POOL_EXECUTOR
являются Добавлено в уровне API 11 (Android 3.0.x, Honeycomb).Это означает, что если вы создадите два параметра
AsyncTask
s для загрузки двух файлов, вторая загрузка не начнется, пока не закончится первый. Если вы общаетесь через два сервера, а первый сервер не работает, вы не сможете подключиться ко второму, пока не истечет время подключения к первому. (Конечно, если вы не используете новые функции API11, но это сделает ваш код несовместимым с 2.x).И если вы хотите нацелиться на 2.x и 3.0+, материал становится действительно сложным.
Кроме того, документы говорят:
Предостережение. Другая проблема, с которой вы можете столкнуться при использовании рабочего потока, - непредвиденные перезапуски в вашей деятельности из-за изменения конфигурации среды выполнения (например, когда пользователь меняет ориентацию экрана), что может разрушить ваш рабочий поток . Чтобы увидеть, как вы можете сохранить свою задачу во время одного из этих перезапусков и как правильно отменить задачу, когда действие уничтожено, см. Исходный код примера приложения Shelves.
источник
Вероятно, мы все, включая Google, злоупотребляем
AsyncTask
с точки зрения MVC .Activity - это Controller , и контроллер не должен запускать операции, которые могут пережить View . То есть AsyncTasks следует использовать из Model , из класса, который не связан с жизненным циклом Activity - помните, что Activity уничтожаются при ротации. (Что касается View , вы обычно не программируете классы, производные от, например, android.widget.Button, но можете. Обычно единственное, что вы делаете в View - это xml.)
Другими словами, неправильно размещать производные AsyncTask в методах Деятельности. OTOH, если мы не должны использовать AsyncTasks в Деятельности, AsyncTask теряет свою привлекательность: раньше его рекламировали как быстрое и простое решение.
источник
Я не уверен, что это правда, что вы рискуете утечкой памяти со ссылкой на контекст из AsyncTask.
Обычный способ их реализации - создание нового экземпляра AsyncTask в рамках одного из методов Activity. Таким образом, если действие уничтожено, то, как только AsyncTask завершит свою работу, не будет ли он недоступен и пригоден для сбора мусора? Таким образом, ссылка на действие не имеет значения, потому что сама AsyncTask не будет зависать.
источник
Было бы надежнее сохранить WeekReference в вашей деятельности:
источник
Почему бы просто не переопределить
onPause()
метод в собственном Activity и отменить егоAsyncTask
оттуда?источник
onPause
том смысле, что он так же плох, как и в другом месте? Т.е. вы могли бы получить ANR?onPause
или другое), потому что мы рискуем получить ANR.Вы абсолютно правы - поэтому движение от использования асинхронных задач / загрузчиков в действиях для извлечения данных набирает обороты. Одним из новых способов является использование инфраструктуры Volley, которая, по сути, обеспечивает обратный вызов после того, как данные будут готовы - гораздо более совместимый с моделью MVC. Залп был популяризирован в Google I / O 2013. Не уверен, почему больше людей не знают об этом.
источник
Лично я просто расширяю Thread и использую интерфейс обратного вызова для обновления пользовательского интерфейса. Я никогда не мог заставить AsyncTask работать правильно без проблем с FC. Я также использую неблокирующую очередь для управления пулом выполнения.
источник
Я думал, что отмена работает, но это не так.
вот они RTFMing об этом:
«Если задача уже запущена, параметр mayInterruptIfRunning определяет, следует ли прерывать поток, выполняющий эту задачу, в попытке остановить задачу».
Однако это не означает, что поток прерывается. Это Java, а не AsyncTask ".
http://groups.google.com/group/android-developers/browse_thread/thread/dcadb1bc7705f1bb/add136eb4949359d?show_docid=add136eb4949359d
источник
Лучше было бы думать об AsyncTask как о чем-то, что более тесно связано с Activity, Context, ContextWrapper и т. Д. Удобнее, когда его область действия полностью понятна.
Убедитесь, что у вас есть политика отмены в вашем жизненном цикле, так что в конечном итоге она будет собирать мусор и больше не будет хранить ссылку на вашу деятельность, и это тоже может быть сборщиком мусора.
Не отменяя вашу AsyncTask при обходе вашего контекста, вы столкнетесь с утечками памяти и NullPointerExceptions, если вам просто нужно предоставить отзыв, такой как простое диалоговое окно Toast, то единственный контекст вашего Application Context поможет избежать проблемы NPE.
AsyncTask не так уж и плох, но определенно происходит много магии, которая может привести к непредвиденным ошибкам.
источник
Что касается «опыта работы с ним»: это возможно , чтобы убить процесс вместе со всеми AsyncTasks, Android будет заново создать стек активности , так что пользователь не будет ничего упоминать.
источник