في ما يلي التعديلات التي تم إجراؤها على هذه المناطق الخاصة بالشاشة:
- تغيير حجم الأنشطة وشاشات العرض
- أحجام العرض ونِسَب العرض إلى الارتفاع
- سياسات العرض
- إعدادات نافذة العرض
- معرّفات الإعلانات الصورية الثابتة
- استخدام أكثر من شاشتين
- التركيز على كل شاشة
تغيير حجم الأنشطة وشاشات العرض
للإشارة إلى أنّ التطبيق قد لا يتيح وضع النوافذ المتعددة أو تغيير الحجم، تستخدم الأنشطة السمة resizeableActivity=false
. تشمل المشاكل الشائعة التي تواجهها التطبيقات عند تغيير حجم الأنشطة ما يلي:
- يمكن أن يكون للنشاط إعداد مختلف عن التطبيق أو أي مكوّن آخر غير مرئي. من الأخطاء الشائعة قراءة مقاييس العرض من سياق التطبيق. لن يتم تعديل القيم التي يتم عرضها لتناسب مقاييس المساحة المرئية التي يتم عرض النشاط فيها.
- قد لا يتمكّن أحد الأنشطة من التعامل مع تغيير الحجم وقد يتعطّل أو يعرض واجهة مستخدم مشوّهة أو يفقد الحالة بسبب إعادة التشغيل بدون حفظ حالة المثيل.
- قد يحاول أحد التطبيقات استخدام إحداثيات إدخال مطلقة (بدلاً من الإحداثيات النسبية إلى موضع النافذة)، ما قد يؤدي إلى تعطُّل الإدخال في وضع النوافذ المتعددة.
في نظام التشغيل Android 7 (والإصدارات الأحدث)، يمكن ضبط أحد التطبيقات على resizeableActivity=false
التشغيل دائمًا في وضع ملء الشاشة. في هذه الحالة، تمنع المنصة الأنشطة غير القابلة لتغيير الحجم من الانتقال إلى وضع تقسيم الشاشة. إذا حاول المستخدم بدء نشاط غير قابل لتغيير الحجم من مشغّل التطبيقات
أثناء استخدام وضع تقسيم الشاشة، ستخرج المنصة من وضع تقسيم الشاشة
وتبدأ النشاط غير القابل لتغيير الحجم في وضع ملء الشاشة.
يجب عدم تشغيل التطبيقات التي تضبط هذه السمة بشكل صريح على false
في ملف البيان في وضع النوافذ المتعدّدة، إلا إذا تم تطبيق وضع التوافق:
- يتم تطبيق الإعدادات نفسها على العملية التي تحتوي على جميع الأنشطة والمكوّنات غير النشطة.
- يستوفي الإعداد المطبَّق متطلبات CDD لشاشات متوافقة مع التطبيقات.
في نظام التشغيل Android 10، تمنع المنصة الأنشطة التي لا يمكن تغيير حجمها من الانتقال إلى وضع تقسيم الشاشة، ولكن يمكن تغيير حجمها مؤقتًا إذا كان النشاط قد حدّد اتجاهًا ثابتًا أو نسبة عرض إلى ارتفاع ثابتة. وإذا لم يكن كذلك، يتم تغيير حجم النشاط لملء الشاشة بالكامل كما هو الحال في الإصدار 9 من نظام التشغيل Android والإصدارات الأقدم.
يطبّق التنفيذ التلقائي السياسة التالية:
عندما يتم الإعلان عن أنّ أحد الأنشطة غير متوافق مع وضع النوافذ المتعدّدة من خلال استخدام السمة android:resizeableActivity
، وعندما يستوفي هذا النشاط أحد الشروط الموضّحة أدناه، وعندما يجب تغيير إعدادات الشاشة المطبَّقة، يتم حفظ النشاط والعملية باستخدام الإعدادات الأصلية، ويتم تزويد المستخدم بأداة لإعادة تشغيل عملية التطبيق لاستخدام إعدادات الشاشة المعدَّلة.
- هل يتم تحديد اتجاه الشاشة من خلال تطبيق
android:screenOrientation
- يحتوي التطبيق على الحد الأقصى أو الأدنى التلقائي لنسبة العرض إلى الارتفاع من خلال استهداف مستوى واجهة برمجة التطبيقات أو تحديد نسبة العرض إلى الارتفاع بشكل صريح
يعرض هذا الشكل نشاطًا غير قابل لتغيير الحجم بنسبة عرض إلى ارتفاع محدّدة. عند طي الجهاز، يتم تصغير حجم النافذة لتناسب المساحة مع الحفاظ على نسبة العرض إلى الارتفاع باستخدام التنسيق المناسب. بالإضافة إلى ذلك، يتم توفير خيار إعادة تشغيل النشاط للمستخدم في كل مرة يتم فيها تغيير مساحة العرض الخاصة بالنشاط.
عند فتح الجهاز، لا يتغيّر الإعداد والحجم ونسبة العرض إلى الارتفاع للنشاط، ولكن يظهر خيار إعادة تشغيل النشاط.
عندما لا يتم ضبط resizeableActivity
(أو يتم ضبطه على true
)، يتيح التطبيق تغيير الحجم بالكامل.
التنفيذ
يُطلق على النشاط غير القابل لتغيير الحجم والذي يتضمّن اتجاهًا أو نسبة عرض إلى ارتفاع ثابتَين اسم وضع توافق الحجم (SCM) في الرمز البرمجي. يتم تحديد الشرط في
ActivityRecord#shouldUseSizeCompatMode()
. عند تشغيل نشاط SCM، يتم تثبيت الإعدادات المتعلّقة بالشاشة (مثل الحجم أو الكثافة) في إعدادات التجاوز المطلوبة، وبالتالي لا يعود النشاط يعتمد على إعدادات العرض الحالية.
إذا لم يتمكّن نشاط SCM من ملء الشاشة بأكملها، تتم محاذاته إلى الأعلى وتوسيطه أفقيًا. يتم احتساب حدود النشاط من خلال
AppWindowToken#calculateCompatBoundsTransformation()
.
عندما يستخدم نشاط SCM إعداد شاشة مختلفًا عن الحاوية (على سبيل المثال، يتم تغيير حجم الشاشة أو نقل النشاط إلى شاشة أخرى)، تكون قيمة ActivityRecord#inSizeCompatMode()
صحيحة ويتلقّى SizeCompatModeActivityController
(في واجهة مستخدم النظام) رد الاتصال لعرض زر إعادة تشغيل العملية.
أحجام شاشات العرض ونسب العرض إلى الارتفاع
يتيح نظام التشغيل Android 10 استخدام نسب عرض إلى ارتفاع جديدة،
بدءًا من النسب العالية للشاشات الطويلة والنحيفة وصولاً إلى النسب 1:1. يمكن للتطبيقات تحديد
ApplicationInfo#maxAspectRatio
وApplicationInfo#minAspectRatio
للشاشة التي يمكنها التعامل معها.
الشكل 1. مثال على نسب العرض إلى الارتفاع للتطبيقات المتوافقة مع الإصدار 10 من نظام التشغيل Android
يمكن أن تتضمّن عمليات تنفيذ الأجهزة شاشات ثانوية بأحجام ودقّات أقل من تلك التي يتطلّبها الإصدار 9 من نظام التشغيل Android، وأقل (بحد أدنى 2.5 بوصة عرضًا أو ارتفاعًا، وبحد أدنى 320 وحدة بكسل مستقلة الكثافة smallestScreenWidth
)، ولكن لا يمكن وضع سوى الأنشطة التي توافق على دعم هذه الشاشات الصغيرة.
يمكن للتطبيقات الموافقة على ذلك من خلال تحديد الحد الأدنى للحجم المتوافق الذي يكون أصغر من حجم الشاشة المستهدَف أو مساويًا له. استخدِم سمتَي تنسيق النشاط android:minHeight
وandroid:minWidth
في ملف AndroidManifest لتنفيذ ذلك.
سياسات العرض
يفصل نظام التشغيل Android 10 بعض سياسات العرض وينقلها من التنفيذ التلقائي WindowManagerPolicy
في PhoneWindowManager
إلى فئات خاصة بكل شاشة، مثل:
- حالة شاشة العرض وتدويرها
- تتبُّع بعض المفاتيح وأحداث الحركة
- واجهة مستخدم النظام ونوافذ الزخارف
في نظام التشغيل Android 9 (والإصدارات الأقدم)، كان الصف PhoneWindowManager
يتعامل مع سياسات العرض والحالة والإعدادات والتدوير وتتبُّع إطار نافذة الزخرفة وغير ذلك. ينقل نظام التشغيل Android 10 معظم هذه البيانات إلى الفئة DisplayPolicy
، باستثناء بيانات تتبُّع دوران الشاشة التي تم نقلها إلى الفئة DisplayRotation
.
إعدادات نافذة العرض
في Android 10، تم توسيع نطاق إعدادات النوافذ القابلة للضبط لكل شاشة لتشمل ما يلي:
- وضع العرض التلقائي في النوافذ
- قيم المسح الزائد
- تدوير المستخدم ووضع التدوير
- الحجم والكثافة ووضع تغيير الحجم الإجباري
- وضع إزالة المحتوى (عند إزالة الشاشة)
- توافق أدوات تزيين النظام وطريقة الإدخال
يحتوي الصف DisplayWindowSettings
على إعدادات لهذه الخيارات. يتم حفظها على القرص في القسم /data
في display_settings.xml
في كل مرة يتم فيها تغيير أحد الإعدادات. لمزيد من التفاصيل، يُرجى الاطّلاع على DisplayWindowSettings.AtomicFileStorage
وDisplayWindowSettings#writeSettings()
. يمكن لمصنّعي الأجهزة توفير قيم تلقائية في display_settings.xml
لإعدادات أجهزتهم. ومع ذلك، بما أنّ الملف مخزّن في /data
،
قد تحتاج إلى منطق إضافي لاستعادة الملف إذا تم محوه.
يستخدم نظام التشغيل Android 10 تلقائيًا
DisplayInfo#uniqueId
كمعرّف لشاشة العرض عند الاحتفاظ
بالإعدادات. يجب ملء الحقل uniqueId
لجميع الإعلانات الصورية. بالإضافة إلى ذلك، يكون ثابتًا لشاشات العرض الفعلية وشاشات العرض على الشبكة. يمكن أيضًا استخدام منفذ شاشة فعلية كمعرّف، ويمكن ضبط ذلك في
DisplayWindowSettings#mIdentifier
. عند كل عملية كتابة، يتم كتابة جميع الإعدادات، لذا يمكن تعديل المفتاح المستخدَم لإدخال بيانات العرض في وحدة التخزين بأمان. لمزيد من التفاصيل، يُرجى الاطّلاع على معرّفات العرض الثابتة.
يتم الاحتفاظ بالإعدادات في الدليل /data
لأسباب تاريخية. في الأصل، كانت تُستخدَم للاحتفاظ بالإعدادات التي يضبطها المستخدم، مثل تدوير الشاشة.
معرّفات العرض الثابت
لم يوفّر الإصدار 9 من نظام التشغيل Android (والإصدارات الأقدم) معرّفات ثابتة للشاشات في إطار العمل. عند إضافة شاشة إلى النظام، يتم إنشاء Display#mDisplayId
أو DisplayInfo#displayId
لهذه الشاشة من خلال زيادة عدّاد ثابت. إذا أضاف النظام الشاشة نفسها ثم أزالها، سيتم إنشاء معرّف مختلف.
إذا كان الجهاز يتضمّن شاشات عرض متعددة متاحة منذ بدء التشغيل، يمكن تعيين معرّفات مختلفة لشاشات العرض، وذلك حسب التوقيت. على الرغم من أنّ الإصدار 9 من نظام التشغيل Android (والإصدارات الأقدم) كان يتضمّن DisplayInfo#uniqueId
، لم يكن يحتوي على معلومات كافية للتمييز بين شاشات العرض لأنّه كان يتم تحديد شاشات العرض المادية على أنّها local:0
أو local:1
لتمثيل شاشة العرض المضمّنة وشاشة العرض الخارجية.
يغيّر نظام التشغيل Android 10 قيمة DisplayInfo#uniqueId
لإضافة معرّف ثابت والتمييز بين الشاشات المحلية وشاشات الشبكة والشاشات الافتراضية.
نوع العرض | التنسيق |
---|---|
بالتوقيت المحلي | local:<stable-id> |
الشبكة | network:<mac-address> |
افتراضي | virtual:<package-name-and-name> |
بالإضافة إلى التحديثات التي تم إجراؤها على uniqueId
،
يحتوي DisplayInfo.address
على DisplayAddress
، وهو
معرّف عرض ثابت عند إعادة التشغيل. في نظام التشغيل Android 10، تتيح واجهة برمجة التطبيقات DisplayAddress
استخدام شاشات فعلية وشاشات شبكة. يحتوي DisplayAddress.Physical
على معرّف عرض ثابت (كما هو الحال في uniqueId
) ويمكن إنشاؤه باستخدام DisplayAddress#fromPhysicalDisplayId()
.
يوفّر نظام التشغيل Android 10 أيضًا طريقة ملائمة للحصول على معلومات المنفذ (Physical#getPort()
). ويمكن استخدام هذه الطريقة في إطار العمل لتحديد الشاشات بشكل ثابت. على سبيل المثال، يتم استخدامه في
DisplayWindowSettings
). يحتوي DisplayAddress.Network
على عنوان MAC ويمكن إنشاؤه باستخدام
DisplayAddress#fromMacAddress()
.
تتيح هذه الإضافات لمصنّعي الأجهزة تحديد الشاشات في عمليات الإعداد الثابتة
للشاشات المتعددة، كما تتيح لهم ضبط إعدادات وميزات مختلفة للنظام
باستخدام معرّفات الشاشات الثابتة، مثل منافذ الشاشات المادية. هذه الطرق مخفية ومخصّصة للاستخدام ضمن system_server
فقط.
بالنظر إلى معرّف شاشة HWC (الذي يمكن أن يكون غير شفاف وغير ثابت دائمًا)، تعرض هذه الطريقة رقم المنفذ (الخاص بالنظام الأساسي) المكوّن من 8 بت والذي يحدّد موصّلاً ماديًا لإخراج الشاشة، بالإضافة إلى مجموعة بيانات EDID الخاصة بالشاشة.
يستخرج SurfaceFlinger معلومات الشركة المصنّعة أو الطراز من EDID لإنشاء معرّفات عرض ثابتة 64 بت يتم عرضها للإطار. إذا لم تكن هذه الطريقة متاحة أو ظهرت أخطاء، سيعود SurfaceFlinger إلى وضع MD القديم، حيث تكون قيمة DisplayInfo#address
فارغة ويتم ترميز DisplayInfo#uniqueId
بشكل ثابت، كما هو موضّح أعلاه.
للتحقّق من توفّر هذه الميزة، نفِّذ ما يلي:
$ dumpsys SurfaceFlinger --display-id # Example output. Display 21691504607621632 (HWC display 0): port=0 pnpId=SHP displayName="LQ123P1JX32" Display 9834494747159041 (HWC display 2): port=1 pnpId=HWP displayName="HP Z24i" Display 1886279400700944 (HWC display 1): port=2 pnpId=AUS displayName="ASUS MB16AP"
استخدام أكثر من شاشتَي عرض
في الإصدار 9 من نظام التشغيل Android (والإصدارات الأقدم)، افترضت كل من SurfaceFlinger وDisplayManagerService
وجود شاشتَين فعليتَين على الأكثر بمعرّفَين مبرمَجين مسبقًا هما 0 و1.
بدءًا من Android 10، يمكن أن تستفيد خدمة SurfaceFlinger من واجهة برمجة تطبيقات Hardware Composer (HWC) لإنشاء أرقام تعريف ثابتة للشاشات، ما يتيح لها إدارة عدد غير محدود من الشاشات المادية. لمزيد من المعلومات، اطّلِع على معرّفات العرض الثابتة.
يمكن للإطار البحث عن الرمز المميز IBinder
لشاشة عرض فعلية من خلال SurfaceControl#getPhysicalDisplayToken
بعد الحصول على معرّف شاشة العرض 64 بت من SurfaceControl#getPhysicalDisplayIds
أو من حدث DisplayEventReceiver
hotplug.
في نظام التشغيل Android 10 (والإصدارات الأقدم)، تكون الشاشة الداخلية الأساسية TYPE_INTERNAL
ويتم وضع علامة TYPE_EXTERNAL
على جميع الشاشات الثانوية بغض النظر عن نوع الاتصال. لذلك، يتم التعامل مع شاشات العرض الداخلية الإضافية على أنّها خارجية.
كحلّ بديل، يمكن أن يضع الرمز البرمجي الخاص بالجهاز افتراضات بشأن DisplayAddress.Physical#getPort
إذا كان HWC معروفًا وكانت منطق تخصيص المنفذ قابلاً للتوقّع.
تمت إزالة هذا القيد في الإصدار 11 من نظام التشغيل Android (والإصدارات الأحدث).
- في نظام التشغيل Android 11، تكون شاشة العرض الأولى التي يتم تسجيلها أثناء عملية التشغيل هي شاشة العرض الأساسية. لا يهم نوع الاتصال (داخلي أو خارجي). ومع ذلك، يظل صحيحًا أنّه لا يمكن فصل شاشة العرض الأساسية، وبالتالي يجب أن تكون شاشة عرض داخلية من الناحية العملية. يُرجى العِلم أنّ بعض الهواتف القابلة للطي تتضمّن شاشات داخلية متعددة.
- يتم تصنيف الشاشات الثانوية بشكل صحيح على أنّها
Display.TYPE_INTERNAL
أوDisplay.TYPE_EXTERNAL
(المعروفة سابقًا باسمDisplay.TYPE_BUILT_IN
وDisplay.TYPE_HDMI
على التوالي) استنادًا إلى نوع الاتصال.
التنفيذ
في الإصدار 9 من نظام التشغيل Android والإصدارات الأقدم، يتم تحديد الشاشات من خلال معرّفات 32 بت، حيث يمثّل 0 الشاشة الداخلية، و1 الشاشة الخارجية، و[2, INT32_MAX]
الشاشات الافتراضية الخاصة بـ HWC، ويمثّل -1 شاشة غير صالحة أو شاشة افتراضية غير خاصة بـ HWC.
بدءًا من Android 10، يتم منح شاشات العرض أرقام تعريف ثابتة ودائمة، ما يتيح لـ SurfaceFlinger وDisplayManagerService
تتبُّع أكثر من شاشتَي عرض والتعرّف على شاشات العرض التي تمّت رؤيتها سابقًا. إذا كان HWC يتوافق مع IComposerClient.getDisplayIdentificationData
ويوفّر بيانات تعريف الشاشة، يحلّل SurfaceFlinger بنية EDID ويخصّص معرّفات شاشة ثابتة 64 بت للشاشات الفعلية والافتراضية في HWC. يتم التعبير عن المعرّفات باستخدام نوع خيار، حيث تمثّل القيمة الفارغة شاشة عرض غير صالحة أو شاشة عرض افتراضية غير تابعة لـ HWC. في حال عدم توفّر دعم HWC، يعود SurfaceFlinger إلى السلوك القديم مع شاشتَين ماديتَين كحد أقصى.
التركيز على كل شاشة
لإتاحة مصادر إدخال متعددة تستهدف شاشات عرض فردية في الوقت نفسه، يمكن ضبط نظام التشغيل Android 10 لإتاحة نوافذ متعددة مركّزة، على أن يكون هناك نافذة واحدة على الأكثر لكل شاشة عرض. هذا النوع مخصّص فقط لأنواع خاصة من الأجهزة التي يتفاعل فيها عدة مستخدمين مع الجهاز نفسه في الوقت نفسه ويستخدمون طرق إدخال أو أجهزة مختلفة، مثل Android Automotive.
ننصح بعدم تفعيل هذه الميزة على الأجهزة العادية، بما في ذلك الأجهزة المتعددة الشاشات أو تلك التي تُستخدم لتوفير تجارب شبيهة بتجربة استخدام الكمبيوتر المكتبي. ويرجع ذلك بشكل أساسي إلى مشكلة أمان قد تجعل المستخدمين يتساءلون عن النافذة التي يتم التركيز عليها.
لنفترض أنّ المستخدم يُدخل معلومات آمنة في حقل إدخال نصي، ربما لتسجيل الدخول إلى تطبيق مصرفي أو إدخال نص يحتوي على معلومات حساسة. يمكن أن ينشئ تطبيق ضار شاشة عرض افتراضية خارج الشاشة لتنفيذ نشاط، مع حقل إدخال نص أيضًا. تتضمّن الأنشطة المشروعة والضارة تركيزًا، ويعرض كلاهما مؤشر إدخال نشطًا (مؤشر يومض).
ومع ذلك، بما أنّ الإدخال من لوحة المفاتيح (الأجهزة أو البرامج) يتم فقط في النشاط الأعلى (التطبيق الذي تم تشغيله مؤخرًا)، يمكن لتطبيق ضار إنشاء شاشة عرض افتراضية مخفية، وبالتالي الحصول على إدخال المستخدم، حتى عند استخدام لوحة مفاتيح برمجية على شاشة الجهاز الأساسي.
استخدِم com.android.internal.R.bool.config_perDisplayFocusEnabled
لضبط التركيز على كل شاشة.
التوافق
المشكلة: في الإصدار 9 من نظام التشغيل Android والإصدارات الأقدم، يمكن أن تركّز النافذة على عنصر واحد فقط في النظام في كل مرة.
الحل: في الحالات النادرة التي يتم فيها التركيز على نافذتين من العملية نفسها، يركّز النظام فقط على النافذة التي تظهر في أعلى ترتيب Z. تتم إزالة هذا القيد للتطبيقات التي تستهدف الإصدار 10 من نظام التشغيل Android، ومن المتوقّع أن تتمكّن هذه التطبيقات من دعم التركيز على نوافذ متعدّدة في الوقت نفسه.
التنفيذ
تتحكّم سياسة WindowManagerService#mPerDisplayFocusEnabled
في مدى توفّر هذه الميزة. في ActivityManager
،
يتم الآن استخدام ActivityDisplay#getFocusedStack()
بدلاً من التتبُّع الشامل
في أحد المتغيّرات. تحدّد الدالة ActivityDisplay#getFocusedStack()
التركيز استنادًا إلى ترتيب Z بدلاً من تخزين القيمة مؤقتًا. ويتم ذلك حتى لا يحتاج سوى مصدر واحد، وهو WindowManager، إلى تتبُّع ترتيب الأنشطة حسب المحور Z.
تتّبع ActivityStackSupervisor#getTopDisplayFocusedStack()
أسلوبًا مشابهًا في الحالات التي يجب فيها تحديد الحزمة المركّزة في أعلى الشاشة في النظام. يتم الانتقال بين الحزم من الأعلى إلى الأسفل، بحثًا عن الحزمة الأولى المؤهَّلة.
يمكن الآن أن يحتوي InputDispatcher
على نوافذ متعدّدة في المقدّمة (نافذة واحدة لكل شاشة). إذا كان حدث الإدخال خاصًا بالشاشة، يتم إرساله إلى النافذة المركّز عليها في الشاشة المعنية. بخلاف ذلك، يتم إرسالها إلى النافذة التي تم التركيز عليها في الشاشة التي تم التركيز عليها، وهي الشاشة التي تفاعل معها المستخدم مؤخرًا.
يمكنك الاطّلاع على InputDispatcher::mFocusedWindowHandlesByDisplay
وInputDispatcher::setFocusedDisplay()
. يتم أيضًا تحديث التطبيقات التي تعمل في الوضع المركّز بشكل منفصل في InputManagerService من خلال NativeInputManager::setFocusedApplication()
.
في WindowManager
، يتم أيضًا تتبُّع النوافذ المركّزة بشكل منفصل.
يمكنك الاطّلاع على DisplayContent#mCurrentFocus
وDisplayContent#mFocusedApp
واستخداماتهما. تم نقل طرق تتبُّع التركيز وتعديله ذات الصلة من WindowManagerService
إلى DisplayContent
.