میاناهای برنامه‌سازی کاربردی Task

Task API روش استاندارد برای مدیریت عملیات ناهمگام در «خدمات Google Play» است. این روشی قدرتمند و انعطاف‌پذیر برای مدیریت تماس‌های ناهمگام ارائه می‌دهد و جایگزین الگوی قدیمی‌تر PendingResult می‌شود. با Task، می‌توانید چندین تماس را زنجیره‌ای کنید، جریان‌های پیچیده را مدیریت کنید، و کنترل‌کننده‌های موفقیت و شکست واضحی بنویسید.

رسیدگی به نتایج تکلیف

بسیاری از میاناهای برنامه‌سازی کاربردی در «خدمات 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 دیگر قابل‌مشاهده نیست جلوگیری شود. برای انجام این کار، می‌توانید از شنوندگان با دامنه فعالیت استفاده کنید. این شنوندگان به‌طور خودکار وقتی متد onStop از Activity شما فراخوانی می‌شود، حذف می‌شوند تا پس‌از توقف Activity اجرا نشوند.

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

زنجیر کردن تکالیف

اگر از مجموعه‌ای از میاناهای برنامه‌سازی کاربردی استفاده می‌کنید که اشیای 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 }

مراحل بعدی