Task API ها

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

مدیریت نتایج وظایف

بسیاری از APIها در سرویس‌های Google Play و Firebase یک شیء Task برای نمایش عملیات ناهمزمان برمی‌گردانند. برای مثال، FirebaseAuth.signInAnonymously() یک Task<AuthResult> برمی‌گرداند که نشان دهنده نتیجه عملیات ورود به سیستم است. Task<AuthResult> نشان می‌دهد که وقتی وظیفه با موفقیت انجام شود، یک شیء AuthResult برمی‌گرداند.

شما می‌توانید نتیجه یک Task را با اتصال شنونده‌هایی (Listeners) که به اتمام موفقیت‌آمیز، شکست یا هر دو پاسخ می‌دهند، مدیریت کنید:

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

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

وظایف زنجیره‌ای

اگر از مجموعه‌ای از APIها استفاده می‌کنید که اشیاء Task را در یک تابع پیچیده برمی‌گردانند، می‌توانید آن‌ها را با استفاده از continueها به هم زنجیر کنید. این به شما کمک می‌کند تا از callbackهای تو در تو جلوگیری کنید و مدیریت خطا را برای چندین وظیفه زنجیر شده، یکپارچه کنید.

برای مثال، سناریویی را در نظر بگیرید که در آن متدی به نام 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 به گونه‌ای طراحی شده است که با سایر الگوهای برنامه‌نویسی ناهمزمان رایج اندروید به خوبی کار کند. می‌توان آن را به/از سایر الگوهای اولیه مانند ListenableFuture و کوروتین‌های Kotlin که توسط AndroidX توصیه می‌شوند ، تبدیل کرد و به شما این امکان را می‌دهد که از رویکردی که به بهترین وجه با نیازهای شما مطابقت دارد، استفاده کنید.

در اینجا مثالی با استفاده از یک Task آورده شده است:

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

کوروتین کاتلین

برای استفاده از کوروتین‌های کاتلین با Task ، وابستگی زیر را به پروژه خود اضافه کنید و سپس از قطعه کد برای تبدیل از یک Task استفاده کنید.

گردل ( 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 استفاده کنید.

گردل ( 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)
)

Observable در RxJava2

علاوه بر کتابخانه‌ی ناهمگام نسبی مورد نظر، وابستگی زیر را به پروژه‌ی خود اضافه کنید و سپس از قطعه کد برای تبدیل از یک Task استفاده کنید.

گردل ( 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 }

مراحل بعدی