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

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

مراحل بعدی