واجهات برمجة تطبيقات "مهام Google"

واجهة برمجة التطبيقات Task هي الطريقة العادية لمعالجة العمليات غير المتزامنة في خدمات Play من Google. ويقدّم هذا الأسلوب طريقة فعّالة ومرنة لإدارة طلبات PendingResult غير المتزامنة، ما يحلّ محلّ نمط 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، لكي لا يتم تنفيذها بعد stopped 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 للعمل بشكل جيد مع أنماط برمجة 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 }

الخطوات التالية