下面提供了對這些特定於顯示的區域所做的更新:
調整活動和顯示的大小
為了指示應用程序可能不支持多窗口模式或調整大小,活動使用resizeableActivity=false
屬性。調整活動大小時應用程序遇到的常見問題包括:
- Activity 可以具有與應用程序或其他非可視組件不同的配置。一個常見的錯誤是從應用上下文中讀取顯示指標。返回的值不會調整為顯示活動的可見區域指標。
- Activity 可能無法處理大小調整和崩潰、顯示扭曲的 UI 或由於重新啟動而丟失狀態而不保存實例狀態。
- 應用程序可能會嘗試使用絕對輸入坐標(而不是相對於窗口位置的坐標),這可能會破壞多窗口中的輸入。
在 Android 7(及更高版本)中,可以將應用設置為resizeableActivity=false
以始終以全屏模式運行。在這種情況下,平台會阻止不可調整大小的活動進入分屏。如果用戶在分屏模式下嘗試從啟動器調用不可調整大小的活動,則平台將退出分屏模式並以全屏模式啟動不可調整大小的活動。
在清單中明確將此屬性設置為false
的應用不得在多窗口模式下啟動,除非應用了兼容模式:
- 相同的配置應用於包含所有活動和非活動組件的流程。
- 應用配置滿足應用兼容顯示器的 CDD 要求。
在 Android 10 中,平台仍會阻止不可調整大小的 Activity 進入分屏模式,但如果 Activity 聲明了固定的方向或縱橫比,它們可以臨時縮放。如果不是,則 Activity 會調整大小以填滿整個屏幕,就像在 Android 9 及更低版本中一樣。
默認實現應用以下策略:
當一個活動通過使用android:resizeableActivity
屬性聲明為與多窗口不兼容並且該活動滿足下述條件之一時,當應用的屏幕配置必須更改時,活動和進程將與原始配置一起保存並且用戶可以重新啟動應用程序進程以使用更新的屏幕配置。
- 通過
android:screenOrientation
的應用程序固定方向 - 通過定位 API 級別或顯式聲明寬高比,應用具有默認的最大或最小寬高比
此圖顯示了一個具有聲明縱橫比的不可調整大小的活動。折疊設備時,窗口會按比例縮小以適應區域,同時使用適當的信箱保持縱橫比。此外,每次更改活動的顯示區域時,都會向用戶提供重新啟動活動選項。
展開設備時,Activity 的配置、大小和縱橫比沒有變化,但會顯示重啟 Activity 的選項。
當resizeableActivity
未設置(或設置為true
)時,應用程序完全支持調整大小。
執行
具有固定方向或縱橫比的不可調整大小的活動在代碼中稱為大小兼容模式 (SCM)。條件在ActivityRecord#shouldUseSizeCompatMode()
中定義。當一個 SCM Activity 啟動時,屏幕相關的配置(如大小或密度)在請求的覆蓋配置中是固定的,因此該 Activity 不再依賴於當前的顯示配置。
如果 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),但只有選擇支持這些小型顯示器的活動才能放在那裡。
應用程序可以通過聲明小於等於目標顯示大小的 oe 的最小支持大小來選擇加入。使用 AndroidManifest 中的android:minHeight
和android:minWidth
活動佈局屬性來執行此操作。
顯示政策
Android 10 將某些顯示策略從PhoneWindowManager
中的默認WindowManagerPolicy
實現分離並移動到每個顯示類中,例如:
- 顯示狀態和旋轉
- 一些按鍵和運動事件跟踪
- 系統UI和裝飾窗口
在 Android 9(及更低版本)中, PhoneWindowManager
類處理顯示策略、狀態和設置、旋轉、裝飾窗口框架跟踪等。 Android 10 將大部分內容移至DisplayPolicy
類,但旋轉跟踪除外,該類已移至DisplayRotation
。
顯示窗口設置
在 Android 10 中,可配置的每顯示器窗口設置已擴展為包括:
- 默認顯示窗口模式
- 過掃描值
- 用戶旋轉和旋轉模式
- 強制大小、密度和縮放模式
- 內容移除模式(移除顯示時)
- 支持系統裝飾和輸入法
DisplayWindowSettings
類包含這些選項的設置。每次更改設置時,它們都會保存到display_settings.xml
的/data
分區中的磁盤。有關詳細信息,請參閱DisplayWindowSettings.AtomicFileStorage
和DisplayWindowSettings#writeSettings()
。設備製造商可以在display_settings.xml
中為其設備配置提供默認值。但是,由於文件存儲在/data
中,如果被擦除擦除,可能需要額外的邏輯來恢復文件。
默認情況下,Android 10 在持久化設置時使用DisplayInfo#uniqueId
作為顯示器的標識符。應該為所有顯示器填充uniqueId
。此外,它對於物理和網絡顯示也很穩定。也可以使用物理顯示器的端口作為標識符,可以在DisplayWindowSettings#mIdentifier
中設置。每次寫入時,都會寫入所有設置,因此可以安全地更新用於存儲中顯示條目的密鑰。有關詳細信息,請參閱靜態顯示標識符。
由於歷史原因,設置保留在/data
目錄中。最初,它們用於保存用戶設置的設置,例如顯示旋轉。
靜態顯示標識符
Android 9(及更低版本)沒有為框架中的顯示器提供穩定的標識符。將顯示器添加到系統時,會通過增加靜態計數器為該顯示器生成Display#mDisplayId
或DisplayInfo#displayId
。如果系統添加和刪除相同的顯示器,則會產生不同的 ID。
如果一個設備在啟動時有多個可用的顯示器,則可以根據時間為這些顯示器分配不同的標識符。雖然 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
包含一個穩定的顯示 ID(與uniqueId
相同),可以使用DisplayAddress#fromPhysicalDisplayId()
創建。
Android 10 還提供了一種方便的方法來獲取端口信息( Physical#getPort()
)。該方法可以在框架中用於靜態識別顯示器。例如,它在DisplayWindowSettings
中使用)。 DisplayAddress.Network
包含 MAC 地址,可以使用DisplayAddress#fromMacAddress()
創建。
這些新增功能使設備製造商能夠識別靜態多顯示器設置中的顯示器,並使用靜態顯示器標識符(例如物理顯示器的端口)配置不同的系統設置和功能。這些方法是隱藏的,只能在system_server
中使用。
給定一個 HWC 顯示 ID(它可能是不透明的且並不總是穩定的),此方法返回(特定於平台的)8 位端口號,該端口號標識用於顯示輸出的物理連接器,以及顯示的 EDID blob。 SurfaceFlinger 從 EDID 中提取製造商或型號信息,以生成向框架公開的穩定 64 位顯示 ID。如果此方法不受支持或出錯,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
假設最多存在兩個硬編碼 ID 為 0 和 1 的物理顯示器。
從 Android 10 開始,SurfaceFlinger 可以利用 Hardware Composer (HWC) API 生成穩定的顯示 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
並提供顯示標識數據,SurfaceFlinger 解析 EDID 結構並為物理和 HWC 虛擬顯示分配穩定的 64 位顯示 ID。 ID 使用選項類型表示,其中 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()
。重點應用也通過NativeInputManager::setFocusedApplication()
在 InputManagerService 中單獨更新。
在WindowManager
中,焦點窗口也被單獨跟踪。請參閱DisplayContent#mCurrentFocus
和DisplayContent#mFocusedApp
以及各自的用途。相關的焦點跟踪和更新方法已從WindowManagerService
移至DisplayContent
。