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

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

Проблема заключается в AsyncTask . Согласно документации

«позволяет выполнять фоновые операции и публиковать результаты в streamе пользовательского интерфейса без необходимости манипулировать streamами и / или обработчиками».

Затем в этом примере показано, как один примерный showDialog() вызывается в onPostExecute() . Это, однако, кажется мне совершенно ухищренным , потому что показ диалога всегда требует ссылки на допустимый Context , а AsyncTask никогда не должен содержать сильную ссылку на объект контекста .

Причина очевидна: что, если активность уничтожается, что вызвало задачу? Это может произойти все время, например, потому что вы перевернули экран. Если в задаче будет содержаться ссылка на контекст, который ее создал, вы не только держитесь за бесполезный объект контекста (окно будет уничтожено, и любое взаимодействие с пользовательским интерфейсом завершится с исключением!), Вы даже рискуете создать утечка памяти.

Если моя логика здесь не испорчена, это означает: onPostExecute() совершенно бесполезен, потому что полезно использовать этот метод для streamа пользовательского интерфейса, если у вас нет доступа к контексту? Здесь вы ничего не можете сделать.

Одним из способов было бы не передавать экземпляры контекста в AsyncTask, а экземпляр Handler . Это работает: поскольку Handler свободно связывает контекст и задачу, вы можете обмениваться сообщениями между ними, не рискуя утечкой (правильно?). Но это будет означать, что предпосылка AsyncTask, а именно то, что вам не нужно беспокоиться с обработчиками, неверна. Это также похоже на злоупотребление обработчиком, поскольку вы отправляете и получаете сообщения в одном streamе (вы создаете его в streamе пользовательского интерфейса и отправляете его в onPostExecute (), который также выполняется в streamе пользовательского интерфейса).

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

Мое решение для этого (как реализовано в библиотеке Droid-Fu ) заключается в том, чтобы поддерживать отображение WeakReference s от имен компонентов до их текущих экземпляров в уникальном объекте приложения. Всякий раз, когда запускается AsyncTask, он записывает вызывающий контекст на этой карте, и при каждом обратном вызове он извлекает текущий экземпляр контекста из этого сопоставления. Это гарантирует, что вы никогда не будете ссылаться на устаревший экземпляр контекста, и у вас всегда есть доступ к допустимому контексту в обратных вызовах, чтобы вы могли делать осмысленный пользовательский интерфейс там. Он также не течет, потому что ссылки слабы и очищаются, когда экземпляр данного компонента больше не существует.

Тем не менее, это сложное обходное решение и требует подclassа некоторых classов библиотеки Droid-Fu, что делает этот процесс довольно интрузивным.

Теперь я просто хочу знать: я просто просто что-то теряю, или AsyncTask действительно полностью испорчен? Как ваш опыт работает с ним? Как вы решили эту проблему?

Спасибо за ваш вклад.

Как насчет чего-то вроде этого:

 class MyActivity extends Activity { Worker mWorker; static class Worker extends AsyncTask { MyActivity mActivity; Worker(MyActivity activity) { mActivity = activity; } @Override protected Long doInBackground(URL... urls) { int count = urls.length; long totalSize = 0; for (int i = 0; i < count; i++) { totalSize += Downloader.downloadFile(urls[i]); publishProgress((int) ((i / (float) count) * 100)); } return totalSize; } @Override protected void onProgressUpdate(Integer... progress) { if (mActivity != null) { mActivity.setProgressPercent(progress[0]); } } @Override protected void onPostExecute(Long result) { if (mActivity != null) { mActivity.showDialog("Downloaded " + result + " bytes"); } } } @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); mWorker = (Worker)getLastNonConfigurationInstance(); if (mWorker != null) { mWorker.mActivity = this; } ... } @Override public Object onRetainNonConfigurationInstance() { return mWorker; } @Override protected void onDestroy() { super.onDestroy(); if (mWorker != null) { mWorker.mActivity = null; } } void startWork() { mWorker = new Worker(this); mWorker.execute(...); } } 

Причина очевидна: что, если активность уничтожается, что вызвало задачу?

Вручную AsyncTask активность от AsyncTask в onDestroy() . Вручную повторно связать новое действие с AsyncTask в onCreate() . Для этого требуется либо статический внутренний class, либо стандартный class Java, плюс, возможно, 10 строк кода.

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

При первом вводе AsyncTasks выполнялись последовательно на одном фоновом streamе. Начиная с DONUT, это было изменено на пул streamов, позволяющий нескольким задачам работать параллельно. Начиная HONEYCOMB, задачи возвращаются к выполнению в одном streamе, чтобы избежать общих ошибок приложений, вызванных параллельным выполнением. Если вам действительно требуется параллельное выполнение, вы можете использовать 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+, материал становится очень сложным.

Кроме того, в документах говорится:

Внимание. Еще одна проблема, с которой вы можете столкнуться при использовании рабочего streamа, – это непредвиденные перезагрузки в вашей деятельности из-за изменения конфигурации среды выполнения (например, когда пользователь меняет ориентацию экрана), что может привести к уничтожению рабочего streamа . Чтобы узнать, как вы можете сохранить свою задачу во время одной из этих перезапусков и как правильно отменить задачу при уничтожении действия, см. Исходный код для примера приложения Shelves.

Вероятно, мы все, включая Google, неправильно используем AsyncTask с точки зрения MVC .

Управлением является controller , и controller не должен запускать операции, которые могут пережить представление . То есть, AsyncTasks следует использовать из Model , из classа, который не связан с жизненным циклом Activity – помните, что Действия уничтожаются при вращении. (Что касается представления , вы обычно не программируете classы, полученные, например, из файла android.widget.Button, но вы можете. Как правило, единственное, что вы делаете в представлении – это xml.)

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

Я не уверен, что это правда, что вы рискуете утечкой памяти с ссылкой на контекст из AsyncTask.

Обычным способом их реализации является создание нового экземпляра AsyncTask в рамках одного из методов Activity. Итак, если активность уничтожена, то как только AsyncTask завершит работу, не будет ли она недоступна и затем будет доступна для сбора мусора? Таким образом, ссылка на активность не имеет значения, потому что сама AsyncTask не будет зависеть.

Было бы более надежно держать WeekReference в вашей деятельности:

 public class WeakReferenceAsyncTaskTestActivity extends Activity { private static final int MAX_COUNT = 100; private ProgressBar progressBar; private AsyncTaskCounter mWorker; @SuppressWarnings("deprecation") @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_async_task_test); mWorker = (AsyncTaskCounter) getLastNonConfigurationInstance(); if (mWorker != null) { mWorker.mActivity = new WeakReference(this); } progressBar = (ProgressBar) findViewById(R.id.progressBar1); progressBar.setMax(MAX_COUNT); } @Override public boolean onCreateOptionsMenu(Menu menu) { getMenuInflater().inflate(R.menu.activity_async_task_test, menu); return true; } public void onStartButtonClick(View v) { startWork(); } @Override public Object onRetainNonConfigurationInstance() { return mWorker; } @Override protected void onDestroy() { super.onDestroy(); if (mWorker != null) { mWorker.mActivity = null; } } void startWork() { mWorker = new AsyncTaskCounter(this); mWorker.execute(); } static class AsyncTaskCounter extends AsyncTask { WeakReference mActivity; AsyncTaskCounter(WeakReferenceAsyncTaskTestActivity activity) { mActivity = new WeakReference(activity); } private static final int SLEEP_TIME = 200; @Override protected Void doInBackground(Void... params) { for (int i = 0; i < MAX_COUNT; i++) { try { Thread.sleep(SLEEP_TIME); } catch (InterruptedException e) { e.printStackTrace(); } Log.d(getClass().getSimpleName(), "Progress value is " + i); Log.d(getClass().getSimpleName(), "getActivity is " + mActivity); Log.d(getClass().getSimpleName(), "this is " + this); publishProgress(i); } return null; } @Override protected void onProgressUpdate(Integer... values) { super.onProgressUpdate(values); if (mActivity != null) { mActivity.get().progressBar.setProgress(values[0]); } } } } 

Почему бы просто не переопределить метод onPause() в деле владения и отменить AsyncTask ?

Вы абсолютно правы – поэтому движение от использования асинхронных задач / загрузчиков в действиях по извлечению данных набирает обороты. Один из новых способов – использовать структуру Volley, которая по существу обеспечивает обратный вызов, как только данные будут готовы, что намного более соответствует модели MVC. Volley был популяризирован в Google I / O 2013. Не знаю, почему больше людей не знают об этом.

Лично я просто расширяю Thread и использую интерфейс обратного вызова для обновления пользовательского интерфейса. Я никогда не смог заставить AsyncTask работать без проблем с FC. Я также использую неблокирующую очередь для управления пулом выполнения.

Я думал, что отменить работы, но это не так.

здесь они RTFMing об этом:

«Если задача уже запущена, параметр mayInterruptIfRunning определяет, должен ли stream, выполняющий эту задачу, прерваться, чтобы остановить задачу».

Однако это не означает, что stream прерывается. Это вещь Java, а не вещь AsyncTask. ”

http://groups.google.com/group/android-developers/browse_thread/thread/dcadb1bc7705f1bb/add136eb4949359d?show_docid=add136eb4949359d

Вам лучше подумать о AsyncTask как о том, что более тесно связано с Activity, Context, ContextWrapper и т. Д. Это больше удобно, когда его область полностью понята.

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

Без отмены вашей AsyncTask во время перехода от вашего Контекста вы столкнетесь с утечками памяти и NullPointerExceptions, если вам просто нужно предоставить обратную связь, например, Toast, простой диалог, тогда одноэлемент вашего контекста приложений поможет избежать проблемы с NPE.

AsyncTask не так уж плох, но определенно много волшебства происходит, что может привести к некоторым непредвиденным ловушкам.

Что касается «опыта работы с ним»: можно убить процесс вместе со всеми AsyncTasks, Android заново создаст стек активности, чтобы пользователь ничего не упоминал.

Давайте будем гением компьютера.