Dưới đây là nội dung cập nhật cho các khu vực hiển thị cụ thể này:
- Đổi kích thước hoạt động và màn hình
- Kích thước màn hình và tỷ lệ khung hình
- Chính sách về quảng cáo hiển thị
- Chế độ cài đặt cửa sổ hiển thị
- Mã nhận dạng hiển thị tĩnh
- Sử dụng nhiều hơn 2 màn hình
- Tiêu điểm trên mỗi màn hình
Đổi kích thước hoạt động và màn hình
Để cho biết rằng một ứng dụng có thể không hỗ trợ chế độ nhiều cửa sổ hoặc tính năng đổi kích thước, các hoạt động sẽ sử dụng thuộc tính resizeableActivity=false
. Các vấn đề thường gặp mà ứng dụng gặp phải khi hoạt động được đổi kích thước bao gồm:
- Một hoạt động có thể có cấu hình khác với ứng dụng hoặc một thành phần không phải hình ảnh khác. Một lỗi thường gặp là đọc các chỉ số hiển thị theo ngữ cảnh của ứng dụng. Giá trị trả về sẽ không được điều chỉnh theo các chỉ số về khu vực hiển thị mà một hoạt động được hiển thị.
- Một hoạt động có thể không xử lý được việc đổi kích thước và gặp sự cố, hiển thị giao diện người dùng bị méo hoặc bị mất trạng thái do chạy lại mà không lưu trạng thái của thực thể.
- Ứng dụng có thể cố gắng sử dụng các toạ độ đầu vào tuyệt đối (thay vì các toạ độ tương đối với vị trí cửa sổ). Điều này có thể làm hỏng dữ liệu đầu vào trong nhiều cửa sổ.
Trên Android 7 (trở lên), bạn có thể đặt resizeableActivity=false
để luôn chạy một ứng dụng ở chế độ toàn màn hình. Trong trường hợp này, nền tảng sẽ ngăn các hoạt động không thể đổi kích thước chuyển sang chế độ chia đôi màn hình. Nếu người dùng cố gắng gọi một hoạt động không thể đổi kích thước từ trình chạy trong khi đã ở chế độ chia đôi màn hình, thì nền tảng sẽ thoát khỏi chế độ chia đôi màn hình và chạy hoạt động không thể đổi kích thước ở chế độ toàn màn hình.
Không được chạy các ứng dụng đặt thuộc tính này thành false
một cách rõ ràng trong tệp kê khai ở chế độ nhiều cửa sổ, trừ khi áp dụng chế độ tương thích:
- Cấu hình tương tự được áp dụng cho quy trình, trong đó chứa tất cả các hoạt động và thành phần không phải hoạt động.
- Cấu hình được áp dụng đáp ứng các yêu cầu của CDD cho màn hình tương thích với ứng dụng.
Trong Android 10, nền tảng này vẫn ngăn các hoạt động không thể đổi kích thước chuyển sang chế độ chia đôi màn hình, nhưng có thể tạm thời điều chỉnh tỷ lệ nếu hoạt động đã khai báo hướng hoặc tỷ lệ khung hình cố định. Nếu không, hoạt động sẽ đổi kích thước để lấp đầy toàn bộ màn hình như trong Android 9 trở xuống.
Phương thức triển khai mặc định áp dụng chính sách sau:
Khi một hoạt động được khai báo là không tương thích với chế độ nhiều cửa sổ thông qua việc sử dụng thuộc tính android:resizeableActivity
và khi hoạt động đó đáp ứng một trong các điều kiện được mô tả bên dưới, thì khi cấu hình màn hình đã áp dụng phải thay đổi, hoạt động và quy trình sẽ được lưu bằng cấu hình ban đầu và người dùng sẽ được cung cấp một cơ hội để khởi chạy lại quy trình ứng dụng nhằm sử dụng cấu hình màn hình đã cập nhật.
- Có hướng cố định thông qua việc áp dụng
android:screenOrientation
- Ứng dụng có tỷ lệ khung hình tối đa hoặc tối thiểu mặc định theo cấp độ API nhắm đến hoặc khai báo tỷ lệ khung hình một cách rõ ràng
Hình này cho thấy một hoạt động không thể đổi kích thước với tỷ lệ khung hình đã khai báo. Khi gập thiết bị, cửa sổ sẽ được điều chỉnh theo tỷ lệ để vừa với khu vực trong khi vẫn duy trì tỷ lệ khung hình bằng cách sử dụng hộp thư phù hợp. Ngoài ra, tuỳ chọn khởi động lại hoạt động được cung cấp cho người dùng mỗi khi khu vực hiển thị của hoạt động thay đổi.
Khi mở thiết bị, cấu hình, kích thước và tỷ lệ khung hình của hoạt động sẽ không thay đổi, nhưng tuỳ chọn khởi động lại hoạt động sẽ hiển thị.
Khi bạn không đặt resizeableActivity
(hoặc đặt thành true
), ứng dụng sẽ hỗ trợ đầy đủ tính năng đổi kích thước.
Triển khai
Một hoạt động không thể đổi kích thước có hướng hoặc tỷ lệ khung hình cố định được gọi là chế độ tương thích với kích thước (SCM) trong mã. Điều kiện được xác định trong ActivityRecord#shouldUseSizeCompatMode()
. Khi một hoạt động SCM chạy, cấu hình liên quan đến màn hình (chẳng hạn như kích thước hoặc mật độ) sẽ được cố định trong cấu hình ghi đè được yêu cầu, nhờ đó, hoạt động không còn phụ thuộc vào cấu hình hiển thị hiện tại.
Nếu hoạt động SCM không thể lấp đầy toàn bộ màn hình, hoạt động đó sẽ được căn trên và căn giữa theo chiều ngang. Giới hạn hoạt động được tính toán bằng
AppWindowToken#calculateCompatBoundsTransformation()
.
Khi một hoạt động SCM sử dụng cấu hình màn hình khác với vùng chứa của hoạt động đó (ví dụ: màn hình được đổi kích thước hoặc hoạt động được chuyển sang một màn hình khác), ActivityRecord#inSizeCompatMode()
sẽ là true và SizeCompatModeActivityController
(trong Giao diện người dùng hệ thống) sẽ nhận được lệnh gọi lại để hiển thị nút khởi động lại quy trình.
Hiển thị kích thước và tỷ lệ khung hình
Android 10 hỗ trợ các tỷ lệ khung hình mới, từ tỷ lệ cao của màn hình dài và màn hình mỏng đến tỷ lệ 1:1. Ứng dụng có thể xác định ApplicationInfo#maxAspectRatio
và ApplicationInfo#minAspectRatio
của màn hình mà ứng dụng có thể xử lý.
Hình 1. Ví dụ về tỷ lệ ứng dụng được hỗ trợ trong Android 10
Quá trình triển khai thiết bị có thể có màn hình phụ với kích thước và độ phân giải nhỏ hơn so với yêu cầu của Android 9 trở xuống (chiều rộng hoặc chiều cao tối thiểu là 2, 5 inch, tối thiểu là 320 DP đối với smallestScreenWidth
), nhưng chỉ có thể đặt các hoạt động chọn hỗ trợ những màn hình nhỏ này ở đó.
Các ứng dụng có thể chọn sử dụng tính năng này bằng cách khai báo kích thước tối thiểu được hỗ trợ nhỏ hơn hoặc bằng kích thước màn hình mục tiêu. Sử dụng các thuộc tính bố cục hoạt động android:minHeight
và android:minWidth
trong AndroidManifest để thực hiện việc này.
Chính sách hiển thị
Android 10 tách biệt và di chuyển một số chính sách hiển thị nhất định từ cách triển khai WindowManagerPolicy
mặc định trong PhoneWindowManager
sang các lớp trên mỗi màn hình, chẳng hạn như:
- Trạng thái hiển thị và chế độ xoay
- Một số phím và tính năng theo dõi sự kiện chuyển động
- Cửa sổ trang trí và giao diện người dùng hệ thống
Trong Android 9 (trở xuống), lớp PhoneWindowManager
xử lý các chính sách hiển thị, trạng thái và chế độ cài đặt, tính năng xoay, theo dõi khung cửa sổ trang trí, v.v. Android 10 chuyển hầu hết các tính năng này sang lớp DisplayPolicy
, ngoại trừ tính năng theo dõi độ xoay đã được chuyển sang DisplayRotation
.
Hiển thị chế độ cài đặt cửa sổ
Trong Android 10, chế độ cài đặt chế độ cửa sổ có thể định cấu hình cho mỗi màn hình đã được mở rộng để bao gồm:
- Chế độ hiển thị cửa sổ mặc định
- Giá trị tràn màn hình
- Chế độ xoay và xoay người dùng
- Kích thước, mật độ và chế độ điều chỉnh theo tỷ lệ bắt buộc
- Chế độ xoá nội dung (khi màn hình bị xoá)
- Hỗ trợ trang trí hệ thống và IME
Lớp DisplayWindowSettings
chứa các chế độ cài đặt cho các tuỳ chọn này. Các giá trị này được lưu vào ổ đĩa trong phân vùng /data
trong display_settings.xml
mỗi khi một chế độ cài đặt thay đổi. Để biết thông tin chi tiết, hãy xem DisplayWindowSettings.AtomicFileStorage
và DisplayWindowSettings#writeSettings()
. Nhà sản xuất thiết bị có thể cung cấp giá trị mặc định trong display_settings.xml
cho cấu hình thiết bị của họ. Tuy nhiên, vì tệp được lưu trữ trong /data
, nên có thể cần thêm logic để khôi phục tệp nếu bị xoá bằng tính năng xoá sạch.
Theo mặc định, Android 10 sử dụng DisplayInfo#uniqueId
làm giá trị nhận dạng cho màn hình khi duy trì chế độ cài đặt. Bạn phải điền uniqueId
cho tất cả màn hình. Ngoài ra, công cụ này cũng ổn định cho màn hình thực và màn hình mạng. Bạn cũng có thể sử dụng cổng của màn hình thực tế làm giá trị nhận dạng. Bạn có thể đặt giá trị này trong DisplayWindowSettings#mIdentifier
. Sau mỗi lần ghi, tất cả chế độ cài đặt sẽ được ghi để có thể an toàn cập nhật khoá dùng cho mục hiển thị trong bộ nhớ. Để biết thông tin chi tiết, hãy xem phần Mã nhận dạng hiển thị tĩnh.
Các chế độ cài đặt vẫn được lưu trong thư mục /data
vì lý do trước đây. Ban đầu, các chế độ này được dùng để duy trì các chế độ cài đặt do người dùng đặt, chẳng hạn như chế độ xoay màn hình.
Giá trị nhận dạng màn hình tĩnh
Android 9 (và các phiên bản thấp hơn) không cung cấp giá trị nhận dạng ổn định cho màn hình trong khung. Khi một màn hình được thêm vào hệ thống, Display#mDisplayId
hoặc DisplayInfo#displayId
sẽ được tạo cho màn hình đó bằng cách tăng bộ đếm tĩnh. Nếu hệ thống thêm và xoá cùng một màn hình, thì một mã nhận dạng khác sẽ dẫn đến.
Nếu một thiết bị có nhiều màn hình từ lúc khởi động, thì màn hình có thể được chỉ định các giá trị nhận dạng khác nhau, tuỳ thuộc vào thời gian. Mặc dù Android 9 (trở xuống) có DisplayInfo#uniqueId
, nhưng thiết bị này không chứa đủ thông tin để phân biệt giữa các màn hình vì màn hình thực tế được xác định là local:0
hoặc local:1
để biểu thị màn hình tích hợp và màn hình bên ngoài.
Android 10 thay đổi DisplayInfo#uniqueId
để thêm giá trị nhận dạng ổn định và để phân biệt giữa màn hình cục bộ, màn hình mạng và màn hình ảo.
Loại hiển thị | Định dạng |
---|---|
Địa phương | local:<stable-id> |
Mạng | network:<mac-address> |
Qua mạng | virtual:<package-name-and-name> |
Ngoài các bản cập nhật cho uniqueId
, DisplayInfo.address
còn chứa DisplayAddress
– một giá trị nhận dạng màn hình ổn định khi khởi động lại. Trong Android 10, DisplayAddress
hỗ trợ màn hình thực và màn hình mạng. DisplayAddress.Physical
chứa một mã hiển thị ổn định (tương tự như trong uniqueId
) và có thể được tạo bằng DisplayAddress#fromPhysicalDisplayId()
.
Android 10 cũng cung cấp một phương thức thuận tiện để lấy thông tin cổng (Physical#getPort()
). Bạn có thể sử dụng phương thức này trong khung để xác định tĩnh màn hình. Ví dụ: thuộc tính này được dùng trong DisplayWindowSettings
). DisplayAddress.Network
chứa địa chỉ MAC và có thể được tạo bằng DisplayAddress#fromMacAddress()
.
Những nội dung bổ sung này cho phép nhà sản xuất thiết bị xác định màn hình trong thiết lập tĩnh nhiều màn hình, đồng thời định cấu hình nhiều chế độ cài đặt và tính năng hệ thống bằng cách sử dụng giá trị nhận dạng màn hình tĩnh, chẳng hạn như cổng cho màn hình thực. Các phương thức này bị ẩn và chỉ dùng trong system_server
.
Với mã màn hình HWC (có thể mờ và không phải lúc nào cũng ổn định), phương thức này sẽ trả về số cổng 8 bit (dành riêng cho nền tảng) giúp xác định đầu nối vật lý cho đầu ra màn hình, cũng như blob EDID của màn hình.
SurfaceFlinger trích xuất thông tin nhà sản xuất hoặc kiểu máy từ EDID để tạo mã màn hình 64 bit ổn định hiển thị cho khung. Nếu phương thức này không được hỗ trợ hoặc gặp lỗi, SurfaceFlinger sẽ quay lại chế độ MD cũ, trong đó DisplayInfo#address
rỗng và DisplayInfo#uniqueId
được mã hoá cứng, như mô tả ở trên.
Để xác minh rằng tính năng này được hỗ trợ, hãy chạy mã:
$ 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"
Sử dụng nhiều hơn 2 màn hình
Trong Android 9 (trở xuống), SurfaceFlinger và DisplayManagerService
giả định rằng có tối đa 2 màn hình thực có mã nhận dạng được cố định giá trị trong mã là 0 và 1.
Kể từ Android 10, SurfaceFlinger có thể tận dụng API Trình tổng hợp phần cứng (HWC) để tạo mã màn hình ổn định, cho phép ứng dụng này quản lý số lượng màn hình thực tế tuỳ ý. Để tìm hiểu thêm, hãy xem phần Giá trị nhận dạng hiển thị tĩnh.
Khung này có thể tra cứu mã thông báo IBinder
cho màn hình thực thông qua SurfaceControl#getPhysicalDisplayToken
sau khi lấy mã màn hình 64 bit từ SurfaceControl#getPhysicalDisplayIds
hoặc từ sự kiện hotplug DisplayEventReceiver
.
Trong Android 10 (và thấp hơn), màn hình trong chính là TYPE_INTERNAL
và tất cả màn hình phụ đều được gắn cờ là TYPE_EXTERNAL
bất kể loại kết nối. Do đó, các màn hình nội bộ bổ sung được coi là màn hình bên ngoài.
Để giải quyết vấn đề này, mã dành riêng cho thiết bị có thể đưa ra các giả định về DisplayAddress.Physical#getPort
nếu đã biết HWC và logic phân bổ cổng có thể dự đoán được.
Giới hạn này đã bị xoá trong Android 11 (trở lên).
- Trong Android 11, màn hình đầu tiên được báo cáo trong quá trình khởi động là màn hình chính. Loại kết nối (nội bộ so với bên ngoài) không liên quan. Tuy nhiên, thực tế vẫn là không thể ngắt kết nối màn hình chính và màn hình chính phải là màn hình nội bộ. Xin lưu ý rằng một số điện thoại có thể gập lại có nhiều màn hình nội bộ.
- Màn hình phụ được phân loại chính xác là
Display.TYPE_INTERNAL
hoặcDisplay.TYPE_EXTERNAL
(trước đây gọi làDisplay.TYPE_BUILT_IN
vàDisplay.TYPE_HDMI
tương ứng) tuỳ thuộc vào loại kết nối.
Triển khai
Trong Android 9 trở xuống, màn hình được xác định bằng mã nhận dạng 32 bit, trong đó 0 là màn hình trong, 1 là màn hình ngoài, [2, INT32_MAX]
là màn hình ảo HWC và -1 biểu thị màn hình không hợp lệ hoặc màn hình ảo không phải HWC.
Kể từ Android 10, màn hình sẽ được cấp các mã nhận dạng ổn định và cố định, cho phép SurfaceFlinger và DisplayManagerService
theo dõi nhiều hơn 2 màn hình và nhận dạng các màn hình đã thấy trước đó. Nếu HWC hỗ trợ IComposerClient.getDisplayIdentificationData
và cung cấp dữ liệu nhận dạng màn hình, thì SurfaceFlinger sẽ phân tích cú pháp cấu trúc EDID và phân bổ các mã màn hình 64 bit ổn định cho màn hình thực và HWC. Các mã nhận dạng được biểu thị bằng một loại tuỳ chọn, trong đó giá trị rỗng biểu thị màn hình không hợp lệ hoặc màn hình ảo không phải HWC. Nếu không có tính năng hỗ trợ HWC, SurfaceFlinger sẽ quay lại hành vi cũ với tối đa 2 màn hình thực.
Tiêu điểm trên mỗi màn hình
Để hỗ trợ một số nguồn đầu vào nhắm đến từng màn hình cùng một lúc, bạn có thể định cấu hình Android 10 để hỗ trợ nhiều cửa sổ được lấy làm tâm điểm, tối đa là một cửa sổ trên mỗi màn hình. Thuộc tính này chỉ dành cho các loại thiết bị đặc biệt khi nhiều người dùng tương tác với cùng một thiết bị cùng lúc và sử dụng nhiều phương thức nhập hoặc thiết bị, chẳng hạn như Android Automotive.
Bạn không nên bật tính năng này cho các thiết bị thông thường, bao gồm cả thiết bị nhiều màn hình hoặc thiết bị dùng cho trải nghiệm giống như máy tính. Điều này chủ yếu là do mối lo ngại về bảo mật có thể khiến người dùng thắc mắc cửa sổ nào có tâm điểm nhập.
Hãy tưởng tượng người dùng nhập thông tin an toàn vào trường nhập dữ liệu văn bản, có thể là đăng nhập vào ứng dụng ngân hàng hoặc nhập văn bản chứa thông tin nhạy cảm. Ứng dụng độc hại có thể tạo màn hình ảo ngoài màn hình để thực thi một hoạt động, cũng có trường nhập văn bản. Các hoạt động hợp pháp và độc hại đều có tâm điểm và cả hai đều hiển thị chỉ báo đầu vào đang hoạt động (con trỏ nhấp nháy).
Tuy nhiên, vì dữ liệu đầu vào từ bàn phím (phần cứng hoặc phần mềm) chỉ được nhập vào hoạt động trên cùng (ứng dụng được khởi chạy gần đây nhất), nên bằng cách tạo màn hình ảo ẩn, ứng dụng độc hại có thể lấy dữ liệu đầu vào của người dùng, ngay cả khi sử dụng bàn phím phần mềm trên màn hình chính của thiết bị.
Sử dụng com.android.internal.R.bool.config_perDisplayFocusEnabled
để đặt tiêu điểm trên mỗi màn hình.
Khả năng tương thích
Vấn đề: Trong Android 9 trở xuống, mỗi lần chỉ có tối đa một cửa sổ trong hệ thống có tiêu điểm.
Giải pháp: Trong trường hợp hiếm gặp khi hai cửa sổ từ cùng một quy trình được lấy tiêu điểm, hệ thống chỉ cung cấp tiêu điểm cho cửa sổ ở thứ tự Z cao hơn. Quy định hạn chế này sẽ bị xoá đối với các ứng dụng nhắm đến Android 10. Tại thời điểm đó, các ứng dụng này dự kiến có thể hỗ trợ nhiều cửa sổ được lấy làm tâm điểm cùng lúc.
Triển khai
WindowManagerService#mPerDisplayFocusEnabled
kiểm soát khả năng sử dụng tính năng này. Trong ActivityManager
, ActivityDisplay#getFocusedStack()
hiện được sử dụng thay cho tính năng theo dõi chung trong một biến. ActivityDisplay#getFocusedStack()
xác định tiêu điểm dựa trên thứ tự Z thay vì lưu giá trị vào bộ nhớ đệm. Điều này là để chỉ một nguồn, WindowManager, cần theo dõi thứ tự Z của các hoạt động.
ActivityStackSupervisor#getTopDisplayFocusedStack()
áp dụng phương pháp tương tự đối với những trường hợp đó khi phải xác định được ngăn xếp được lấy làm tâm điểm trên cùng trong hệ thống. Các ngăn xếp được truyền từ trên xuống dưới để tìm kiếm ngăn xếp đủ điều kiện đầu tiên.
InputDispatcher
hiện có thể có nhiều cửa sổ được lấy tiêu điểm (một cửa sổ trên mỗi màn hình). Nếu một sự kiện đầu vào là dành riêng cho màn hình, thì sự kiện đó sẽ được gửi đến cửa sổ được lấy tiêu điểm trong màn hình tương ứng. Nếu không, thông báo sẽ được gửi đến cửa sổ được lấy tiêu điểm trong màn hình được lấy tiêu điểm, tức là màn hình mà người dùng tương tác gần đây nhất.
Hãy xem InputDispatcher::mFocusedWindowHandlesByDisplay
và InputDispatcher::setFocusedDisplay()
. Các ứng dụng được lấy làm tâm điểm cũng được cập nhật riêng trong InputManagerService thông qua NativeInputManager::setFocusedApplication()
.
Trong WindowManager
, các cửa sổ được lấy tiêu điểm cũng được theo dõi riêng biệt.
Hãy xem DisplayContent#mCurrentFocus
và DisplayContent#mFocusedApp
cũng như các trường hợp sử dụng tương ứng. Các phương thức theo dõi và cập nhật tiêu điểm liên quan đã được chuyển từ WindowManagerService
sang DisplayContent
.