Замена AsyncTask в приложении

Класс AsyncTask в Android является крайне полезным, поскольку он предоставляет удобный интерфейс для реализации асинхронных операций.

Класс AsyncTask позволяет запускать команды в фоновом режиме и возвращать результат в основной поток. Он также сообщает о ходе выполнения задач.

Чтобы использовать AsyncTask, вы должны задать ему параметры, например: AsyncTask <TypeOfVarArgParams, ProgressValue, ResultValue>. AsyncTask запускается с помощью метода execute(). Этот метод вызывает методы doInBackground() и onPostExecute().

TypeOfVarArgParams передается в метод doInBackground() в качестве входного. ProgressValue используется для информации о ходе и ResultValue должен быть возвращен из doInBackground() и передан в onPostExecute() в качестве параметра. Метод doInBackground() содержит основной код, который должен выполняться в фоновом потоке. Метод onPostExecute() синхронизируется с потоком пользовательского интерфейса и передаёт в него результат работы. Этот метод вызывается тогда, когда метод doInBackground() прекращает выполнение операций.

Однако критическим недостатком этого класса является то, что он предназначен только для коротких операций и операций, не связанных с сетью.

Объясняется это тем, что, пока выполняются длительных по времени операции в методе класса doInBackground(), на активности могут произойти изменения вплоть до её уничтожения или смены на другую активность, что приведёт к нежелательным утечкам. Всё потому, что AsyncTask зависит от контекста активности, что и позволяет ему возвращать результат работы в UI-поток.

Напишем, к примеру, приложение, которое через AsyncTask загружает картинку из Интернета и размещает в ImageView.

Код AsyncTask-а в данном случае выглядит следующим образом:

private static class ImageDownloadTask extends AsyncTask<String, Void, Bitmap> {
  private WeakReference<ImageView> imageViewReference;

  ImageDownloadTask(ImageView imageView) {
    this.imageViewReference = new WeakReference<>(imageView);
  }

  @Override protected Bitmap doInBackground(String... strings) {
    String url = strings[0];
    Bitmap image = null;

    try {
      InputStream in = new URL(url).openStream();
      image = BitmapFactory.decodeStream(in);
    } catch (Exception e) {
      e.printStackTrace();
    }

    return image;
  }

  @Override protected void onPostExecute(Bitmap bitmap) {
    if (imageViewReference != null && bitmap != null) {
      final ImageView imageView = imageViewReference.get();
      if (imageView != null) {
        imageView.setImageBitmap(bitmap);
      }
    }
  }
}

И вызываться он будет при нажатии кнопки:

new ImageDownloadTask(imageView).execute(urlString);

Если изображение достаточно маленького размера и интернет-соединение стабильно, выполнение этих операций хоть и не желательно, но не вызовет особых проблем. Однако если изображение окажется слишком большим, то пользователь может не дождаться выполнения и сделать что-либо (свернуть приложение, перевернуть экран, запустить новый AsyncTask). В случае переворота экрана активность будет создана заново и результат операций не будет доставлен до активности, поскольку imageViewReference.get() вернёт null.

В теории реально написать приложение так, чтобы AsyncTask обрабатывал подобные случаи как положено, однако это сделает код негибким и неудобным. Поэтому проще будет воспользоваться более простыми средствами, такими как Thread и Runnable.

Так же, как и AsyncTask, класс Thread реализовывает асинхронное выполнение задач в отдельном потоке. Для того, чтобы получать доступ из потока, нужно использовать класс Handler. Handler регистрируется в потоке, в котором он был создан. Он предоставляет канал для передачи данных между UI-потоком и Thread. Данные, которые отправляются через Handler, могут быть экземплярами классов Message или Runnable. Handler особенно полезен, если вы хотите передать данные в основной поток несколько раз.

Реализуем ту же загрузку изображения этим способом.

private void imageDownloadThread() {
  Thread thread = new Thread(new Runnable() {
    @Override public void run() {
      try {
        InputStream in = new URL(urlString).openStream();
        final Bitmap bitmap = BitmapFactory.decodeStream(in);

        if (bitmap != null) {
          mUiHandler.post(new Runnable() {
            @Override public void run() {
              imageView.setImageBitmap(bitmap);
            }
          });
        }
      } catch (Exception e) {
        e.printStackTrace();
      }
    }
  });
  thread.start();
}

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

Таким образом, можно сказать, что каждый из способов полезен, если речь заходит об асинхронных операциях, однако нужно помнить, что AsyncTask подходит для единоразовых маленьких операций, не связанных с сетью, а Thread для всех остальных. Если же вам нужно будет выполнять Thread несколько раз, то лучше использовать класс HandlerThread, реализация которого была описана в одной из предыдущих статей.

Замена AsyncTask в приложении: 5 комментариев

  1. Roman

    То есть вы считаете, что экземпляр интерфейса Runnable не захватывает imageView, который в свою очередь указывает на Context – и вы считаете, что это не может привести к утечке памяти?)

  2. GeminiX

    Правильно понимаю, что у AsynTask есть преимущество в completed из коробки, в отличие от Thread?

  3. Федя

    Спасибо за статью.

    А что будет если метод run() завершит свою работу именно в тот момент, когда будет пересоздаваться активити или фрагмент и, например, imageView или какой-то другой элемент экрана, который мы хотим использовать по результату работы Thread, еще не инициализирован (null). Тогда, по идее, прорвется NullPointerException.

    Как использование такого подхода соотносится с жизненным циклом активити или фрагмента?

  4. Pavel

    почему вместо того, чтобы изобретать велосипед не использовать `AsyncTaskLoader`?

    1. Владимир

      AsyncTaskLoader имеет свои недостатки, при определенных условиях он может не выполниться никогда.

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *