إتاحة إمكانية مواصلة النشاط على عدة أجهزة على Googlebook

يبدأ المستخدمون غالبًا مهامهم على هواتف Android، مثل مراجعة مستند أو فرز الرسائل أو تصفّح مجموعة من العناصر، ثم يريدون الانتقال إلى Googlebook للاستفادة من شاشته الأكبر ولوحة المفاتيح المادية ومساحة العمل المتعددة النوافذ.

تتيح ميزة المتابعة في نظام التشغيل Android 17 (مستوى واجهة برمجة التطبيقات 37) والإصدارات الأحدث لشريط مهام Googlebook عرض رمز تطبيق (يُعرف باسم اقتراح) للنشاط الذي كان المستخدم ينفّذه على هاتفه القريب. بنقرة واحدة، يمكن للمستخدمين متابعة نشاطهم على Googlebook.

يتيح تنفيذ ميزة "المتابعة على جميع الأجهزة" نقل سياق النشاط في تطبيقك، ما يتيح للمستخدم استئناف سير عمله، إما في تطبيق Android المتكيّف أو في متصفّح سطح المكتب، من المكان الذي توقّف فيه على هاتفه.

اختيار مسار نقل المحادثة

قبل تفعيل ميزة "نقل النشاط بين الأجهزة"، حدِّد الطريقة التي يجب أن يتم بها فتح كل نشاط في تطبيق الهاتف عندما يختار المستخدم اقتراح "متابعة" على Googlebook:

مسار الإحالة الاستهداف على Googlebook أفضل مع Key API
نقل المكالمة من تطبيق إلى آخر تطبيق Android أصلي يتم تشغيله في نافذة على الكمبيوتر تطبيقات محسَّنة للشاشات الكبيرة والعرض في نافذة على سطح المكتب على Google Play HandoffActivityData.Builder
نقل البيانات من تطبيق إلى آخر مع توفير خيار الرجوع إلى الويب تطبيق Android الأصلي إذا كان مثبَّتًا، وإلا سيتم فتح عنوان URL في متصفّح سطح المكتب التلقائي التطبيقات المحسَّنة لتوفير تجربة استخدام نافذة سطح مكتب أصلية ولكنها تتطلّب متصفّحًا احتياطيًا موثوقًا به عندما لا يكون التطبيق مثبَّتًا على Googlebook setFallbackUri
تسليم المحتوى مباشرةً إلى الويب عنوان URL في متصفّح سطح المكتب التلقائي (أو تم اعتراض طلبه من خلال التطبيق الأصلي إذا تم إعداده لاستخدام روابط تطبيقات Android) حالات استخدام يكون فيها تطبيق الويب هو تجربة سطح المكتب الأساسية HandoffActivityData.createWebHandoff

تفعيل ميزة "التسليم" لنشاط معيّن

يكون خيار "المتابعة" غير مفعّل تلقائيًا ويتم ضبطه لكل نشاط. اتّبِع الخطوات التالية في كل نشاط إرسال على الهاتف:

  1. استدعِ الدالة setHandoffEnabled ومرِّر true بعد تحميل النشاط الحالة المطلوبة لتسليم النشاط. وبعد تفعيل هذا الخيار، يمكن للنظام طلب بيانات التسليم أثناء عرض النشاط في المقدّمة أو عند الانتقال إلى الخلفية.
  2. يمكنك إلغاء onHandoffActivityDataRequested لعرض عنصر HandoffActivityData غير فارغ. يمكنك طلب isHandoffEnabled في أي وقت لمعرفة ما إذا كانت ميزة "نقل النشاط" مفعّلة للنشاط.
import android.app.Activity
import android.app.HandoffActivityData
import android.app.HandoffActivityDataRequestInfo
import android.os.Bundle
import android.os.PersistableBundle

class DocumentEditorActivity : Activity() {

    private var documentId: String = "doc_42"
    private var scrollOffsetPx: Int = 0

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        // Enable handoff once the activity is ready to transfer state.
        setHandoffEnabled(true, null)
    }

    override fun onHandoffActivityDataRequested(
        handoffRequestInfo: HandoffActivityDataRequestInfo
    ): HandoffActivityData {
        val extras = PersistableBundle().apply {
            putString(EXTRA_DOCUMENT_ID, documentId)
            putInt(EXTRA_SCROLL_OFFSET, scrollOffsetPx)
        }

        return HandoffActivityData.Builder(getComponentName())
            .setExtras(extras)
            .build()
    }

    companion object {
        const val EXTRA_DOCUMENT_ID = "extra_document_id"
        const val EXTRA_SCROLL_OFFSET = "extra_scroll_offset"
    }
}

حالة نشاط الحزمة عند التسليم

يستدعي النظام onHandoffActivityDataRequested في حالتين:

  • نقل النشاط إلى المقدّمة: يكون النشاط في المقدّمة على الهاتف، ويبدأ المستخدم عملية النقل من شريط التطبيقات على Googlebook.
  • تسجيل حالة الخلفية: ينتقل النشاط إلى الخلفية على الهاتف، ويخزّن النظام مؤقتًا حمولة التسليم في حال اختار المستخدم الاقتراح على Googlebook لاحقًا.

التحقّق مما إذا كان طلب التسليم نشطًا

افحص HandoffActivityDataRequestInfo باستخدام isActiveRequest للتمييز بين عملية تسليم نشطة بدأها المستخدم وعملية حفظ في الخلفية:

  • إذا عرضت isActiveRequest القيمة true، يعني ذلك أنّ المستخدم طلب بشكل نشط مواصلة النشاط على جهاز آخر أثناء ظهوره.
  • إذا عرضت isActiveRequest القيمة false، يعني ذلك أنّ النظام يخزّن الحالة مؤقتًا عند توقّف النشاط.

إذا عرضت مؤشرًا مرئيًا على الهاتف يؤكّد أنّ المهمة تنتقل إلى Googlebook، حدِّد موعدًا لتعديل واجهة المستخدم بشكل غير متزامن كي تعرض onHandoffActivityDataRequested بدون تأخير.

إرفاق إضافات الحالة ومسار احتياطي على الويب

تحتوي حمولة HandoffActivityData على المعلومات التالية:

  • المكوّن المستهدف (مطلوب لتسليم البيانات من تطبيق إلى آخر): يجب تمرير this.getComponentName() (ComponentName للنشاط) أو ComponentName(this, TargetActivity::class.java) صريح إلى HandoffActivityData.Builder لتحديد النشاط الذي سيتم تشغيله على Googlebook.
  • Extras (اختياري): مرِّر PersistableBundle إلى setExtras() يتضمّن المعرّفات وإحداثيات واجهة المستخدم (مثل معرّفات المستندات المفتوحة أو مفاتيح علامات التبويب النشطة أو مواضع التمرير). يجب أن يكون حجم الحزمة أقل من 50 كيلوبايت.
  • معرّف الموارد المنتظم (URI) الاحتياطي (اختياري): يمكنك تمرير Uri على الويب إلى setFallbackUri كي يتمكّن Googlebook من فتح المهمة في المتصفّح التلقائي إذا لم يكن تطبيق Android مثبَّتًا.
import android.app.Activity
import android.app.HandoffActivityData
import android.app.HandoffActivityDataRequestInfo
import android.app.HandoffActivityParams
import android.net.Uri
import android.os.Bundle
import android.os.PersistableBundle

class ArticleReaderActivity : Activity() {

    private var articleId: String = "android-desktop-windowing"
    private var readingProgressPercent: Int = 45

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        setHandoffEnabled(
            true,
            HandoffActivityParams.Builder()
                .setAllowHandoffWithoutPackageInstalled(true)
                .build()
        )
    }

    override fun onHandoffActivityDataRequested(
        handoffRequestInfo: HandoffActivityDataRequestInfo
    ): HandoffActivityData {
        if (handoffRequestInfo.isActiveRequest()) {
            window.decorView.post {
                onHandoffStartedToSecondaryDevice()
            }
        }

        val extras = PersistableBundle().apply {
            putString(EXTRA_ARTICLE_ID, articleId)
            putInt(EXTRA_READING_PROGRESS, readingProgressPercent)
        }

        val fallbackWebUri = Uri.Builder()
            .scheme("https")
            .authority("example.com")
            .appendPath("articles")
            .appendPath(articleId)
            .appendQueryParameter("progress", readingProgressPercent.toString())
            .build()

        return HandoffActivityData.Builder(getComponentName())
            .setExtras(extras)
            .setFallbackUri(fallbackWebUri)
            .build()
    }

    private fun onHandoffStartedToSecondaryDevice() {
        // Save local drafts and update phone UI without blocking handoff.
    }

    companion object {
        const val EXTRA_ARTICLE_ID = "extra_article_id"
        const val EXTRA_READING_PROGRESS = "extra_reading_progress"
    }
}

ضبط عملية التسليم المباشر إلى الويب

إذا كانت تجربة الكمبيوتر المكتبي الأساسية على Googlebook هي تطبيق ويب، مرِّر HandoffActivityParams مع setAllowHandoffWithoutPackageInstalled(true) إلى setHandoffEnabled، واستخدِم HandoffActivityData.createWebHandoff بدلاً من HandoffActivityData.Builder:

import android.app.Activity
import android.app.HandoffActivityData
import android.app.HandoffActivityDataRequestInfo
import android.app.HandoffActivityParams
import android.net.Uri
import android.os.Bundle

class WebDashboardActivity : Activity() {

    private var projectId: String = "proj_901"

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        setHandoffEnabled(
            true,
            HandoffActivityParams.Builder()
                .setAllowHandoffWithoutPackageInstalled(true)
                .build()
        )
    }

    override fun onHandoffActivityDataRequested(
        handoffRequestInfo: HandoffActivityDataRequestInfo
    ): HandoffActivityData {
        val webUri = Uri.Builder()
            .scheme("https")
            .authority("app.example.com")
            .appendPath("projects")
            .appendPath(projectId)
            .build()

        return HandoffActivityData.createWebHandoff(webUri)
    }
}

تلقّي الحالة واستعادتها على جهاز Googlebook

عندما ينقر المستخدم على اقتراح "متابعة" في شريط مهام Googlebook، يفتح النظام ComponentName المستهدف في نافذة على سطح المكتب ويُرفق الإضافات PersistableBundle بعملية الفتح Intent.

على Googlebook، تعامَل مع عملية التسليم الواردة مع مراعاة ميزة "العرض في نافذة" وتنسيقات النوافذ المتعددة:

  1. استخراج البيانات الإضافية لعملية التسليم: في كلتا الطريقتين onCreate وonNewIntent لنشاط الاستلام، استرجِع البيانات الإضافية من Intent الوارد. يضمن التعامل مع onNewIntent أنّه إذا كان نشاطك يستخدم وضع التشغيل singleTop أو يوجّه إلى نافذة سطح مكتب حالية، سيتم تعديل النافذة لتشمل العنصر الذي تم تسليمه.
  2. تكييف الحالة مع التصاميم المتعددة الألواح: غالبًا ما يتم ربط الحالة التي شغلت عرض تفاصيل بملء الشاشة على الهاتف بعنصر محدّد داخل عرض على شكل قائمة مع تفاصيل أو تصميم لوحة ثانوية على Googlebook. املأ كلاً من تحديد القائمة الأساسية ولوحة التفاصيل لكي تتضمّن نافذة الكمبيوتر المكتبي السياق الكامل.
  3. الحفاظ على الحالة المستعادة عند تغيير حجم النافذة: خزِّن الحالة التي تم نقلها في ViewModel (SavedStateHandle) أو rememberSaveable في Jetpack Compose حتى لا تؤدي عملية تغيير حجم النافذة الحرة على Googlebook إلى إعادة ضبط الجلسة المستعادة.
import android.app.Activity
import android.content.Intent
import android.os.Bundle

class DocumentEditorReceiverActivity : Activity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        // Restore from the handoff intent only on initial launch so subsequent
        // desktop window configuration changes use saved instance state.
        if (savedInstanceState == null) {
            handleHandoffIntent(intent)
        }
    }

    override fun onNewIntent(intent: Intent) {
        super.onNewIntent(intent)
        setIntent(intent)
        handleHandoffIntent(intent)
    }

    private fun handleHandoffIntent(incomingIntent: Intent?) {
        val documentId = incomingIntent?.getStringExtra(EXTRA_DOCUMENT_ID)
            ?: return
        val scrollOffsetPx = incomingIntent.getIntExtra(EXTRA_SCROLL_OFFSET, 0)

        openDocumentInDesktopWorkspace(
            documentId = documentId,
            initialScrollOffsetPx = scrollOffsetPx
        )
    }

    private fun openDocumentInDesktopWorkspace(
        documentId: String,
        initialScrollOffsetPx: Int
    ) {
        // Load the document into the list-detail layout and restore scroll position.
    }

    companion object {
        const val EXTRA_DOCUMENT_ID = "extra_document_id"
        const val EXTRA_SCROLL_OFFSET = "extra_scroll_offset"
    }
}

اختبار ميزة "النقل" بين هاتف وجهاز Googlebook

للتحقّق من صحة عملية التنفيذ بالكامل، اتّبِع الخطوات التالية:

  1. تجهيز كلا الجهازين
    • سجِّل الدخول إلى حساب Google نفسه على كلّ من هاتف Android (الذي يعمل بالإصدار 17 من نظام التشغيل Android أو إصدار أحدث) وجهاز Googlebook (أو جهاز اختباري يعمل في وضع العرض في نافذة).
    • فعِّل البلوتوث ووصِّل كلا الجهازين بشبكة Wi-Fi نفسها.
    • على كلا الجهازَين، افتح الإعدادات > الأجهزة المتصلة > إعدادات الاتصال المفضّلة > الخدمات التي تعمل على عدة أجهزة > متابعة النشاط، واتّبِع التعليمات التي تظهر على الشاشة لإعداد الميزة، ثم فعِّل مهام Google. للاطّلاع على المتطلبات الأساسية الكاملة للبيئة، يُرجى الانتقال إلى الإعداد والاختبار.
  2. تثبيت تطبيقك
    • بالنسبة إلى نقل البيانات من تطبيق إلى آخر، ثبِّت الإصدار على كل من الهاتف وجهاز Googlebook.
    • بالنسبة إلى الرجوع إلى الويب أو التسليم المباشر إلى الويب، ثبِّت الإصدار على الهاتف واترك التطبيق غير مثبَّت على Googlebook حتى تتمكّن من التأكّد من أنّ المتصفّح يفتح عنوان URL المتوقّع.
  3. تفعيل عملية التسليم
    • على الهاتف، افتح تطبيقك وانتقِل إلى النشاط الذي يتيح عملية التسليم (الاتصال setHandoffEnabled(true, null) لتسليم المكالمة من تطبيق إلى تطبيق، أو تمرير HandoffActivityParams مع setAllowHandoffWithoutPackageInstalled(true) عند الاختبار مع إلغاء تثبيت التطبيق على Googlebook).
    • على جهاز Googlebook، ابحث على الجانب الأيسر من شريط التطبيقات عن رمز تطبيقك مع شارة "المتابعة على" وانقر عليه.
  4. التأكّد من استعادة الكمبيوتر المكتبي
    • تأكَّد من أنّ تطبيقك يفتح في نافذة على الكمبيوتر المكتبي (أو علامة تبويب متصفّح) عند موضع التمرير وموضع العنصر نفسهما على الهاتف.
    • غيِّر حجم نافذة التطبيق على Googlebook وتأكَّد من أنّ المحتوى المستعاد والتحديد يظلان ثابتَين عند الانتقال بين فئات أحجام النوافذ.

مراجع إضافية