Task API

Task API는 Google Play 서비스에서 비동기 작업을 처리하는 표준 방법입니다. 이전 PendingResult 패턴을 대체하여 비동기 호출을 관리하는 강력하고 유연한 방법을 제공합니다. Task를 사용하면 여러 호출을 체이닝하고, 복잡한 흐름을 처리하고, 명확한 성공 및 실패 핸들러를 작성할 수 있습니다.

태스크 결과 처리

Google Play 서비스 및 Firebase의 많은 API는 비동기 작업을 나타내는 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가 더 이상 표시되지 않을 때 리스너가 호출되지 않도록 리스너의 수명 주기를 관리하는 것이 중요합니다. 이렇게 하려면 활동 범위 리스너를 사용하면 됩니다. 이러한 리스너는 ActivityonStop 메서드가 호출될 때 자동으로 삭제되므로 Activity가 중지된 후에는 실행되지 않습니다.

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

태스크 체이닝

복잡한 함수에서 Task 객체를 반환하는 API 세트를 사용하는 경우 연속을 사용하여 API를 체이닝할 수 있습니다. 이렇게 하면 중첩된 콜백을 피하고 여러 체이닝된 작업의 오류 처리를 통합할 수 있습니다.

예를 들어 Task<String>을 반환하지만 AuthResult가 매개변수로 필요한 doSomething 메서드가 있는 시나리오를 생각해 보세요. 다른 Task에서 이 AuthResult를 비동기식으로 가져올 수 있습니다.

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 비동기 프로그래밍 패턴과 잘 작동하도록 설계되었습니다. ListenableFutureAndroidX에서 권장하는 Kotlin 코루틴과 같은 다른 원시형으로 변환할 수 있으므로 필요에 가장 적합한 접근 방식을 사용할 수 있습니다.

다음은 Task를 사용하는 예입니다.

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

Kotlin 코루틴

Task와 함께 Kotlin 코루틴을 사용하려면 다음 종속 항목을 프로젝트에 추가한 다음 코드 스니펫을 사용하여 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

Task와 함께 Guava ListenableFuture를 사용하려면 다음 종속 항목을 프로젝트에 추가한 다음 코드 스니펫을 사용하여 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 }

다음 단계