Android AsyncTask для длительных операций

91

Цитируя документацию для AsyncTask, найденную здесь , говорится:

В идеале AsyncTasks следует использовать для коротких операций (максимум несколько секунд). Если вам нужно поддерживать работу потоков в течение длительных периодов времени, настоятельно рекомендуется использовать различные API, предоставляемые java.util.concurrent pacakge, такие как Исполнитель, ThreadPoolExecutor и FutureTask.

Возникает вопрос: почему? В doInBackground()функции сбегает в потоке пользовательского интерфейса так , что вред есть, имея долго запущенную операцию здесь?

user1730789
источник
2
Проблема, с которой я столкнулся при развертывании приложения (использующего asyncTask) на реальном устройстве, заключалась в том, что длительная doInBackgroundфункция замораживает экран, если индикатор выполнения не используется.
venkatKA
2
Поскольку AsyncTask привязан к Activity, в которой он запущен. Таким образом, если Activity уничтожается, ваш экземпляр AsyncTask также может быть убит.
Игорь Ганапольский 07
Что, если я создам AsyncTask внутри службы? Разве это не решило бы проблему?
eddy
1
Используйте IntentService, идеальное решение для длительной работы в фоновом режиме.
Keyur Thumar 01

Ответы:

120

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

  • Они слабо привязаны к жизненному циклу деятельности
  • Они очень легко создают утечки памяти.

Внутри RoboSpice приложение мотиваций ( доступно на Google Play ) мы ответить на этот вопрос подробно. Он предоставит подробное представление об AsyncTasks, Loaders, их функциях и недостатках, а также познакомит вас с альтернативным решением для сетевых запросов: RoboSpice. Сетевые запросы являются обычным требованием в Android и по своей природе являются длительными операциями. Вот отрывок из приложения:

Жизненный цикл AsyncTask и Activity

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

И когда он завершится, AsyncTask не будет обновлять пользовательский интерфейс нового Activity. Действительно, он обновляет предыдущий экземпляр активности, который больше не отображается. Это может привести к исключению типа java.lang.IllegalArgumentException: представление, не прикрепленное к диспетчеру окон, если вы используете, например, findViewById для получения представления внутри действия.

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

Очень удобно создавать AsyncTasks как внутренние классы ваших Activity. Поскольку AsyncTask необходимо будет манипулировать представлениями Activity, когда задача завершена или выполняется, использование внутреннего класса Activity кажется удобным: внутренние классы могут напрямую обращаться к любому полю внешнего класса.

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

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


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

Я рекомендую вам загрузить приложение RoboSpice Motivations , оно действительно подробно объясняет это и предоставляет образцы и демонстрации различных способов выполнения некоторых фоновых операций.

Snicolas
источник
@Snicolas Привет. У меня есть приложение, в котором оно сканирует данные из тега NFC и отправляет их на сервер. Он отлично работает в областях с хорошими сигналами, но там, где нет сигнала, AsyncTask, который заставляет веб-вызов, продолжает работать. например, диалоговое окно прогресса длится несколько минут, а затем, когда оно исчезнет, ​​экран станет черным и не отвечает. Моя AsyncTask - это внутренний класс. Я пишу обработчик для отмены задачи через X секунд. Кажется, что приложение отправляет старые данные на сервер через несколько часов после сканирования. Может ли это быть из-за того, что AsyncTask не завершается, а затем, возможно, завершается через несколько часов? Буду признателен за любое понимание. спасибо
turtleboy
Проследите, что произойдет. Но да, это вполне возможно! Если вы правильно спроектируете свою асинтаксическую задачу, вы можете отменить ее достаточно правильно, это будет хорошей отправной точкой, если вы не хотите переходить на RS или службу ...
Сниколас
@Snicolas Спасибо за ответ. Вчера я сделал сообщение на SO, в котором изложил свою проблему и показал код обработчика, который я написал, чтобы попытаться остановить AsyncTask через 8 секунд. Не могли бы вы взглянуть, если у вас будет время? Будет ли вызов AsyncTask.cancel (true) из обработчика правильно отменить задачу? Я знаю, что должен периодически проверять значение iscancelled () в моем doInBackgroud, но я не думаю, что это применимо к моим обстоятельствам, так как я просто делаю однострочный веб-вызов HttpPost и не публикую обновления в пользовательском интерфейсе. Есть ли альтернативы AsyncTask. например, можно ли создать HttPost из IntentService
turtleboy
вот ссылка stackoverflow.com/questions/17725767/…
turtleboy
Вам следует попробовать RoboSpice (на github). ;)
Snicolas
38

Зачем ?

Потому что AsyncTaskпо умолчанию используется пул потоков, который вы не создавали . Никогда не привязывайте ресурсы из пула, который вы не создавали, поскольку вы не знаете, каковы требования к этому пулу. И никогда не привязывайте ресурсы из пула, который вы не создавали, если документация для этого пула говорит вам не делать этого, как здесь.

В частности, начиная с Android 3.2, пул потоков, используемый по AsyncTaskумолчанию (для приложений, для которых android:targetSdkVersionустановлено значение 13 или выше), имеет только один поток в нем - если вы свяжете этот поток на неопределенный срок, ни одна из ваших задач не будет выполняться.

CommonsWare
источник
Спасибо за это объяснение. Я не нашел ничего действительно ошибочного в использовании AsyncTasks для длительных операций (хотя я обычно сам делегирую их службам), но ваш аргумент о привязке этого ThreadPool (для которого вы действительно можете не делайте предположений о его размере) кажется точным. В качестве дополнения к сообщению Anup об использовании службы: эта служба должна сама запускать задачу в фоновом потоке, а не блокировать собственный основной поток. Вариантом может быть IntentService или, для более сложных требований параллелизма, применить вашу собственную стратегию многопоточности.
baske
То же самое, даже если я запускаю AsyncTask внутри службы? Я имею в виду, будет ли проблема с продолжительной работой?
eddy
1
@eddy: Да, поскольку это не меняет природы пула потоков. Для a Serviceпросто используйте a Threadили a ThreadPoolExecutor.
CommonsWare
Спасибо @CommonsWare, последний вопрос, имеет ли TimerTasks те же недостатки, что и AsyncTasks? или это совсем другое дело?
eddy
1
@eddy: TimerTaskэто стандартная Java, а не Android. TimerTaskот него отказались в пользу ScheduledExecutorService(который, несмотря на свое название, является частью стандартной Java). Ни один из них не привязан к Android, поэтому вам все равно нужна служба, если вы ожидаете, что эти вещи будут работать в фоновом режиме. И вам действительно следует подумать AlarmManager, от Android, так что вам не нужна служба, которая просто следит за тиканьем часов.
CommonsWare
4

Задача Aysnc - это специализированные потоки, которые по-прежнему предназначены для использования с графическим интерфейсом ваших приложений, но при этом сохраняют ресурсоемкие задачи потока пользовательского интерфейса. Поэтому, когда такие вещи, как обновление списков, изменение ваших представлений и т. Д., Требуют от вас выполнения некоторых операций выборки или операций обновления, вы должны использовать асинхронные задачи, чтобы вы могли держать эти операции вне потока пользовательского интерфейса, но обратите внимание, что эти операции все еще каким-то образом связаны с пользовательским интерфейсом. .

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

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

для получения дополнительной информации см. темы:

AsyncTask дольше нескольких секунд?

а также

AsyncTask не остановится, даже если действие было уничтожено

Ануп Ковкур
источник
1

Проблема с AsyncTask в том, что если он определен как нестатический внутренний класс действия, он будет иметь ссылку на действие. В сценарии, когда действие контейнера асинхронной задачи завершается, но фоновая работа в AsyncTask продолжается, объект действия не будет собираться мусором, поскольку на него есть ссылка, это вызывает утечку памяти.

Решение исправить это - определить асинхронную задачу как статический внутренний класс активности и использовать слабую ссылку на контекст.

Но все же рекомендуется использовать его для простых и быстрых фоновых задач. Чтобы разработать приложение с чистым кодом, лучше использовать RxJava для запуска сложных фоновых задач и обновления пользовательского интерфейса с их результатами.

Арнав Рао
источник