الوصول إلى Google APIs باستخدام GoogleApiClient (متوقّف نهائيًا)

يمكنك استخدام كائن GoogleApiClient ("عميل واجهة برمجة تطبيقات Google") للوصول إلى واجهات Google APIs المقدَّمة في مكتبة "خدمات Google Play" (مثل "تسجيل الدخول بحساب Google" و"الألعاب" وDrive). يوفّر عميل Google API نقطة دخول مشتركة لخدمات Google Play ويدير اتصال الشبكة بين جهاز المستخدم وكل خدمة من خدمات Google.

ومع ذلك، فإنّ واجهة GoogleApi الأحدث وعمليات تنفيذها أسهل في الاستخدام، وهي الطريقة المفضّلة للوصول إلى واجهات برمجة تطبيقات "خدمات Play". راجع الوصول إلى Google APIs.

يوضّح هذا الدليل كيفية تنفيذ ما يلي:

  • إدارة اتصالك بخدمات Google Play تلقائيًا
  • إجراء طلبات بيانات متزامنة وغير متزامنة من واجهة برمجة التطبيقات لأي خدمة من خدمات Google Play
  • يمكنك إدارة اتصالك بخدمات Google Play يدويًا في الحالات النادرة التي يستدعي ذلك فيها ذلك. لمزيد من المعلومات، يُرجى الاطّلاع على الاتصالات المُدارة يدويًا.
الشكل 1: رسم توضيحي يوضّح كيفية توفير عميل Google API واجهة للاتصال وإجراء مكالمات مع أي من خدمات Google Play المتاحة مثل "ألعاب Google Play" وGoogle Drive.

للبدء، يجب عليك أولاً تثبيت مكتبة خدمات Google Play (النسخة رقم 15 أو أعلى) لحزمة تطوير البرامج (SDK) لنظام التشغيل Android. اتّبِع التعليمات الواردة في إعداد حزمة تطوير البرامج (SDK) لخدمات Google Play إذا لم يسبق لك إجراء ذلك.

بدء اتصال مُدار تلقائيًا

بعد ربط مشروعك بمكتبة "خدمات Google Play"، أنشِئ مثيلاً لـ GoogleApiClient باستخدام GoogleApiClient.Builder واجهات برمجة التطبيقات في طريقة onCreate() النشاط. توفِّر الفئة GoogleApiClient.Builder طرقًا تسمح لك بتحديد واجهات Google APIs التي تريد استخدامها ونطاقات OAuth 2.0 المطلوبة. إليك مثال على رمز برمجي يؤدي إلى إنشاء مثيل GoogleApiClient يرتبط بخدمة Google Drive:

GoogleApiClient mGoogleApiClient = new GoogleApiClient.Builder(this)
    .enableAutoManage(this /* FragmentActivity */,
                      this /* OnConnectionFailedListener */)
    .addApi(Drive.API)
    .addScope(Drive.SCOPE_FILE)
    .build();

يمكنك إضافة واجهات برمجة تطبيقات متعددة ونطاقات متعددة إلى GoogleApiClient نفسها عن طريق إلحاق طلبات إضافية بكل من addApi() وaddScope().

ملاحظة مُهمّة: في حال إضافة Wearable API مع واجهات برمجة تطبيقات أخرى إلى GoogleApiClient، قد تحدث أخطاء في اتصال العميل على الأجهزة التي لم يتم تثبيت تطبيق Wear OS عليها. لتجنُّب أخطاء الاتصال، استدعِ الطريقة addApiIfAvailable() وأدخِل Wearable API للسماح لعميلك بالتعامل مع واجهة برمجة التطبيقات المفقودة بسهولة. لمزيد من المعلومات، يُرجى الاطّلاع على مقالة الوصول إلى واجهة برمجة التطبيقات القابلة للارتداء.

لبدء اتصال مُدار تلقائيًا، عليك تحديد تنفيذ لواجهة OnConnectionFailedListener لتلقّي أخطاء الاتصال التي لا يمكن حلها. عندما يحاول مثيل GoogleApiClient المُدار تلقائيًا الاتصال بواجهات Google APIs، سيعرض تلقائيًا واجهة المستخدم لمحاولة إصلاح أي أعطال في الاتصال يمكن حلها (على سبيل المثال، إذا كانت خدمات Google Play بحاجة إلى التحديث). وفي حال حدوث خطأ لا يمكن حلّه، ستتلقّى مكالمة على الرقم onConnectionFailed().

يمكنك أيضًا تحديد عملية تنفيذ اختيارية لواجهة ConnectionCallbacks إذا كان تطبيقك بحاجة إلى معرفة وقت إنشاء أو تعليق الاتصال المُدار تلقائيًا. على سبيل المثال، إذا كان تطبيقك يجري استدعاءات لكتابة البيانات في Google APIs، فلا يجب استدعاء هذه البيانات إلا بعد استدعاء طريقة onConnected().

في ما يلي مثال على نشاط ينفّذ واجهات معاودة الاتصال ويضيفها إلى برنامج عميل Google API:

import com.google.android.gms.common.api.GoogleApiClient;
import com.google.android.gms.common.api.GoogleApiClient.OnConnectionFailedListener;
import gms.drive.*;
import android.support.v4.app.FragmentActivity;

public class MyActivity extends FragmentActivity
        implements OnConnectionFailedListener {
    private GoogleApiClient mGoogleApiClient;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        // Create a GoogleApiClient instance
        mGoogleApiClient = new GoogleApiClient.Builder(this)
                .enableAutoManage(this /* FragmentActivity */,
                                  this /* OnConnectionFailedListener */)
                .addApi(Drive.API)
                .addScope(Drive.SCOPE_FILE)
                .build();

        // ...
    }

    @Override
    public void onConnectionFailed(ConnectionResult result) {
        // An unresolvable error has occurred and a connection to Google APIs
        // could not be established. Display an error message, or handle
        // the failure silently

        // ...
    }
}

سيتم تلقائيًا ربط الجهاز الافتراضي GoogleApiClient بعد طلب النشاط من الهاتف onStart()، ثم يتم قطع الاتصال بعد الاتصال بـ onStop(). يمكن لتطبيقك البدء فورًا في إرسال طلبات قراءة إلى Google APIs بعد إنشاء GoogleApiClient، بدون انتظار اكتمال الاتصال.

التواصل مع خدمات Google

بعد الاتصال، يمكن للعميل إجراء طلبات قراءة وكتابة باستخدام واجهات برمجة التطبيقات الخاصة بالخدمة التي تم منح تطبيقك الإذن باستخدامها، على النحو المحدّد في واجهات برمجة التطبيقات والنطاقات التي أضفتها إلى مثيل GoogleApiClient.

ملاحظة: قبل إجراء مكالمات مع خدمات معيَّنة من Google، قد تحتاج أولاً إلى تسجيل تطبيقك في Google Play Console. للحصول على التعليمات، يمكنك الرجوع إلى دليل البدء المناسب لواجهة برمجة التطبيقات التي تستخدمها، مثل Google Drive أو تسجيل الدخول باستخدام حساب Google.

عند تنفيذ طلب قراءة أو كتابة باستخدام GoogleApiClient، يعرض برنامج واجهة برمجة التطبيقات عنصر PendingResult يمثّل الطلب. ويحدث ذلك فورًا قبل تسليم الطلب إلى خدمة Google التي يطلبها تطبيقك.

على سبيل المثال، إليك طلب لقراءة ملف من Google Drive يوفّر عنصر PendingResult:

Query query = new Query.Builder()
        .addFilter(Filters.eq(SearchableField.TITLE, filename));
PendingResult<DriveApi.MetadataBufferResult> result = Drive.DriveApi.query(mGoogleApiClient, query);

بعد أن يحتوي تطبيقك على عنصر PendingResult، يمكن لتطبيقك تحديد ما إذا كان سيتم التعامل مع الطلب كطلب غير متزامن أو كطلب متزامن.

ملاحظة: يمكن لتطبيقك إدراج طلبات القراءة في قائمة المحتوى التالي بدون ربطه بخدمات Google Play. على سبيل المثال، يمكن لتطبيقك استدعاء طُرق لقراءة ملف من Google Drive بغض النظر عمّا إذا كان مثيل GoogleApiClient قد تم ربطه بعد أم لا. بعد إنشاء الاتصال، يتم تنفيذ طلبات القراءة في قائمة الانتظار. تؤدي طلبات الكتابة إلى ظهور رسالة خطأ إذا كان تطبيقك يستدعي طرق الكتابة على "خدمات Google Play" عندما يكون عميل Google API غير متصل.

استخدام المكالمات غير المتزامنة

لجعل الطلب غير متزامن، يمكنك استدعاء setResultCallback() على PendingResult وتنفيذ واجهة ResultCallback. على سبيل المثال، إليك الطلب الذي يتم تنفيذه بشكل غير متزامن:

private void loadFile(String filename) {
    // Create a query for a specific filename in Drive.
    Query query = new Query.Builder()
            .addFilter(Filters.eq(SearchableField.TITLE, filename))
            .build();
    // Invoke the query asynchronously with a callback method
    Drive.DriveApi.query(mGoogleApiClient, query)
            .setResultCallback(new ResultCallback<DriveApi.MetadataBufferResult>() {
        @Override
        public void onResult(DriveApi.MetadataBufferResult result) {
            // Success! Handle the query result.
            // ...
        }
    });
}

عندما يتلقّى تطبيقك عنصر Result في استدعاء onResult()، يتم تقديمه كمثيل للفئة الفرعية المناسبة على النحو الذي تحدِّده واجهة برمجة التطبيقات التي تستخدمها، مثل DriveApi.MetadataBufferResult.

استخدام المكالمات المتزامنة

إذا كنت تريد تنفيذ الرمز البرمجي بترتيب محدَّد بدقة، ربما لأن نتيجة إحدى الاستدعاءات تكون مطلوبة كوسيطة إلى أخرى، يمكنك جعل طلبك متزامنًا من خلال استدعاء await() في PendingResult. يؤدي ذلك إلى حظر سلسلة التعليمات وعرض الكائن Result عند اكتمال الطلب. يتم تسليم هذا الكائن كمثيل للفئة الفرعية المناسبة على النحو المحدّد في واجهة برمجة التطبيقات التي تستخدمها، مثل DriveApi.MetadataBufferResult.

ونظرًا لأنّ طلب await() يؤدي إلى حظر سلسلة التعليمات إلى أن تصل النتيجة، يجب ألّا يُجري تطبيقك طلبات متزامنة إلى Google APIs في سلسلة محادثات واجهة المستخدم. يمكن لتطبيقك إنشاء سلسلة محادثات جديدة باستخدام عنصر AsyncTask، واستخدام سلسلة المحادثات هذه لإنشاء طلب متزامن.

يوضح المثال التالي كيفية إرسال طلب ملف إلى Google Drive كاستدعاء متزامن:

private void loadFile(String filename) {
    new GetFileTask().execute(filename);
}

private class GetFileTask extends AsyncTask {
    protected void doInBackground(String filename) {
        Query query = new Query.Builder()
                .addFilter(Filters.eq(SearchableField.TITLE, filename))
                .build();
        // Invoke the query synchronously
        DriveApi.MetadataBufferResult result =
                Drive.DriveApi.query(mGoogleApiClient, query).await();

        // Continue doing other stuff synchronously
        // ...
    }
}

الوصول إلى واجهة برمجة التطبيقات القابلة للارتداء

توفّر Wearable API قناة اتصال للتطبيقات التي تعمل على الأجهزة المحمولة والقابلة للارتداء. تتكوّن واجهة برمجة التطبيقات من مجموعة من عناصر البيانات التي يمكن للنظام إرسالها ومزامنتها، وأجهزة الاستماع التي ترسل إشعارًا إلى تطبيقاتك بالأحداث المهمة باستخدام طبقة بيانات. تتوفر واجهة برمجة التطبيقات Wearable API على الأجهزة التي تعمل بالإصدار 4.3 من نظام التشغيل Android (المستوى 18 من واجهة برمجة التطبيقات) أو الإصدارات الأحدث عند توصيل جهاز قابل للارتداء ويتم تثبيت تطبيق Wear OS المصاحب على الجهاز.

استخدام واجهة برمجة التطبيقات القابلة للارتداء بشكل مستقل

إذا كان تطبيقك يستخدم Wearable API وليس واجهات Google APIs الأخرى، يمكنك إضافة واجهة برمجة التطبيقات هذه من خلال استدعاء الطريقة addApi(). يوضّح المثال التالي كيفية إضافة Wearable API إلى مثيل GoogleApiClient:

GoogleApiClient mGoogleApiClient = new GoogleApiClient.Builder(this)
    .enableAutoManage(this /* FragmentActivity */,
                      this /* OnConnectionFailedListener */)
    .addApi(Wearable.API)
    .build();

في الحالات التي لا تتوفّر فيها Wearable API، يتعذّر على طلبات الربط التي تتضمّن Wearable API ظهور رمز الخطأ API_UNAVAILABLE.

يوضّح المثال التالي كيفية تحديد ما إذا كانت Wearable API متاحة:

// Connection failed listener method for a client that only
// requests access to the Wearable API
@Override
public void onConnectionFailed(ConnectionResult result) {
    if (result.getErrorCode() == ConnectionResult.API_UNAVAILABLE) {
        // The Wearable API is unavailable
    }
    // ...
}

استخدام واجهة برمجة التطبيقات القابلة للارتداء مع واجهات Google APIs الأخرى

إذا كان تطبيقك يستخدم Wearable API إلى جانب واجهات Google APIs الأخرى، عليك طلب الطريقة addApiIfAvailable() وإدخال Wearable API للتحقّق من توفّرها. يمكنك استخدام عملية التحقق هذه لمساعدة تطبيقك في التعامل مع الحالات التي لا تتوفّر فيها واجهة برمجة تطبيقات بشكل ملائم.

يوضّح المثال التالي كيفية الوصول إلى Wearable API مع Drive API:

// Create a GoogleApiClient instance
mGoogleApiClient = new GoogleApiClient.Builder(this)
        .enableAutoManage(this /* FragmentActivity */,
                          this /* OnConnectionFailedListener */)
        .addApi(Drive.API)
        .addApiIfAvailable(Wearable.API)
        .addScope(Drive.SCOPE_FILE)
        .build();

في المثال أعلاه، يمكن ربط GoogleApiClient مع Google Drive بنجاح بدون الاتصال بواجهة Wearable API إذا لم تكن متاحة. بعد ربط مثيل GoogleApiClient، تأكَّد من توفّر واجهة برمجة التطبيقات القابلة للارتداء قبل إجراء طلبات البيانات من واجهة برمجة التطبيقات:

boolean wearAvailable = mGoogleApiClient.hasConnectedApi(Wearable.API);

تجاهل إخفاقات اتصال واجهة برمجة التطبيقات

في حال استدعيت addApi() وتعذّر على GoogleApiClient الاتصال بواجهة برمجة التطبيقات هذه بنجاح، تتعذّر عملية الاتصال بالكامل لهذا البرنامج وتؤدي إلى معاودة الاتصال بـ onConnectionFailed().

يمكنك تسجيل تعذُّر اتصال واجهة برمجة التطبيقات ليتم تجاهله باستخدام addApiIfAvailable(). إذا تعذّر الاتصال بواجهة برمجة تطبيقات تمت إضافتها باستخدام addApiIfAvailable() بسبب خطأ لا يمكن إصلاحه (مثل API_UNAVAILABLE لـ Wear)، يتم حذف واجهة برمجة التطبيقات هذه من GoogleApiClient ويتابع العميل الاتصال بواجهات برمجة تطبيقات أخرى. ومع ذلك، في حال تعذّر إجراء أي اتصال بواجهة برمجة التطبيقات مع ظهور خطأ قابل للإصلاح (مثل الغرض من دقة موافقة OAuth)، سيتعذّر إتمام عملية ربط العميل. عند استخدام اتصال مُدار تلقائيًا، ستحاول GoogleApiClient حلّ هذه الأخطاء قدر الإمكان. عند استخدام اتصال مُدار يدويًا، يتم إرسال ConnectionResult يحتوي على نية الشراء إلى معاودة الاتصال onConnectionFailed(). يتم تجاهل حالات تعطُّل اتصال واجهة برمجة التطبيقات فقط في حال عدم توفُّر حل للفشل وإضافة واجهة برمجة التطبيقات من خلال addApiIfAvailable(). للتعرف على كيفية التعامل مع إخفاقات الاتصال اليدوي، راجع التعامل مع حالات إخفاق الاتصال.

بما أنّ واجهات برمجة التطبيقات التي تمت إضافتها باستخدام addApiIfAvailable() قد لا تكون متوفّرة دائمًا في مثيل GoogleApiClient المرتبط، عليك حماية طلبات البيانات من واجهات برمجة التطبيقات هذه من خلال إضافة علامة تحقُّق باستخدام hasConnectedApi(). لمعرفة سبب تعذُّر ربط واجهة برمجة تطبيقات معيّنة بنجاح عملية الربط بأكملها للعميل، يمكنك استدعاء getConnectionResult() والحصول على رمز الخطأ من الكائن ConnectionResult. إذا استدعى العميل واجهة برمجة تطبيقات في حال لم تكن متصلة بالعميل، سيتعذّر الاتصال برمز الحالة API_NOT_AVAILABLE.

إذا كانت واجهة برمجة التطبيقات التي تضيفها من خلال addApiIfAvailable() تتطلّب نطاقًا واحدًا أو أكثر، أضِف هذه النطاقات كمَعلمات في طلب طريقة addApiIfAvailable() بدلاً من استخدام طريقة addScope(). قد لا يتم طلب النطاقات التي تمت إضافتها باستخدام هذا الأسلوب في حال تعذُّر ربط واجهة برمجة التطبيقات قبل الحصول على موافقة OAuth، في حين يتم دائمًا طلب النطاقات المُضافة مع addScope().

الاتصالات المُدارة يدويًا

يوضّح لك معظم هذا الدليل كيفية استخدام طريقة enableAutoManage لبدء اتصال مُدار تلقائيًا مع حدوث أخطاء يتم حلّها تلقائيًا. في جميع الحالات تقريبًا، تعد هذه هي الطريقة الأفضل والأسهل للاتصال بواجهات Google APIs من تطبيق Android. ومع ذلك، هناك بعض الحالات التي قد تحتاج فيها إلى استخدام اتصال مُدار يدويًا بـ Google APIs في تطبيقك:

  • الوصول إلى Google APIs خارج نطاق نشاط أو الاحتفاظ بالتحكّم في اتصال واجهة برمجة التطبيقات
  • لتخصيص معالجة أخطاء الاتصال وحلّها

يقدّم هذا القسم أمثلة على هذه الحالات وغيرها من حالات الاستخدام المتقدّمة.

بدء اتصال مُدار يدويًا

لبدء اتصال مُدار يدويًا بـ GoogleApiClient، يجب تحديد عملية تنفيذ لواجهات معاودة الاتصال، ConnectionCallbacks وOnConnectionFailedListener. تتلقى هذه الواجهات معاودة الاتصال استجابةً لطريقة connect() غير المتزامنة عندما ينجح الاتصال بخدمات Google Play أو يتعذّر عليه أو يتم تعليقه.

    mGoogleApiClient = new GoogleApiClient.Builder(this)
            .addApi(Drive.API)
            .addScope(Drive.SCOPE_FILE)
            .addConnectionCallbacks(this)
            .addOnConnectionFailedListener(this)
            .build()

عند إدارة الاتصال يدويًا، عليك طلب إجراءَي connect() وdisconnect() خلال المراحل المناسبة من مراحل نشاط تطبيقك. في سياق النشاط، أفضل ممارسة هي استدعاء connect() في طريقة onStart() لنشاطك وdisconnect() في طريقة onStop() لنشاطك. يتم استدعاء الطريقتين connect() وdisconnect() تلقائيًا عند استخدام اتصال مُدار تلقائيًا.

إذا كنت تستخدم GoogleApiClient للاتصال بواجهات برمجة تطبيقات تتطلّب مصادقة، مثل Google Drive أو "ألعاب Google Play"، هناك احتمال كبير أن تتعذّر محاولة الاتصال الأولى وأن يتلقّى تطبيقك مكالمة إلى onConnectionFailed() مع ظهور الخطأ SIGN_IN_REQUIRED بسبب عدم تحديد حساب المستخدم.

التعامل مع حالات تعذُّر الاتصال

عندما يتلقّى تطبيقك مكالمة لمعاودة الاتصال بـ onConnectionFailed()، يجب الاتصال بـ hasResolution() على الكائن ConnectionResult الذي تم توفيره. إذا كانت القيمة "صحيح"، يمكن لتطبيقك أن يطلب من المستخدم اتخاذ إجراء فوري لحل الخطأ من خلال استدعاء startResolutionForResult() في الكائن ConnectionResult. تتصرف طريقة startResolutionForResult() مثل startActivityForResult() في هذه الحالة، وتطلق نشاطًا مناسبًا للسياق الذي يساعد المستخدم في حل الخطأ (مثل نشاط يساعد المستخدم في اختيار حساب).

إذا عرضت العلامة hasResolution() خطأ، يجب على تطبيقك استدعاء GoogleApiAvailability.getErrorDialog()، وتمرير رمز الخطأ إلى هذه الطريقة. يؤدي ذلك إلى عرض رمز الاستجابة Dialog الذي قدّمته خدمات Google Play بما يتناسب مع الخطأ. قد يعرض مربّع الحوار ببساطة رسالة تشرح الخطأ، أو قد يوفّر أيضًا إجراءً لبدء نشاط يمكن أن يحل الخطأ (مثلاً، عندما يحتاج المستخدم إلى تثبيت إصدار أحدث من "خدمات Google Play").

على سبيل المثال، من المفترض أن تبدو الآن طريقة معاودة الاتصال على onConnectionFailed() على النحو التالي:

public class MyActivity extends Activity
        implements ConnectionCallbacks, OnConnectionFailedListener {

    // Request code to use when launching the resolution activity
    private static final int REQUEST_RESOLVE_ERROR = 1001;
    // Unique tag for the error dialog fragment
    private static final String DIALOG_ERROR = "dialog_error";
    // Bool to track whether the app is already resolving an error
    private boolean mResolvingError = false;

    // ...

    @Override
    public void onConnectionFailed(ConnectionResult result) {
        if (mResolvingError) {
            // Already attempting to resolve an error.
            return;
        } else if (result.hasResolution()) {
            try {
                mResolvingError = true;
                result.startResolutionForResult(this, REQUEST_RESOLVE_ERROR);
            } catch (SendIntentException e) {
                // There was an error with the resolution intent. Try again.
                mGoogleApiClient.connect();
            }
        } else {
            // Show dialog using GoogleApiAvailability.getErrorDialog()
            showErrorDialog(result.getErrorCode());
            mResolvingError = true;
        }
    }

    // The rest of this code is all about building the error dialog

    /* Creates a dialog for an error message */
    private void showErrorDialog(int errorCode) {
        // Create a fragment for the error dialog
        ErrorDialogFragment dialogFragment = new ErrorDialogFragment();
        // Pass the error that should be displayed
        Bundle args = new Bundle();
        args.putInt(DIALOG_ERROR, errorCode);
        dialogFragment.setArguments(args);
        dialogFragment.show(getSupportFragmentManager(), "errordialog");
    }

    /* Called from ErrorDialogFragment when the dialog is dismissed. */
    public void onDialogDismissed() {
        mResolvingError = false;
    }

    /* A fragment to display an error dialog */
    public static class ErrorDialogFragment extends DialogFragment {
        public ErrorDialogFragment() { }

        @Override
        public Dialog onCreateDialog(Bundle savedInstanceState) {
            // Get the error code and retrieve the appropriate dialog
            int errorCode = this.getArguments().getInt(DIALOG_ERROR);
            return GoogleApiAvailability.getInstance().getErrorDialog(
                    this.getActivity(), errorCode, REQUEST_RESOLVE_ERROR);
        }

        @Override
        public void onDismiss(DialogInterface dialog) {
            ((MyActivity) getActivity()).onDialogDismissed();
        }
    }
}

بعد أن يكمل المستخدم مربّع الحوار الذي يقدّمه startResolutionForResult() أو يتجاهل الرسالة المقدَّمة من GoogleApiAvailability.getErrorDialog()، يتلقّى نشاطك معاودة الاتصال بـ onActivityResult() مع رمز نتيجة RESULT_OK. ويمكن لتطبيقك بعد ذلك الاتصال برقم connect() مرة أخرى. مثال:

@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
    if (requestCode == REQUEST_RESOLVE_ERROR) {
        mResolvingError = false;
        if (resultCode == RESULT_OK) {
            // Make sure the app is not already connected or attempting to connect
            if (!mGoogleApiClient.isConnecting() &&
                    !mGoogleApiClient.isConnected()) {
                mGoogleApiClient.connect();
            }
        }
    }
}

في الرمز أعلاه، ربما لاحظت القيمة المنطقية mResolvingError. يؤدي ذلك إلى تتبُّع حالة التطبيق أثناء معالجة المستخدم للخطأ لتجنب المحاولات المتكررة لحل الخطأ نفسه. على سبيل المثال، أثناء عرض مربّع حوار أداة اختيار الحسابات لمساعدة المستخدم في حل الخطأ SIGN_IN_REQUIRED، يمكن للمستخدم تدوير الشاشة. يؤدي هذا الإجراء إلى إعادة إنشاء نشاطك ويؤدي إلى طلب البيانات مجددًا بالطريقة onStart()، والتي يتم بعدها استدعاء connect() مرة أخرى. يؤدي ذلك إلى استدعاء آخر للرقم startResolutionForResult()، ما يؤدي إلى إنشاء مربّع حوار آخر لأداة اختيار الحسابات أمام مربّع الحوار الحالي.

لا تخدم هذه الطريقة المنطقية الغرض المرجو منها إلا إذا استمرت في جميع حالات النشاط. يوضّح القسم التالي كيفية الحفاظ على حالة معالجة الخطأ في تطبيقك على الرغم من إجراءات المستخدم أو الأحداث الأخرى التي تحدث على الجهاز.

الحفاظ على الحالة أثناء حل الخطأ

لتجنُّب تنفيذ الرمز في onConnectionFailed() أثناء إجراء محاولة سابقة لحل خطأ، عليك الاحتفاظ بمنطقية تتتبّع ما إذا كان تطبيقك يحاول إصلاح أحد الأخطاء أم لا.

كما هو موضَّح في مثال الرمز البرمجي أعلاه، يجب أن يضبط تطبيقك قيمة منطقية على true في كل مرة يطلب فيها startResolutionForResult() أو يعرض مربّع الحوار من GoogleApiAvailability.getErrorDialog(). بعد ذلك، عندما يتلقّى تطبيقك RESULT_OK في معاودة الاتصال onActivityResult()، اضبط القيمة المنطقية على false.

لتتبُّع القيمة المنطقية خلال عمليات إعادة تشغيل النشاط (مثلاً عند تدوير المستخدم للشاشة)، احفظ القيمة المنطقية في بيانات مثيل النشاط المحفوظة باستخدام onSaveInstanceState():

private static final String STATE_RESOLVING_ERROR = "resolving_error";

@Override
protected void onSaveInstanceState(Bundle outState) {
    super.onSaveInstanceState(outState);
    outState.putBoolean(STATE_RESOLVING_ERROR, mResolvingError);
}

يمكنك بعد ذلك استرداد الحالة المحفوظة خلال onCreate():

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    // ...
    mResolvingError = savedInstanceState != null
            && savedInstanceState.getBoolean(STATE_RESOLVING_ERROR, false);
}

أنت الآن جاهز لتشغيل تطبيقك بأمان وربطه يدويًا بخدمات Google Play.