以下是針對這些特定顯示區域所做的更新:
調整活動和螢幕大小
如要指出應用程式可能不支援多視窗模式或調整大小,活動會使用 resizeableActivity=false
屬性。活動調整大小時,應用程式可能會遇到下列常見問題:
- 活動的設定可能與應用程式或其他非視覺元件不同。常見的錯誤是從應用程式內容讀取顯示指標。回傳的值不會調整為活動顯示的顯示區域指標。
- 活動可能無法處理大小調整作業而當機、顯示扭曲的 UI,或是因重新啟動而失去狀態,但未儲存例項狀態。
- 應用程式可能會嘗試使用絕對輸入座標 (而非相對於視窗位置的座標),這可能會導致多視窗中的輸入內容中斷。
在 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
(在系統 UI 中) 會收到回呼,以顯示程序重新啟動按鈕。
顯示大小和顯示比例
Android 10 支援新的顯示比例,從長而窄的螢幕高寬比到 1:1 比例。應用程式可定義可處理的畫面 ApplicationInfo#maxAspectRatio
和 ApplicationInfo#minAspectRatio
。
圖 1. Android 10 支援的應用程式比例範例
裝置實作項目可採用次要螢幕,其大小和解析度小於 Android 9 和更低版本所需的大小和解析度 (寬度或高度至少為 2.5 吋,smallestScreenWidth
的寬度或高度至少為 320 DP),但只有選擇支援這些小螢幕的活動才能放置在其中。
應用程式可以聲明最小支援的尺寸,該尺寸必須小於或等於目標顯示器尺寸。請在 AndroidManifest 中使用 android:minHeight
和 android:minWidth
活動版面配置屬性。
顯示政策
Android 10 會將特定顯示政策從 PhoneWindowManager
中的預設 WindowManagerPolicy
實作項目分離並移至個別顯示類別,例如:
- 顯示狀態和旋轉
- 部分按鍵和動作事件追蹤
- 系統 UI 和裝飾視窗
在 Android 9 (和以下版本) 中,PhoneWindowManager
類別會處理顯示政策、狀態和設定、旋轉、裝飾視窗框架追蹤等。Android 10 將其中大部分項目移至 DisplayPolicy
類別,但旋轉追蹤已移至 DisplayRotation
。
顯示畫面視窗設定
在 Android 10 中,可設定的每個螢幕視窗設定已擴充至包含:
- 預設顯示視窗模式
- 超掃值
- 使用者旋轉和旋轉模式
- 強制設定的大小、密度和縮放模式
- 移除內容模式 (當螢幕移除時)
- 支援系統修飾和 IME
DisplayWindowSettings
類別包含這些選項的設定。每次變更設定時,系統都會將這些值儲存在 display_settings.xml
的 /data
分割區中。詳情請參閱 DisplayWindowSettings.AtomicFileStorage
和 DisplayWindowSettings#writeSettings()
。裝置製造商可以在 display_settings.xml
中為裝置設定提供預設值。不過,由於檔案儲存在 /data
中,因此如果檔案遭到清除,可能需要額外的邏輯來還原檔案。
根據預設,Android 10 會在儲存設定時,使用 DisplayInfo#uniqueId
做為螢幕的 ID。uniqueId
應填入所有顯示裝置。此外,實體和網路顯示器也能穩定運作。您也可以使用實體螢幕的通訊埠做為 ID,這可在 DisplayWindowSettings#mIdentifier
中設定。每次寫入時,系統都會寫入所有設定,因此可安全地更新用於儲存空間中顯示項目的鍵。詳情請參閱「靜態顯示 ID」。
設定會保留在 /data
目錄中,以便追蹤歷史記錄。原本用於儲存使用者設定的設定,例如螢幕旋轉。
靜態顯示 ID
Android 9 (和以下版本) 並未在架構中提供顯示器的穩定 ID。當系統新增顯示幕時,系統會透過遞增靜態計數器,為該顯示幕產生 Display#mDisplayId
或 DisplayInfo#displayId
。如果系統新增和移除相同的顯示螢幕,就會產生不同的 ID。
如果裝置在啟動時提供多個螢幕,則可視情況為螢幕指派不同的 ID。雖然 Android 9 (和更早版本) 包含 DisplayInfo#uniqueId
,但由於實體螢幕會標示為 local:0
或 local:1
,用於代表內建和外部螢幕,因此系統中沒有足夠的資訊可用於區分螢幕。
Android 10 會變更 DisplayInfo#uniqueId
,新增穩定的 ID,並區分本機、網路和虛擬顯示器。
顯示類型 | 格式 |
---|---|
地區性 | local:<stable-id> |
網路 | network:<mac-address> |
虛擬 | virtual:<package-name-and-name> |
除了 uniqueId
的更新項目外,DisplayInfo.address
還包含 DisplayAddress
,這是在重新啟動時保持穩定的顯示 ID。在 Android 10 中,DisplayAddress
支援實體和網路螢幕。DisplayAddress.Physical
包含穩定的顯示 ID (與 uniqueId
相同),可透過 DisplayAddress#fromPhysicalDisplayId()
建立。
Android 10 也提供方便的方法來取得連接埠資訊 (Physical#getPort()
)。這個方法可用於架構中,用於靜態識別螢幕。例如,它會用於 DisplayWindowSettings
)。DisplayAddress.Network
包含 MAC 位址,可透過 DisplayAddress#fromMacAddress()
建立。
這些新增功能可讓裝置製造商在靜態多螢幕設定中識別螢幕,並使用靜態螢幕 ID (例如實體螢幕的連接埠) 設定不同的系統設定和功能。這些方法是隱藏的,且僅供 system_server
使用。
在提供 HWC 螢幕 ID (可能不透明且不一定穩定) 的情況下,這個方法會傳回 (平台專屬) 8 位元通訊埠編號,用於識別螢幕輸出的實體連接器,以及螢幕的 EDID blob。SurfaceFlinger 會從 EDID 擷取製造商或型號資訊,產生公開給架構的穩定 64 位元顯示 ID。如果系統不支援此方法或發生錯誤,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"
使用兩個以上的螢幕
在 Android 9 (和更低版本) 中,SurfaceFlinger 和 DisplayManagerService
會假設最多有兩個實體螢幕,且硬式編碼 ID 為 0 和 1。
從 Android 10 開始,SurfaceFlinger 可利用硬體編寫程式 (HWC) API 產生穩定的顯示 ID,進而管理任意數量的實體螢幕。詳情請參閱「靜態顯示 ID」。
在從 SurfaceControl#getPhysicalDisplayIds
或 DisplayEventReceiver
熱插拔事件取得 64 位元螢幕 ID 後,架構可以透過 SurfaceControl#getPhysicalDisplayToken
查詢實體螢幕的 IBinder
權杖。
在 Android 10 (和更低版本) 中,主要內部螢幕為 TYPE_INTERNAL
,所有次要螢幕都會標示為 TYPE_EXTERNAL
,不論連線類型為何。因此,其他內部螢幕會視為外部螢幕。如要解決這個問題,如果已知 HWC 且可預測通訊埠分配邏輯,裝置專屬程式碼可對 DisplayAddress.Physical#getPort
做出假設。
這項限制已在 Android 11 (及以上版本) 中移除。
- 在 Android 11 中,開機時回報的第一個螢幕即為主要螢幕。連線類型 (內部或外部) 無關緊要。不過,主要螢幕仍無法斷開連線,因此實際上必須是內建螢幕。請注意,部分摺疊式手機有多個內部螢幕。
- 次要螢幕會根據連接類型正確歸類為
Display.TYPE_INTERNAL
或Display.TYPE_EXTERNAL
(分別是舊稱Display.TYPE_BUILT_IN
和Display.TYPE_HDMI
)。
實作
在 Android 9 以下版本中,螢幕會以 32 位元 ID 識別,其中 0 是內部螢幕、1 是外部螢幕、[2, INT32_MAX]
是 HWC 虛擬螢幕,而 -1 則代表無效螢幕或非 HWC 虛擬螢幕。
從 Android 10 開始,螢幕會獲得穩定且持久的 ID,讓 SurfaceFlinger 和 DisplayManagerService
追蹤兩個以上的螢幕,並辨識先前看過的螢幕。如果 HWC 支援 IComposerClient.getDisplayIdentificationData
並提供顯示裝置 ID 資料,SurfaceFlinger 就會剖析 EDID 結構,並為實體和 HWC 虛擬顯示裝置分配穩定的 64 位元顯示裝置 ID。這些 ID 會使用選項類型表示,其中空值代表無效的顯示器或非 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
。