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 세트를 사용하는 경우 연속을 사용하여 함께 연결할 수 있습니다. 이렇게 하면 깊이 중첩된 콜백을 방지하고 여러 개의 연결된 작업의 오류 처리를 통합할 수 있습니다.

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

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 }

다음 단계