تطبيقات قيد التشغيل دائمًا وميزة "وضع الإضاءة السينمائية"

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

يؤثر إبقاء التطبيق مرئيًا باستمرار بشكل كبير في عمر البطارية، لذا يجب مراعاة تأثير استهلاك الطاقة عند إضافة هذه الميزة.

المفاهيم الرئيسية

عند عرض تطبيق Wear OS على الشاشة الكاملة، يكون في إحدى حالتين من حالات الطاقة:

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

يتحكّم نظام التشغيل في الانتقال بين هذه الحالات.

التطبيق الذي يعمل دائمًا هو تطبيق يعرض المحتوى في كل من حالتَي التفاعلية والخلفية.

عندما يستمر تطبيق يعمل دائمًا في عرض واجهة المستخدم الخاصة به بينما يكون الجهاز في حالة الاستراحة منخفضة الطاقة، يُقال إنّه في وضع الاستراحة النشط.

عمليات الانتقال في النظام والسلوك التلقائي

عندما يكون التطبيق في المقدّمة، يدير النظام عمليات الانتقال بين حالات الطاقة استنادًا إلى مهلتَين يتم تفعيلهما عند توقّف نشاط المستخدم.

  • المهلة رقم 1: من الحالة التفاعلية إلى الحالة المحيطة: بعد فترة من عدم نشاط المستخدم، ينتقل الجهاز إلى الحالة المحيطة.
  • المهلة رقم 2: الرجوع إلى خلفية شاشة الساعة: بعد فترة أخرى من عدم النشاط، قد يخفي النظام التطبيق الحالي ويعرض خلفية شاشة الساعة.

بعد أن ينتقل النظام إلى حالة الوضع المحيط للمرة الأولى، يعتمد السلوك التلقائي على إصدار Wear OS وإعدادات تطبيقك:

  • في الإصدار 5 من Wear OS والإصدارات الأقدم، يعرض النظام لقطة شاشة مشوّشة لتطبيقك المتوقف مؤقتًا، مع عرض الوقت فوقها. يتم تمثيل هذه الحالة من خلال عقدة "AOD Lite" في مخطط التدفق التالي.
  • في الإصدار 6 من Wear OS والإصدارات الأحدث، إذا كان التطبيق يستهدف الإصدار 36 من حزمة تطوير البرامج (SDK) أو إصدارًا أحدث، سيتم اعتباره قيد التشغيل دائمًا. يتم تعتيم الشاشة، ولكن يظل التطبيق قيد التشغيل ومرئيًا. (قد يتم إجراء التحديثات مرة واحدة فقط في الدقيقة). يتم تمثيل هذه الحالة من خلال عقدة "العرض الدائم على الشاشة على مستوى العالم" في مخطط التدفق التالي.

تخصيص السلوك في "وضع الاستراحة"

بغض النظر عن السلوك التلقائي للنظام، يمكنك في جميع إصدارات Wear OS تخصيص مظهر تطبيقك أو سلوكه أثناء حالة الوضع المحيط باستخدام LocalAmbientModeManager لمراقبة عمليات الانتقال بين الحالات في Jetpack Compose على Wear OS. يتم تمثيل هذه الحالة من خلال عقدة "الوضع النشط المحيط" في مخطط التدفق التالي.

مراقبة حالة الاستراحة

للتفاعل مع أحداث وضع الاستراحة في Jetpack Compose على Wear OS، اتّبِع الخطوات التالية:

  1. استخدِم LocalAmbientModeManager لمراقبة التغييرات في حالة "الوضع المحيط" في النظام، مع تعديل العناصر القابلة للإنشاء وفقًا لذلك. اضبط إعدادات المكوّن ليتوافق مع وضع استهلاك الطاقة المنخفضة في الحالة AmbientMode.Ambient، واستعادة العرض التفاعلي الكامل في الحالة AmbientMode.Interactive.

    // In a production application, the AmbientModeManager should be instantiated and provided at
    // the highest level of the Compose hierarchy (typically in the host Activity's setContent
    // block) using a CompositionLocalProvider. This ensures proper lifecycle management and
    // broad accessibility.
    
    // For this self-contained demo, AmbientModeManager is created and provided locally:
    val activityAmbientModeManager = rememberAmbientModeManager()
    CompositionLocalProvider(LocalAmbientModeManager provides activityAmbientModeManager) {
        val ambientModeManager = LocalAmbientModeManager.current
        val ambientMode = ambientModeManager?.currentAmbientMode
    
        if (ambientModeManager != null) {
            ambientModeManager.AmbientTickEffect {
                // While device is in ambient mode, update properties every minute or so
                // ...
            }
        }
    
        // ...
            val ambientModeName =
                when (ambientMode) {
                    is AmbientMode.Interactive -> "Interactive"
                    is AmbientMode.Ambient -> "Ambient"
                    else -> "Unknown"
                }
    
            Text(text = "$ambientModeName Mode")
            // ...
    }

بالنسبة إلى المطوّرين الذين يستخدمون Jetpack Compose، استخدِم LocalAmbientModeManager، ما يسهّل تنفيذ هذا النمط.

المخطط الانسيابي للسلوك المحيط

يوضّح مخطط التدفق التالي كيف يحدّد النظام سلوك وضع العرض المحيط استنادًا إلى إصدار Wear OS على الجهاز وtargetSdkVersion في تطبيقك وما إذا كان تطبيقك ينفّذ AmbientLifecycleCallback.

مخطط انسيابي يوضّح منطق القرار في ما يتعلق بوضع عدم النشاط على Wear OS يوضّح هذا الفيديو كيف يحدّد إصدار نظام التشغيل على الجهاز وإعدادات التطبيق إحدى النتائج الثلاث التالية: تراكب مموّه أو ميزة "تشغيل الشاشة دائمًا (AOD)" العامة أو "وضع Ambiactive" الذي يديره التطبيق.
الشكل 1.: مخطط انسيابي يوضّح منطق القرار لوضع عدم النشاط في Wear OS

التحكّم في مدة تشغيل الشاشة

توضّح الأقسام التالية كيفية إدارة المدة التي يظل فيها تطبيقك معروضًا على الشاشة.

منع الرجوع إلى خلفية شاشة الساعة باستخدام "نشاط قيد التقدّم" أو "تحديث فوري"

بعد فترة من الوقت في حالة الوضع المحيط (المهلة رقم 2)، سيعود النظام عادةً إلى خلفية شاشة الساعة. يمكن للمستخدم ضبط مدة المهلة في إعدادات النظام. في بعض حالات الاستخدام، مثل تتبُّع المستخدم لتمرين رياضي، قد يحتاج التطبيق إلى البقاء مرئيًا لفترة أطول.

في الإصدار 5 من Wear OS والإصدارات الأحدث، يمكنك منع ذلك من خلال تنفيذ نشاط مستمر. إذا كان تطبيقك يعرض معلومات حول مهمة مستخدم مستمرة، مثل جلسة تمرين، يمكنك استخدام Ongoing Activity API لإبقاء تطبيقك مرئيًا إلى أن تنتهي المهمة. إذا عاد المستخدم يدويًا إلى خلفية شاشة الساعة، يوفّر مؤشر النشاط المستمر في الخلفية طريقة بنقرة واحدة للعودة إلى تطبيقك.

بدلاً من ذلك، يمكنك استخدام تحديث مباشر على الإصدار 7 من Wear OS والإصدارات الأحدث بدلاً من ميزة "النشاط الجاري". لضمان التوافق مع الإصدارات القديمة، واصِل توفير ميزة <0x0A>النشاط المستمر على الأجهزة التي تعمل بالإصدار 6 أو الإصدارات الأقدم من Wear OS.

لتنفيذ ذلك، يجب أن يشير الغرض من اللمس في الإشعار المستمر إلى نشاطك الذي يعمل دائمًا، كما هو موضّح في مقتطف الرمز التالي:

val activityIntent =
    Intent(this, AlwaysOnActivity::class.java).apply {
        flags = Intent.FLAG_ACTIVITY_SINGLE_TOP
    }

val pendingIntent =
    PendingIntent.getActivity(
        this,
        0,
        activityIntent,
        PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE,
    )

val notificationBuilder =
    NotificationCompat.Builder(this, CHANNEL_ID)
        // ...
        // ...
        .setOngoing(true)

// ...

val ongoingActivity =
    OngoingActivity.Builder(applicationContext, NOTIFICATION_ID, notificationBuilder)
        // ...
        // ...
        .setTouchIntent(pendingIntent)
        .build()

ongoingActivity.apply(applicationContext)

val notification = notificationBuilder.build()

إبقاء الشاشة مفعّلة ومنع حالة "العرض المحيط"

في حالات نادرة، قد تحتاج إلى منع الجهاز تمامًا من الانتقال إلى حالة الاستراحة. أي لتجنُّب الخطأ Timeout #1. لإجراء ذلك، يمكنك استخدام المعدِّل keepScreenOn. تعمل هذه الميزة كقفل التنشيط، ما يحافظ على الجهاز في حالة تفاعلية. يجب استخدام هذا الخيار بحذر شديد لأنّه يؤثر بشكل كبير في عمر البطارية.

اقتراحات بشأن "وضع الاستراحة"

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

  • تقليل الفوضى البصرية واستهلاك الطاقة تشير واجهة المستخدم البسيطة والنظيفة إلى أنّ التطبيق في وضع توفير الطاقة، كما أنّها توفّر شحن البطارية بشكل كبير من خلال الحد من عدد وحدات البكسل الساطعة.
    • إبقاء 85% على الأقل من الشاشة باللون الأسود
    • اعرض المعلومات الأكثر أهمية فقط، وانقل التفاصيل الثانوية إلى الشاشة التفاعلية.
    • استخدِم خطوطًا خارجية للرموز أو الأزرار الكبيرة بدلاً من التعبئة الكاملة.
    • تجنَّب استخدام مساحات كبيرة من الألوان الثابتة والعلامات التجارية أو صور الخلفية غير الوظيفية.
  • التعامل مع البيانات الديناميكية القديمة
    • يتم استدعاء AmbientTickEffect بشكل دوري فقط، عادةً مرة واحدة في الدقيقة، للحفاظ على الطاقة. وبسبب هذا القيد، تصبح أي بيانات تتغيّر بشكل متكرّر، مثل ساعة الإيقاف أو معدّل نبضات القلب أو مسافة التمرين، قديمة بين عمليات التحديث. لتجنُّب عرض معلومات مضلّلة وغير صحيحة، استبدِل هذه القيم المباشرة بمحتوى عنصر نائب ثابت، مثل --.
  • الحفاظ على تنسيق متّسق
    • احتفِظ بموضع العناصر نفسه في كل من وضعَي التفاعلي والمحيطي لإنشاء انتقال سلس.
    • عرض الوقت دائمًا
  • الاستجابة للسياق
    • إذا كان المستخدم يتصفّح شاشة إعدادات أو ضبط عند دخول الجهاز إلى وضع الاستراحة، ننصحك بعرض شاشة أكثر صلةً من تطبيقك بدلاً من عرض الإعدادات.
  • التعامل مع المتطلبات الخاصة بالجهاز
    • في عنصر AmbientMode الذي تم عرضه من currentAmbientMode:
      • إذا كانت قيمة isLowBitAmbientSupported هي true، أوقِف ميزة "تنعيم الحواف" حيثما أمكن ذلك.
      • إذا كانت قيمة isBurnInProtectionRequired هي true، عليك تغيير موضع عناصر واجهة المستخدم بشكل دوري قليلاً وتجنُّب المساحات البيضاء الصلبة لمنع احتراق الشاشة.

تصحيح الأخطاء والاختبار

قد تكون أوامر adb هذه مفيدة عند تطوير أو اختبار طريقة عمل تطبيقك عندما يكون الجهاز في وضع العرض المحيط:

# put device in ambient mode if the always on display is enabled in settings
# (and not disabled by other settings, such as theatre mode)
$ adb shell input keyevent KEYCODE_SLEEP

# put device in interactive mode
$ adb shell input keyevent KEYCODE_WAKEUP

مثال: تطبيق تمارين رياضية

لنفترض أنّ هناك تطبيقًا للتمارين الرياضية يحتاج إلى عرض مقاييس للمستخدم طوال مدة جلسة التمرين. يجب أن يظل التطبيق مرئيًا خلال عمليات الانتقال إلى وضع العرض المحيط وتجنُّب استبداله بخلفية شاشة الساعة.

لتحقيق ذلك، على المطوّر اتّخاذ الخطوات التالية:

  1. استخدِم LocalAmbientModeManager للتعامل مع تغييرات واجهة المستخدم بين حالتَي تفاعلية ومحيطة، مثل تعتيم الشاشة وإزالة البيانات غير الأساسية.
  2. أنشئ تصميمًا جديدًا منخفض الطاقة لحالة الاستراحة يتّبع أفضل الممارسات.
  3. استخدِم Ongoing Activity API (أو التحديثات الفورية على الإصدار 7 من Wear OS والإصدارات الأحدث) طوال مدة التمرين لمنع النظام من العودة إلى خلفية شاشة الساعة.

للحصول على عملية تنفيذ كاملة، يمكنك الاطّلاع على نموذج التمرين المستند إلى Compose على GitHub. يوضّح هذا النموذج أيضًا كيفية استخدام الدالة المركّبة LocalAmbientModeManager لتبسيط عملية التعامل مع &quot;وضع عدم النشاط&quot; في Compose.