בהמשך מפורטים העדכונים שבוצעו באזורים הספציפיים האלה במסך:
- שינוי הגודל של פעילויות ותצוגות
- גדלי תצוגה ויחסי גובה-רוחב
- מדיניות בנושא מודעות לרשת המדיה
- הגדרות של חלון התצוגה
- מזהים סטטיים של מודעות לרשת המדיה
- שימוש ביותר משני מסכים
- מיקוד לכל תצוגה
שינוי הגודל של פעילויות ותצוגות
כדי לציין שאפליקציה לא תומכת במצב מרובה חלונות או בשינוי גודל, משתמשים בפעילויות במאפיין resizeableActivity=false
. בעיות נפוצות שנתקלים בהן באפליקציות כשמשנים את הגודל של הפעילויות כוללות:
- לפעילות יכולה להיות הגדרה שונה מזו של האפליקציה או של רכיב אחר שאינו ויזואלי. טעות נפוצה היא קריאת מדדי התצוגה מהקשר האפליקציה. הערכים שיוחזרו לא יותאמו למדדים של האזור הגלוי שבו מוצגת פעילות.
- יכול להיות שפעילות מסוימת לא תתמודד עם שינוי הגודל ותקרוס, תציג ממשק משתמש מעוות או תאבד את הסטטוס שלה בגלל הפעלה מחדש בלי לשמור את סטטוס המופע.
- אפליקציה עשויה לנסות להשתמש בקואורדינטות קלט מוחלטות (במקום בקואורדינטות יחסיות למיקום החלון), מה שעלול לשבש את הקלט במצב ריבוי חלונות.
ב-Android 7 (ובגרסאות מתקדמות יותר), אפשר להגדיר אפליקציה כך שתמיד תפעל במצב מסך מלא.resizeableActivity=false
במקרה הזה, הפלטפורמה מונעת פעילויות שלא ניתן לשנות את הגודל שלהן לעבור למסך מפוצל. אם המשתמש מנסה להפעיל פעילות שלא ניתן לשנות את הגודל שלה ממפעיל האפליקציות כשהוא כבר במצב מסך מפוצל, הפלטפורמה יוצאת ממצב מסך מפוצל ומפעילה את הפעילות שלא ניתן לשנות את הגודל שלה במצב מסך מלא.
אסור להפעיל במצב ריבוי חלונות אפליקציות שבהן המאפיין הזה מוגדר במפורש לערך false
בקובץ המניפסט, אלא אם מופעל מצב התאימות:
- אותה הגדרה חלה על התהליך, שמכיל את כל הפעילויות ואת הרכיבים שאינם פעילויות.
- התצורה שהוחלה עומדת בדרישות ה-CDD לתצוגות שמתאימות לאפליקציות.
ב-Android 10, הפלטפורמה עדיין מונעת פעילויות שלא ניתן לשנות את הגודל שלהן לעבור למצב מסך מפוצל, אבל אפשר לשנות את הגודל שלהן באופן זמני אם הפעילות הצהירה על כיוון קבוע או על יחס גובה-רוחב קבוע. אם לא, גודל הפעילות ישתנה כדי למלא את המסך כולו כמו ב-Android 9 ובגרסאות קודמות.
ההטמעה שמוגדרת כברירת מחדל מחילה את המדיניות הבאה:
אם הפעילות מוצהרת כלא תואמת לריבוי חלונות באמצעות השימוש במאפיין android:resizeableActivity
, ואם הפעילות עומדת באחד מהתנאים שמתוארים בהמשך, אז כשצריך לשנות את הגדרת המסך שחלה על הפעילות, הפעילות והתהליך נשמרים עם ההגדרה המקורית, והמשתמש מקבל אפשרות להפעיל מחדש את תהליך האפליקציה כדי להשתמש בהגדרת המסך המעודכנת.
- האם הכיוון קבוע באמצעות החלת
android:screenOrientation
- האפליקציה כוללת יחס גובה-רוחב מקסימלי או מינימלי שמוגדר כברירת מחדל על ידי טירגוט לרמת API או שהיא כוללת הצהרה מפורשת על יחס הגובה-רוחב
באיור הזה מוצגת פעילות שלא ניתן לשנות את הגודל שלה, עם יחס גובה-רוחב מוצהר. כשמקפלים את המכשיר, גודל החלון מוקטן כדי להתאים לאזור, תוך שמירה על יחס הגובה-רוחב באמצעות הוספת מסגרת שחורה מתאימה. בנוסף, בכל פעם שמשנים את אזור התצוגה של הפעילות, מוצגת למשתמש אפשרות להפעיל מחדש את הפעילות.
כשפותחים את המכשיר, ההגדרה, הגודל ויחס הגובה-רוחב של הפעילות לא משתנים, אבל מוצגת האפשרות להפעיל מחדש את הפעילות.
אם המדיניות resizeableActivity
לא מוגדרת (או שהיא מוגדרת לערך true
), האפליקציה תומכת באופן מלא בשינוי גודל.
הטמעה
פעילות שלא ניתן לשנות את הגודל שלה עם כיוון או יחס גובה-רוחב קבועים נקראת בקוד מצב תאימות לגודל (SCM). המצב מוגדר ב-ActivityRecord#shouldUseSizeCompatMode()
. כשמפעילים פעילות SCM, ההגדרה שקשורה למסך (כמו גודל או צפיפות) קבועה בהגדרת הביטול שנדרשה, כך שהפעילות כבר לא תלויה בהגדרת התצוגה הנוכחית.
אם הפעילות של SCM לא יכולה למלא את המסך כולו, היא מיושרת לחלק העליון וממורכזת אופקית. הגבולות של הפעילות מחושבים על ידי
AppWindowToken#calculateCompatBoundsTransformation()
.
כשפעילות SCM משתמשת בהגדרת מסך שונה מהקונטיינר שלה (לדוגמה, אם משנים את הגודל של התצוגה או מעבירים את הפעילות לתצוגה אחרת), הערך של ActivityRecord#inSizeCompatMode()
הוא true והערך של SizeCompatModeActivityController
(בממשק המשתמש של המערכת) מקבל את הקריאה החוזרת כדי להציג את לחצן ההפעלה מחדש של התהליך.
גדלי תצוגה ויחסי גובה-רוחב
Android מגרסה 10 ואילך תומך ביחסי גובה-רוחב חדשים,
ממסכים ארוכים וצרים עם יחסים גבוהים ועד ליחסים של 1:1. אפליקציות יכולות להגדיר את ApplicationInfo#maxAspectRatio
ואת ApplicationInfo#minAspectRatio
של המסך שהן יכולות לטפל בהם.
איור 1. דוגמאות ליחסי גובה-רוחב של אפליקציות שנתמכים ב-Android 10
יכול להיות שבמכשירים מסוימים יש מסכים משניים עם גדלים ורזולוציות קטנים יותר מאלה שנדרשים ב-Android 9 ומגרסאות קודמות (רוחב או גובה מינימלי של 2.5 אינץ', מינימום של 320 DPI ל-smallestScreenWidth
), אבל רק פעילויות שמוגדרות לתמיכה במסכים הקטנים האלה יכולות להופיע בהם.
אפליקציות יכולות להצטרף על ידי הצהרה על גודל מינימלי נתמך שהוא קטן יותר או שווה לגודל התצוגה של היעד. כדי לעשות זאת, משתמשים במאפייני פריסת הפעילות android:minHeight
ו-android:minWidth
בקובץ AndroidManifest.
מדיניות בנושא רשת המדיה
ב-Android 10, מדיניות מסוימת לגבי תצוגה מופרדת ומועברת מההטמעה של ברירת המחדל WindowManagerPolicy
ב-PhoneWindowManager
לסיווגים לפי תצוגה, כמו:
- מצב התצוגה וסיבוב המסך
- מעקב אחרי אירועים מסוימים של מקשים ותנועה
- ממשק המשתמש של המערכת וחלונות קישוט
ב-Android 9 (ובגרסאות קודמות), המחלקה PhoneWindowManager
טיפלה במדיניות התצוגה, במצב ובהגדרות, בסיבוב, במעקב אחר מסגרת חלון העיטור ועוד. ב-Android 10, רוב הפונקציות האלה מועברות למחלקה DisplayPolicy
, למעט מעקב אחרי סיבוב, שמועבר למחלקה DisplayRotation
.
הגדרות של חלון התצוגה
ב-Android 10, הגדרת החלונות שניתנת להגדרה לכל מסך הורחבה וכוללת עכשיו:
- מצב ברירת המחדל של חלון התצוגה
- ערכי Overscan
- סיבוב משתמשים ומצב סיבוב
- גודל, צפיפות ומצב שינוי קנה מידה שנכפים
- מצב הסרת תוכן (כשהתצוגה מוסרת)
- תמיכה בעיטורים של המערכת וב-IME
ההגדרות של האפשרויות האלה נמצאות בכיתה DisplayWindowSettings
. הן נשמרות בדיסק במחיצה /data
ב-display_settings.xml
בכל פעם שמשנים הגדרה. פרטים נוספים זמינים במאמרים DisplayWindowSettings.AtomicFileStorage
וDisplayWindowSettings#writeSettings()
. יצרני מכשירים יכולים לספק ערכי ברירת מחדל ב-display_settings.xml
להגדרת המכשיר שלהם. עם זאת, מכיוון שהקובץ מאוחסן ב-/data
, יכול להיות שיידרש היגיון נוסף כדי לשחזר את הקובץ אם הוא נמחק על ידי ניגוב.
כברירת מחדל, מערכת Android 10 משתמשת ב-DisplayInfo#uniqueId
כמזהה לתצוגה כששומרים את ההגדרות. צריך למלא את הערך uniqueId
בכל התצוגות. בנוסף, הוא יציב בתצוגות פיזיות וברשתות. אפשר גם להשתמש ביציאה של מסך פיזי כמזהה, שאפשר להגדיר ב-DisplayWindowSettings#mIdentifier
. בכל פעולת כתיבה, כל ההגדרות נכתבות, כך שניתן לעדכן בבטחה את המפתח שמשמש להצגת רשומה באחסון. פרטים נוספים מופיעים במאמר בנושא מזהים סטטיים של מסכים.
ההגדרות נשמרות בספרייה /data
מסיבות היסטוריות. במקור, הם שימשו לשמירת הגדרות שהמשתמש קבע, כמו סבב תצוגה.
מזהים של מודעות לרשת המדיה
ב-Android 9 (ובגרסאות קודמות) לא סופקו מזהים יציבים לתצוגות ב-framework. כשמסך נוסף למערכת, נוצר עבורו Display#mDisplayId
או DisplayInfo#displayId
על ידי הגדלה של מונה סטטי. אם המערכת הוסיפה והסירה את אותו מסך, נוצר מזהה שונה.
אם במכשיר יש כמה מסכים שזמינים מאתחול, יכול להיות שיוקצו למסכים מזהים שונים, בהתאם לתזמון. ב-Android 9 (ובגרסאות קודמות) נכלל 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 הביטים שמזהה מחבר פיזי לפלט תצוגה, וגם את ה-blob של EDID של התצוגה.
SurfaceFlinger מחלץ מידע על היצרן או הדגם מ-EDID כדי ליצור את מזהי התצוגה היציבים בני 64 הביטים שמוצגים למסגרת. אם השיטה הזו לא נתמכת או שהיא מחזירה שגיאה, SurfaceFlinger חוזר למצב MD מדור קודם, שבו DisplayInfo#address
הוא null ו-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"
שימוש ביותר משני מסכים
ב-Android 9 (ובגרסאות קודמות), SurfaceFlinger ו-DisplayManagerService
הניחו שקיימים לכל היותר שני מסכים פיזיים עם מזהים מקודדים 0 ו-1.
החל מ-Android 10, SurfaceFlinger יכול להשתמש ב-API של Hardware Composer (HWC) כדי ליצור מזהי תצוגה יציבים, וכך לנהל מספר שרירותי של מסכים פיזיים. מידע נוסף זמין במאמר בנושא מזהים סטטיים של מודעות לרשת המדיה.
המסגרת יכולה לחפש את אסימון IBinder
עבור תצוגה פיזית באמצעות SurfaceControl#getPhysicalDisplayToken
אחרי קבלת מזהה התצוגה בן 64 הביטים מ-SurfaceControl#getPhysicalDisplayIds
או מאירוע DisplayEventReceiver
של חיבור והפעלה.
ב-Android 10 (ובגרסאות קודמות), התצוגה הפנימית הראשית היא TYPE_INTERNAL
וכל התצוגות המשניות מסומנות כ-TYPE_EXTERNAL
, ללא קשר לסוג החיבור. לכן, מסכים פנימיים נוספים נחשבים למסכים חיצוניים.
כפתרון עקיף, קוד ספציפי למכשיר יכול להניח הנחות לגבי
DisplayAddress.Physical#getPort
אם ה-HWC ידוע וההקצאה של היציאות
צפויה.
המגבלה הזו הוסרה ב-Android 11 (ומגרסאות מתקדמות יותר).
- ב-Android 11, המסך הראשון שמדווח במהלך האתחול הוא המסך הראשי. סוג החיבור (פנימי או חיצוני) לא רלוונטי. עם זאת, עדיין נכון שאי אפשר לנתק את המסך הראשי, ולכן בפועל הוא חייב להיות מסך פנימי. שימו לב: בחלק מהטלפונים המתקפלים יש כמה מסכים פנימיים.
- מסכים משניים מסווגים בצורה נכונה כ-
Display.TYPE_INTERNAL
או כ-Display.TYPE_EXTERNAL
(לשעברDisplay.TYPE_BUILT_IN
ו-Display.TYPE_HDMI
בהתאמה) בהתאם לסוג החיבור שלהם.
הטמעה
ב-Android 9 ומגרסאות קודמות, המסכים מזוהים באמצעות מזהים של 32 ביט,
כאשר 0 הוא המסך הפנימי, 1 הוא המסך החיצוני, [2, INT32_MAX]
הם מסכים וירטואליים של HWC, ו-1- מייצג מסך לא תקין או מסך וירטואלי שאינו HWC.
החל מ-Android 10, למסכים מוקצים מזהים יציבים וקבועים, שמאפשרים ל-SurfaceFlinger ול-DisplayManagerService
לעקוב אחרי יותר משני מסכים ולזהות מסכים שנראו בעבר. אם ה-HWC
תומך ב-IComposerClient.getDisplayIdentificationData
ומספק נתוני זיהוי של התצוגה, SurfaceFlinger מנתח את מבנה ה-EDID ומקצה מזהי תצוגה יציבים של 64 ביט לתצוגות פיזיות ולתצוגות וירטואליות של HWC. המזהים מבוטאים באמצעות סוג אפשרות, כאשר הערך null מייצג תצוגה לא חוקית או תצוגה וירטואלית לא חוקית של HWC. ללא תמיכה ב-HWC, SurfaceFlinger חוזר להתנהגות מדור קודם עם שני מסכים פיזיים לכל היותר.
התמקדות בכל מסך
כדי לתמוך בכמה מקורות קלט שמטרגטים מסכים נפרדים בו-זמנית, אפשר להגדיר את Android 10 כך שיתמוך בכמה חלונות ממוקדים, לכל היותר אחד לכל מסך. התכונה הזו מיועדת רק לסוגים מיוחדים של מכשירים שבהם כמה משתמשים מקיימים אינטראקציה עם אותו מכשיר בו-זמנית ומשתמשים בשיטות או במכשירים שונים להזנת נתונים, כמו Android Automotive.
לא מומלץ להפעיל את התכונה הזו במכשירים רגילים, כולל מכשירים עם כמה מסכים או מכשירים שמשמשים כחלופת מחשב. הסיבה העיקרית לכך היא בעיית אבטחה שעלולה לגרום למשתמשים לתהות איזה חלון נמצא במוקד הקלט.
תארו לעצמכם משתמש שמזין מידע מאובטח בשדה להזנת טקסט, למשל כשהוא מתחבר לאפליקציית בנק או מזין טקסט שמכיל מידע רגיש. אפליקציה זדונית יכולה ליצור תצוגה וירטואלית מחוץ למסך כדי להפעיל פעילות, וגם שדה להזנת טקסט. פעילויות לגיטימיות ופעילויות זדוניות מקבלות התמקדות, ובשתיהן מוצג אינדיקטור קלט פעיל (סמן מהבהב).
עם זאת, מאחר שקלט ממקלדת (חומרה או תוכנה) מוזן רק לפעילות העליונה ביותר (האפליקציה שהופעלה לאחרונה), אפליקציה זדונית יכולה ליצור תצוגה וירטואלית מוסתרת כדי לתפוס קלט משתמש, גם כשמשתמשים במקלדת תוכנה בתצוגה הראשית של המכשיר.
משתמשים ב-com.android.internal.R.bool.config_perDisplayFocusEnabled
כדי להגדיר פוקוס לכל מסך.
תאימות
בעיה: ב-Android 9 ובגרסאות קודמות, לכל היותר חלון אחד במערכת נמצא במיקוד בכל פעם.
פתרון: במקרה הנדיר שבו שני חלונות מאותו תהליך יקבלו פוקוס, המערכת תספק פוקוס רק לחלון שנמצא גבוה יותר בסדר Z. ההגבלה הזו מוסרת באפליקציות שמטרגטות את Android 10, ובשלב הזה צפוי שהן יוכלו לתמוך במספר חלונות שמתמקדים בהם בו-זמנית.
הטמעה
המדיניות 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
.