Tasks API

Task API – это стандартный способ обработки асинхронных операций в сервисах Google Play. Это мощный и гибкий способ управления асинхронными вызовами, который заменяет старый шаблон PendingResult. С помощью Task вы можете объединять несколько вызовов в цепочку, обрабатывать сложные потоки и писать понятные обработчики успешных и неудачных вызовов.

Как обрабатывать результаты задач

Многие API в сервисах Google Play и Firebase возвращают объект Task, представляющий асинхронные операции. Например, FirebaseAuth.signInAnonymously() возвращает Task<AuthResult>, который представляет результат операции входа. Task<AuthResult> указывает, что после успешного выполнения задачи будет возвращен объект AuthResult.

Вы можете обработать результат Task, прикрепив прослушиватели, которые реагируют на успешное завершение, сбой или и то, и другое:

Task<AuthResult> task = FirebaseAuth.getInstance().signInAnonymously();

Чтобы обработать успешное выполнение задачи, прикрепите OnSuccessListener:

task.addOnSuccessListener(new OnSuccessListener<AuthResult>() {
    @Override
    public void onSuccess(AuthResult authResult) {
        // Task completed successfully
        // ...
    }
});

Чтобы обработать невыполненную задачу, прикрепите OnFailureListener:

task.addOnFailureListener(new OnFailureListener() {
    @Override
    public void onFailure(@NonNull Exception e) {
        // Task failed with an exception
        // ...
    }
});

Чтобы обрабатывать успешные и неудачные попытки в одном прослушивателе, подключите OnCompleteListener:

task.addOnCompleteListener(new OnCompleteListener<AuthResult>() {
    @Override
    public void onComplete(@NonNull Task<AuthResult> task) {
        if (task.isSuccessful()) {
            // Task completed successfully
            AuthResult result = task.getResult();
        } else {
            // Task failed with an exception
            Exception exception = task.getException();
        }
    }
});

Управление цепочками

По умолчанию прослушиватели, прикрепленные к Task, выполняются в основном потоке приложения (потоке UI). Это означает, что в слушателях не следует выполнять длительные операции. Если вам нужно выполнить продолжительную операцию, вы можете указать Executor, который используется для планирования прослушивателей в фоновом потоке.

// Create a new ThreadPoolExecutor with 2 threads for each processor on the
// device and a 60 second keep-alive time.
int numCores = Runtime.getRuntime().availableProcessors();
ThreadPoolExecutor executor = new ThreadPoolExecutor(numCores * 2, numCores *2,
        60L, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>());

task.addOnCompleteListener(executor, new OnCompleteListener<AuthResult>() {
    @Override
    public void onComplete(@NonNull Task<AuthResult> task) {
        // ...
    }
});

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

Если вам нужно обрабатывать результаты задач в Activity, важно управлять жизненным циклом прослушивателей, чтобы они не вызывались, когда Activity больше не видна. Для этого можно использовать слушатели на уровне действия. Эти прослушиватели автоматически удаляются при вызове метода onStop объекта Activity, чтобы они не выполнялись после остановки Activity.

Activity activity = MainActivity.this;
task.addOnCompleteListener(activity, new OnCompleteListener<AuthResult>() {
    @Override
    public void onComplete(@NonNull Task<AuthResult> task) {
        // ...
    }
});

Цепочки задач

Если вы используете набор API, которые возвращают объекты Task в сложной функции, вы можете связать их вместе с помощью продолжений. Это позволяет избежать глубоко вложенных обратных вызовов и объединяет обработку ошибок для нескольких связанных задач.

Например, у вас есть метод doSomething, который возвращает Task<String>, но требует AuthResult в качестве параметра. Вы можете получить этот AuthResult асинхронно из другого Task:

public Task<String> doSomething(AuthResult authResult) {
    // ...
}

С помощью метода Task.continueWithTask вы можете связать эти две задачи:

Task<AuthResult> signInTask = FirebaseAuth.getInstance().signInAnonymously();

signInTask.continueWithTask(new Continuation<AuthResult, Task<String>>() {
    @Override
    public Task<String> then(@NonNull Task<AuthResult> task) throws Exception {
        // Take the result from the first task and start the second one
        AuthResult result = task.getResult();
        return doSomething(result);
    }
}).addOnSuccessListener(new OnSuccessListener<String>() {
    @Override
    public void onSuccess(String s) {
        // Chain of tasks completed successfully, got result from last task.
        // ...
    }
}).addOnFailureListener(new OnFailureListener() {
    @Override
    public void onFailure(@NonNull Exception e) {
        // One of the tasks in the chain failed with an exception.
        // ...
    }
});

Как заблокировать задачу

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

try {
    // Block on a task and get the result synchronously. This is generally done
    // when executing a task inside a separately managed background thread. Doing this
    // on the main (UI) thread can cause your application to become unresponsive.
    AuthResult authResult = Tasks.await(task);
} catch (ExecutionException e) {
    // The Task failed, this is the same exception you'd get in a non-blocking
    // failure handler.
    // ...
} catch (InterruptedException e) {
    // An interrupt occurred while waiting for the task to complete.
    // ...
}

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

try {
    // Block on the task for a maximum of 500 milliseconds, otherwise time out.
    AuthResult authResult = Tasks.await(task, 500, TimeUnit.MILLISECONDS);
} catch (ExecutionException e) {
    // ...
} catch (InterruptedException e) {
    // ...
} catch (TimeoutException e) {
    // Task timed out before it could complete.
    // ...
}

Совместимость

Task хорошо работает с другими распространенными шаблонами асинхронного программирования для Android. Его можно преобразовывать в другие примитивы, такие как ListenableFuture и сопрограммы Kotlin, которые рекомендуются AndroidX, и обратно. Это позволяет использовать подход, который лучше всего соответствует вашим потребностям.

Пример с разметкой типа Task:

// ...
simpleTask.addOnCompleteListener(this) {
  completedTask -> textView.text = completedTask.result
}

Сопрограмма Kotlin

Чтобы использовать сопрограммы Kotlin с Task, добавьте в проект следующую зависимость, а затем используйте фрагмент кода для преобразования из Task.

Gradle (build.gradle на уровне модуля, обычно app/build.gradle)
// Source: https://github.com/Kotlin/kotlinx.coroutines/tree/master/integration/kotlinx-coroutines-play-services
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-play-services:1.7.3'
Описание
import kotlinx.coroutines.tasks.await
// ...
  textView.text = simpleTask.await()
}

Гуава ListenableFuture

Чтобы использовать Guava ListenableFuture с Task, добавьте в проект следующую зависимость, а затем используйте фрагмент кода для преобразования из Task.

Gradle (build.gradle на уровне модуля, обычно app/build.gradle)
implementation "androidx.concurrent:concurrent-futures:1.2.0"
Описание
import com.google.common.util.concurrent.ListenableFuture
// ...
/** Convert Task to ListenableFuture. */
fun <T> taskToListenableFuture(task: Task<T>): ListenableFuture<T> {
  return CallbackToFutureAdapter.getFuture { completer ->
    task.addOnCompleteListener { completedTask ->
      if (completedTask.isCanceled) {
        completer.setCancelled()
      } else if (completedTask.isSuccessful) {
        completer.set(completedTask.result)
      } else {
        val e = completedTask.exception
        if (e != null) {
          completer.setException(e)
        } else {
          throw IllegalStateException()
        }
      }
    }
  }
}
// ...
this.listenableFuture = taskToListenableFuture(simpleTask)
this.listenableFuture?.addListener(
  Runnable {
    textView.text = listenableFuture?.get()
  },
  ContextCompat.getMainExecutor(this)
)

RxJava2 Observable

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

Gradle (build.gradle на уровне модуля, обычно app/build.gradle)
// Source: https://github.com/ashdavies/rx-tasks
implementation 'io.ashdavies.rx.rxtasks:rx-tasks:2.2.0'
Описание
import io.ashdavies.rx.rxtasks.toSingle
import java.util.concurrent.TimeUnit
// ...
simpleTask.toSingle(this).subscribe { result -> textView.text = result }

Дальнейшие действия