API задач

API Task — это стандартный способ обработки асинхронных операций в сервисах 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 выполняются в основном потоке приложения (пользовательском интерфейсе). Это означает, что следует избегать выполнения длительных операций в обработчиках событий. Если вам необходимо выполнить длительную операцию, вы можете указать 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 перестанет быть видимой. Для этого можно использовать слушатели, ограниченные областью действия 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()
}

Guava 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 }

Следующие шаги