Android AsyncTask для тривалих операцій


90

Цитуючи документацію для AsyncTask, знайдену тут , там сказано:

AsyncTasks в ідеалі слід використовувати для коротких операцій (не більше декількох секунд.) Якщо вам потрібно тримати потоки запущеними протягом тривалого періоду часу, настійно рекомендується використовувати різні API, надані пакетом java.util.concurrent, таким як Виконавець, ThreadPoolExecutor та FutureTask.

Тепер виникає моє запитання: чому? У doInBackground()функції збігає в потоці призначеного для користувача інтерфейсу так , що шкода є, маючи довго запущену операцію тут?


2
Проблема, з якою я зіткнувся під час розгортання програми (яка використовує asyncTask) на реальному пристрої, було те, що тривала doInBackgroundфункція заморожує екран, якщо не використовується індикатор виконання.
venkatKA

2
Оскільки AsyncTask прив’язаний до активності, в якій він запускається. Тож якщо активність буде вбито, ваш екземпляр AsyncTask також може бути вбитий.
Ігор Ганапольський

Що робити, якщо я створюю AsyncTask у службі? Хіба це не вирішить проблему?
вихровий

1
Використовуйте IntentService, ідеальне рішення для тривалої роботи у фоновому режимі.
Keyur Thumar

Відповіді:


120

Це дуже гарне питання, для програмування Android потрібен час, щоб повністю зрозуміти проблему. Справді, AsyncTask має дві основні проблеми, пов'язані між собою:

  • Вони погано прив’язані до життєвого циклу діяльності
  • Вони дуже легко створюють витоки пам'яті.

Всередині програми RoboSpice Motivations ( доступна в Google Play ) ми детально відповідаємо на це запитання. Це дасть глибокий огляд AsyncTasks, Loaders, їх особливостей та недоліків, а також познайомить вас з альтернативним рішенням для мережевих запитів: RoboSpice. Мережеві запити є загальною вимогою для Android і за своєю природою тривають операції. Ось уривок із програми:

Життєвий цикл AsyncTask та Activity

AsyncTasks не відповідають життєвому циклу екземплярів Activity. Якщо ви запустите AsyncTask всередині Activity і обернете пристрій, Activity буде знищено, і буде створено новий екземпляр. Але AsyncTask не помре. Він буде продовжувати жити, поки не завершиться.

І коли він завершиться, AsyncTask не буде оновлювати інтерфейс нової активності. Дійсно, він оновлює попередній екземпляр діяльності, який більше не відображається. Це може призвести до винятку типу java.lang.IllegalArgumentException: подання, не приєднане до диспетчера вікон, якщо ви використовуєте, наприклад, findViewById для отримання подання всередині Activity.

Проблема витоку пам'яті

Дуже зручно створювати AsyncTasks як внутрішні класи вашої діяльності. Оскільки AsyncTask потрібно буде маніпулювати поданнями Activity, коли завдання завершено або виконується, використання внутрішнього класу Activity здається зручним: внутрішні класи можуть отримувати безпосередній доступ до будь-якого поля зовнішнього класу.

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

У довгостроковій перспективі це спричиняє витік пам’яті: якщо AsyncTask триває довго, він зберігає активність «живою», тоді як Android хотів би позбутися від неї, оскільки вона більше не відображається. Діяльність не може бути зібрана сміттям, і це центральний механізм для Android для збереження ресурсів на пристрої.


Насправді дуже погана ідея використовувати AsyncTasks для тривалих операцій. Тим не менше, вони чудово підходять для короткоживучих, таких як оновлення подання через 1 або 2 секунди.

Я закликаю вас завантажити додаток RoboSpice Motivations , він справді пояснює це поглиблено, а також надає зразки та демонстрації різних способів виконання деяких фонових операцій.


@Snicolas Привіт. У мене є програма, де він сканує дані з тегу NFC і надсилає на сервер. Це чудово працює в хороших зонах сигналу, але там, де немає сигналу, AsyncTask, що робить веб-дзвінок, продовжує працювати. наприклад, діалогове вікно прогресу працює протягом хвилин, а потім, коли воно зникає, екран стає чорним і не реагує. Мій AsyncTask - це внутрішній клас. Я пишу Handler для скасування завдання через X секунд. Здається, додаток надсилає старі дані на сервер через кілька годин після сканування. Можливо, це пов’язано з тим, що AsyncTask не закінчив, а потім, можливо, завершив роботу через години? Я був би вдячний за будь-яке розуміння. спасибі
turtleboy

Простежте, щоб побачити, що станеться. Але так, це цілком можливо! Якщо ви добре розробили свій асинктаск, ви можете досить правильно його скасувати, це було б хорошою відправною точкою, якщо ви не хочете мігрувати до РС чи служби ...
Сніколас

@Snicolas Дякую за відповідь. Вчора я зробив допис на SO, в якому виклав свою проблему та показав код обробника, який я написав, щоб спробувати зупинити AsyncTask через 8 секунд. Не могли б ви поглянути, якщо встигнете? Чи буде виклик AsyncTask.cancel (true) з обробника належним чином скасувати завдання? Я знаю, що мені слід періодично перевіряти значення iscancelled () у моєму doInBackgroud, але я не думаю, що це стосується моїх обставин, оскільки я просто роблю веб-виклик HttpPost в один рядок і не публікую оновлення в інтерфейсі. Чи є альтернативи AsyncTask наприклад, чи можна зробити HttPost з IntentService
turtleboy


Вам слід спробувати RoboSpice (на github). ;)
Сніколас

38

чому?

Оскільки AsyncTaskза замовчуванням використовує пул потоків, який ви не створили . Ніколи не зв’язуйте ресурси з пулу, який ви не створили, оскільки ви не знаєте, які вимоги до цього пулу. І ніколи не зв’язуйте ресурси з пулу, який ви не створили, якщо документація для цього пулу говорить вам не робити, як у цьому випадку.

Зокрема, починаючи з Android 3.2, у пулі потоків, який використовується за AsyncTaskзамовчуванням (для програм, android:targetSdkVersionвстановлених на 13 або вище), є лише один потік - якщо ви зв'яжете цей потік на невизначений час, жодне з ваших інших завдань не буде запущено.


Дякую за це пояснення .. Я не зміг знайти нічого по-справжньому несправного у використанні AsyncTasks для тривалих операцій (хоча я зазвичай делегую це службам сам), але ваш аргумент щодо зав'язування цього ThreadPool (для якого ви справді можете не робіть жодних припущень щодо його розміру), здається спотовим. Як доповнення до допису Анупа про використання сервісу: Ця служба сама повинна також запускати завдання у фоновому потоці, а не блокувати власний основний потік. Варіантом може бути IntentService або, для більш складних вимог до паралельності, застосувати власну стратегію багатопоточності.
baske

Це те саме, навіть якщо я запускаю AsyncTask всередині служби ?? Я маю на увазі, чи тривала операція все одно буде проблемою?
вихровий

1
@eddy: Так, оскільки це не змінює природу пулу потоків. Для a Service, просто використовуйте a Threadабо a ThreadPoolExecutor.
CommonsWare

Дякуємо @CommonsWare - лише останнє питання, чи поділяють TimerTasks ті самі недоліки AsyncTasks? чи це зовсім інша річ?
вихровий

1
@eddy: TimerTaskзі стандартної Java, а не Android. TimerTaskв основному було відмовлено на користь ScheduledExecutorService(яка, незважаючи на свою назву, є частиною стандартної Java). Вони не прив'язані до Android, тому вам все одно потрібна послуга, якщо ви очікуєте, що ці речі працюватимуть у фоновому режимі. І, справді, варто врахувати AlarmManager, що стосується Android, тож вам не потрібна послуга, яка просто бовтається, просто спостерігаючи за тим, як тикає годинник.
CommonsWare

4

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

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

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

для отримання додаткової інформації див. теми:

AsyncTask довше, ніж кілька секунд?

і

AsyncTask не зупиняється, навіть коли діяльність знищена


1

Проблема AsyncTask полягає в тому, що якщо він визначений як нестатичний внутрішній клас діяльності, він матиме посилання на Activity. У сценарії, коли діяльність контейнер асинхронного завдання закінчується, але фонова робота в AsyncTask продовжується, об’єкт активності не буде зібраним сміттям, оскільки на нього є посилання, це спричиняє витік пам'яті.

Рішенням для виправлення цього є визначення асинхронного завдання як статичного внутрішнього класу активності та використання слабкого посилання на контекст.

Але все ж корисно використовувати його для простих і швидких фонових завдань. Для розробки програми з чистим кодом краще використовувати RxJava для запуску складних фонових завдань та оновлення інтерфейсу користувача з результатами від нього.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.