Как AsyncTask все еще может использовать Activity, если пользователь уже перешел от него?

На Android вы можете работать в отдельном AsyncTask например, с помощью Runnable или AsyncTask . В обоих случаях вам может понадобиться выполнить некоторую работу после завершения работы, например, переопределив onPostExecute() в AsyncTask . Однако пользователь может перемещаться или закрывать приложение, пока работа выполняется в фоновом режиме.

Мой вопрос: что произойдет, если пользователь перейдет или закрывает приложение, пока у меня есть ссылка на Activity пользователь только что закрыл в моей AsyncTask ?

Я предполагаю, что он должен быть уничтожен, как только пользователь переместится, однако, когда я тестирую его на устройстве по какой-то причине, я все еще могу вызывать методы в Activity даже если он уже ушел! Что здесь происходит?

Простой ответ: вы только что обнаружили

Утечки памяти

Пока какая-то часть приложения, такая как AsyncTask прежнему содержит ссылку на Activity она не будет уничтожена . Он будет придерживаться до тех пор, пока AsyncTask будет выполнен или не выпустит ссылку другим способом. Это может иметь очень плохие последствия, например, сбой вашего приложения, но худшие последствия – те, которые вы не замечаете: ваше приложение может ссылаться на Activities которые должны были быть выпущены веками назад, и каждый раз, когда пользователь делает все, На устройстве может получить все больше и больше, пока, казалось бы, из ниоткуда Android не убьет ваше приложение для того, чтобы потреблять слишком много памяти. Утечки памяти – это самые частые и худшие ошибки, которые я вижу в вопросах Android о переполнении стека


Решение

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

Вместо этого используйте шаблон слушателя и всегда используйте WeakReference . Никогда не держите ссылки на что-то вне AsyncTask .


Несколько примеров

Ссылка на View в AsyncTask

Правильно реализованная AsyncTask которая использует ImageView может выглядеть так:

 public class ExampleTask extends AsyncTask<Void, Void, Bitmap> { private final WeakReference<ImageView> mImageViewReference; public ExampleTask(ImageView imageView) { mImageViewReference = new WeakReference<>(imageView); } @Override protected Bitmap doInBackground(Void... params) { ... } @Override protected void onPostExecute(Bitmap bitmap) { super.onPostExecute(bitmap); final ImageView imageView = mImageViewReference.get(); if (imageView != null) { imageView.setImageBitmap(bitmap); } } } 

Это прекрасно иллюстрирует то, что делает WeakReference . WeakReferences позволяют Object они ссылаются, собирать мусор. Итак, в этом примере мы создаем WeakReference для ImageView в конструкторе AsyncTask . Затем в onPostExecute() который может быть вызван через 10 секунд, когда ImageView больше не существует, мы вызываем get() на WeakReference чтобы увидеть, существует ли ImageView . Пока ImageView возвращаемый ImageView get() , не является нулевым, ImageView не был собран мусором, поэтому мы можем использовать его без проблем! В то же время пользователь должен выйти из приложения, тогда ImageView получит право на сборку мусора, и если AsyncTask закончит некоторое время спустя, он увидит, что ImageView уже ушел. Нет утечек памяти, никаких проблем.


Использование слушателя

 public class ExampleTask extends AsyncTask<Void, Void, Bitmap> { public interface Listener { void onResult(Bitmap image); } private final WeakReference<Listener> mListenerReference; public ExampleTask(Listener listener) { mListenerReference = new WeakReference<>(listener); } @Override protected Bitmap doInBackground(Void... params) { ... } @Override protected void onPostExecute(Bitmap bitmap) { super.onPostExecute(bitmap); final Listener listener = mListenerReference.get(); if (listener != null) { listener.onResult(bitmap); } } } 

Это выглядит очень похоже, потому что на самом деле это очень похоже. Вы можете использовать его так, как это происходит в Activity или Fragment :

 public class ExampleActivty extends AppCompatActivity implements ExampleTask.Listener { private ImageView mImageView; ... @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); ... new ExampleTask(this).execute(); } @Override public void onResult(Bitmap image) { mImageView.setImageBitmap(image); } } 

Или вы можете использовать его так:

 public class ExampleFragment extends Fragment { private ImageView mImageView; private final ExampleTask.Listener mListener = new ExampleTask.Listener() { @Override public void onResult(Bitmap image) { mImageView.setImageBitmap(image); } }; @Override public void onViewCreated(View view, Bundle savedInstanceState) { super.onViewCreated(view, savedInstanceState); new ExampleTask(mListener).execute(); } ... } 

WeakReference и его последствия при использовании слушателя

Однако есть еще одна вещь, о которой вы должны знать. Следствием того, что у слушателя есть только WeakReference . Представьте, что вы реализуете интерфейс слушателя следующим образом:

 private static class ExampleListener implements ExampleTask.Listener { private final ImageView mImageView; private ExampleListener(ImageView imageView) { mImageView = imageView; } @Override public void onResult(Bitmap image) { mImageView.setImageBitmap(image); } } public void doSomething() { final ExampleListener listener = new ExampleListener(someImageView); new ExampleTask(listener).execute(); } 

Довольно необычный способ сделать это – я знаю, но что-то подобное может проникнуть в ваш код где-нибудь, не зная об этом, и последствия могут быть сложными для отладки. Вы уже заметили, что может быть неправильным с приведенным выше примером? Попробуйте выяснить, в противном случае продолжить чтение ниже.

Проблема проста: вы создаете экземпляр ExampleListener который содержит вашу ссылку на ImageView . Затем вы передаете его в ExampleTask и запускаете задачу. И затем заканчивается метод doSomething() , поэтому все локальные переменные становятся пригодными для сбора мусора. Нет сильной ссылки на экземпляр ExampleListener вы передали в ExampleTask , есть только WeakReference . Таким образом, ExampleListener будет собирать мусор, и когда ExampleTask ничего не завершит, ничего не произойдет. Если ExampleTask выполняется достаточно быстро, сборщик мусора, возможно, еще не собрал экземпляр ExampleListener , так что он может работать некоторое время или вообще не работать. И такие отладки могут быть кошмаром. Итак, мораль этой истории: всегда будьте в курсе ваших сильных и слабых ссылок и когда объекты становятся пригодными для сбора мусора.


Вложенные классы и использование static

Еще одна вещь, которая, вероятно, является причиной большинства утечек памяти, я вижу, что люди из Stack Overflow используют вложенные классы неправильно. Посмотрите следующий пример и попытайтесь определить причину утечки памяти в следующем примере:

 public class ExampleActivty extends AppCompatActivity { @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); ... final ImageView imageView = (ImageView) findViewById(R.id.image); new ExampleTask(imageView).execute(); } public class ExampleTask extends AsyncTask<Void, Void, Bitmap> { private final WeakReference<ImageView> mListenerReference; public ExampleTask(ImageView imageView) { mListenerReference = new WeakReference<>(imageView); } @Override protected Bitmap doInBackground(Void... params) { ... } @Override protected void onPostExecute(Bitmap bitmap) { super.onPostExecute(bitmap); final ImageView imageView = mListenerReference.get(); if (imageView != null) { imageView.setImageAlpha(bitmap); } } } } 

Вы видите это? Вот еще один пример с той же самой проблемой, она выглядит просто иначе:

 public class ExampleActivty extends AppCompatActivity { @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); ... final ImageView imageView = (ImageView) findViewById(R.id.image); final Thread thread = new Thread() { @Override public void run() { ... final Bitmap image = doStuff(); imageView.post(new Runnable() { @Override public void run() { imageView.setImageBitmap(image); } }); } }; thread.start(); } } 

Вы поняли, в чем проблема? Я ежедневно вижу людей, которые небрежно реализуют такие вещи, как, например, выше, не зная, что они делают неправильно. Эта проблема является следствием того, как Java работает на основе фундаментальной функции Java – нет оправдания, люди, которые реализуют такие вещи, как описано выше, либо пьяны, либо ничего не знают о Java. Давайте упростим проблему:

Представьте, что у вас есть вложенный класс:

 public class A { private String mSomeText; public class B { public void doIt() { System.out.println(mSomeText); } } } 

Когда вы это сделаете, вы можете получить доступ к членам класса A из класса B Вот как doIt() может печатать mSomeText , он имеет доступ ко всем членам A даже частных.
Причина, по которой вы можете сделать это, – это то, что если вы вставляете классы, подобные Java, неявно создает ссылку на A внутри B Это из-за этой ссылки и ничего другого, что у вас есть доступ ко всем членам A внутри B Однако в контексте утечек памяти, которые снова создают проблему, если вы не знаете, что делаете. Рассмотрим первый пример (я изложу все части, которые не имеют значения из примера):

 public class ExampleActivty extends AppCompatActivity { public class ExampleTask extends AsyncTask<Void, Void, Bitmap> { ... } } 

Таким образом, мы имеем AsyncTask как вложенный класс внутри Activity . Поскольку вложенный класс не является статическим, мы можем получить доступ к элементам ExampleActivity внутри ExampleTask . Здесь не имеет значения, что ExampleTask фактически не имеет доступа к каким-либо элементам из Activity , так как это ExampleTask вложенный класс Java неявно создает ссылку на Activity внутри ExampleTask и поэтому, по-видимому, нет видимой причины, мы имеем утечку памяти , Как мы можем это исправить? Очень просто. Нам просто нужно добавить одно слово, и это статично:

 public class ExampleActivty extends AppCompatActivity { public static class ExampleTask extends AsyncTask<Void, Void, Bitmap> { ... } } 

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

А что касается другого примера с Thread ? Точно такая же проблема, такие анонимные классы, как просто нестатические вложенные классы и сразу утечка памяти. Однако на самом деле это в миллион раз хуже. Со всех сторон вы смотрите на это, что пример Thread – просто ужасный код. Избегайте любой ценой.


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

Intereting Posts
Google Play Android .apk Загрузить «Не удалось запустить апап-дамп-бейджинг» Android: создание запроса Https Получение исключения IllegalArgumentException при добавлении данных линии Android: управление несколькими уведомлениями Как получить временную метку входящего сообщения xmpp? Для вызова требуется уровень API 14 (текущий минимум равен 11) Отключить сеть в эмуляторе Android, сохраняя АБР в живых Как настроить размер и ширину окна EditText программно в android Откройте еще одно приложение из нашего приложения? Включить одновременно WiFi и 3G-интерфейс на Android Отображать текст Kannada в приложении для Android Как фрагменты влияют на активность «единая, целенаправленная вещь, которую пользователь может делать»? Как отключить положительную кнопку AlertDialog во время выполнения? Android Как выполнить метод каждые х часов или минут OnCreate (Bundle savedInstanceState) всегда имеет значение null