Hỗ trợ quảng cáo hiển thị

Dưới đây là những điểm cập nhật đối với các khu vực dành riêng cho quảng cáo hiển thị:

Đổ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 đổi kích thước, các hoạt động sẽ sử dụng thuộc tính resizeableActivity=false. Sau đây là những vấn đề thường gặp mà các ứng dụng gặp phải khi hoạt động được đổi kích thước:

  • 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 trực quan khác. Một lỗi thường gặp là đọc các chỉ số hiển thị từ ngữ cảnh ứng dụng. Các giá trị được trả về sẽ không được điều chỉnh theo các chỉ số về vùng 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 thay đổi kích thước và gặp sự cố, hiển thị giao diện người dùng bị biến dạng hoặc mất trạng thái do khởi chạy lại mà không lưu trạng thái phiên bản.
  • Một ứng dụng có thể cố gắng sử dụng toạ độ đầu vào tuyệt đối (thay vì toạ độ tương ứng với vị trí cửa sổ). Điều này có thể làm gián đoạn hoạt động nhập dữ liệu ở chế độ nhiều cửa sổ.

Trong Android 7 (trở lên), bạn có thể đặt resizeableActivity=false cho một ứng dụng để luôn chạy ở 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ể thay đổi kích thước từ trình chạy trong khi đang ở chế độ chia đôi màn hình, thì nền tảng sẽ thoát chế độ chia đôi màn hình và chạy hoạt động không thể thay đổi kích thước ở chế độ toàn màn hình.

Những ứ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 sẽ không được chạy ở chế độ nhiều cửa sổ, trừ phi chế độ tương thích được áp dụng:

  • Cùng một cấu hình được áp dụng cho quy trình, trong đó có 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 đối với 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 đổi kích thước chuyển sang chế độ chia đôi màn hình, nhưng các hoạt động này có thể được tạm thời điều chỉnh tỷ lệ nếu hoạt động đã khai báo một 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.

Việc triển khai mặc định sẽ á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 được á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ơ chế để 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 mới.

  • Được cố định hướng 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 bằng cách nhắm đến cấp độ API hoặc khai báo tỷ lệ khung hình một cách rõ ràng

Hình này minh hoạ 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 thu nhỏ để 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 phương pháp hòm thư thích hợp. Ngoài ra, mỗi khi vùng hiển thị cho hoạt động thay đổi, người dùng sẽ thấy lựa chọn khởi động lại hoạt động.

Khi bạn 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 lựa chọn khởi động lại hoạt động sẽ xuất hiện.

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 được khởi 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, do đó, hoạt động này không còn phụ thuộc vào cấu hình màn hình hiện tại nữa.

Nếu hoạt động SCM không thể lấp đầy toàn bộ màn hình, thì hoạt động đó sẽ được căn chỉnh ở trên cùng và căn giữa theo chiều ngang. Ranh giới 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 thay đổ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 nút khởi động lại quy trình.

Kích thước hiển thị 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ỏng đến tỷ lệ 1:1. Các ứng dụng có thể xác định ApplicationInfo#maxAspectRatioApplicationInfo#minAspectRatio của màn hình mà chúng có thể xử lý.

tỷ lệ khung hình của ứng dụng trong Android 10

Hình 1. Ví dụ về các tỷ lệ ứng dụng được hỗ trợ trong Android 10

Các chế độ 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 những kích thước và độ phân giải mà Android 9 trở xuống yêu cầu (tối thiểu 2, 5 inch chiều rộng hoặc chiều cao, tối thiểu 320 DP cho smallestScreenWidth), nhưng chỉ những hoạt động chọn hỗ trợ các màn hình nhỏ này mới có thể được đặt ở đó.

Các ứng dụng có thể chọn tham gia 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. Hãy sử dụng các thuộc tính bố cục hoạt động android:minHeightandroid:minWidth trong AndroidManifest để thực hiện việc này.

Chính sách hiển thị

Android 10 tách và di chuyển một số chính sách hiển thị nhất định khỏi 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 và hướng xoay màn hình
  • Theo dõi một số khoá và sự kiện chuyển động
  • Giao diện người dùng hệ thống và cửa sổ trang trí

Trong Android 9 (và các phiên bản thấp hơn), lớp PhoneWindowManager đã xử lý các chính sách, trạng thái và chế độ cài đặt hiển thị, hướng xoay, theo dõi khung cửa sổ trang trí và nhiều nội dung khác. Android 10 chuyển hầu hết các thành phần này sang lớp DisplayPolicy, ngoại trừ tính năng theo dõi hướng xoay đã được chuyển sang DisplayRotation.

Cài đặt cửa sổ hiển thị

Trong Android 10, chế độ cài đặt 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ị mặc định theo cửa sổ
  • Giá trị quét dư
  • Chế độ xoay và hướng xoay của người dùng
  • Chế độ kích thước, mật độ và chia tỷ lệ bắt buộc
  • Chế độ xoá nội dung (khi màn hình bị tháo)
  • Hỗ trợ các thành phần trang trí hệ thống và IME

Lớp DisplayWindowSettings chứa các chế độ cài đặt cho những lựa chọn này. Các tệp này được lưu trữ trên đĩa trong phân vùng /data trong display_settings.xml mỗi khi một chế độ cài đặt được thay đổi. Để biết thông tin chi tiết, hãy xem DisplayWindowSettings.AtomicFileStorageDisplayWindowSettings#writeSettings(). Nhà sản xuất thiết bị có thể cung cấp các 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 bạn có thể cần thêm logic để khôi phục tệp nếu bị xoá bằng thao tác xoá dữ liệu.

Theo mặc định, Android 10 sử dụng DisplayInfo#uniqueId làm mã nhận dạng cho màn hình khi duy trì các chế độ cài đặt. Bạn phải điền sẵn uniqueId cho tất cả màn hình. Ngoài ra, nó còn ổn định cho màn hình thực và màn hình mạng. Bạn cũng có thể dùng cổng của màn hình thực làm giá trị nhận dạng. Giá trị này có thể được đặt trong DisplayWindowSettings#mIdentifier. Khi ghi, tất cả các chế độ cài đặt đều được ghi, vì vậy, bạn có thể cập nhật khoá được dùng cho một mục hiển thị trong bộ nhớ một cách an toàn. Để biết thông tin chi tiết, hãy xem phần Giá trị nhận dạng hiển thị tĩnh.

Các chế độ cài đặt được duy trì trong thư mục /data vì lý do lịch sử. Ban đầu, chúng đượ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 (trở xuống) 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ì sẽ có một mã nhận dạng khác.

Nếu một thiết bị có nhiều màn hình ngay từ khi khởi động, thì các 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 (và các phiên bản trước đó) có DisplayInfo#uniqueId, nhưng 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 đượ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 ngoài.

Android 10 thay đổi DisplayInfo#uniqueId để thêm một 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 màn hình Định dạng
Địa phương
local:<stable-id>
Mạng
network:<mac-address>
Trực tuyến
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 trong quá trình 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ã nhận dạng hiển thị ổn định (giống 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 về cổng (Physical#getPort()). Phương thức này có thể được dùng trong khung để xác định tĩnh các màn hình. Ví dụ: tham số 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 điểm bổ sung này cho phép nhà sản xuất thiết bị xác định màn hình trong chế độ thiết lập tĩnh nhiều màn hình và định cấu hình các chế độ cài đặt cũng như tính năng hệ thống khác nhau bằng cách sử dụng các giá trị nhận dạng màn hình tĩnh, chẳng hạn như các cổng cho màn hình thực. Các phương thức này bị ẩn và chỉ được dùng trong system_server.

Với mã nhận dạng màn hình HWC (có thể không rõ ràng 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 (cụ thể theo nền tảng) xác định một đầ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 về nhà sản xuất hoặc kiểu máy từ EDID để tạo các mã nhận dạng màn hình 64 bit ổn định được 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 có giá trị rỗng và DisplayInfo#uniqueId được mã hoá cứng, như mô tả ở trên.

Để xác minh xem tính năng này có được hỗ trợ hay không, hãy chạy lệnh:

$ 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"

Dùng nhiều hơn 2 màn hình

Trong Android 9 (và các phiên bản thấp hơn), SurfaceFlinger và DisplayManagerService giả định sự tồn tại của tối đa 2 màn hình thực có mã nhận dạng được mã hoá cứng là 0 và 1.

Kể từ Android 10, SurfaceFlinger có thể tận dụng API Trình kết hợp phần cứng (HWC) để tạo mã nhận dạng màn hình ổn định, cho phép quản lý số lượng màn hình thực 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ột màn hình thực thông qua SurfaceControl#getPhysicalDisplayToken sau khi nhận được mã nhận dạng màn hình 64 bit từ SurfaceControl#getPhysicalDisplayIds hoặc từ một sự kiện DisplayEventReceiver hotplug.

Trong Android 10 (trở xuống), màn hình chính bên trong 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 sẽ được coi là màn hình ngoài. Để khắc phục, mã dành riêng cho thiết bị có thể đưa ra giả định về DisplayAddress.Physical#getPort nếu HWC đã biết và logic phân bổ cổng có thể dự đoán được.

Hạn chế này đã được 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, màn hình chính vẫn không thể ngắt kết nối và phải là màn hình trong. 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 trong.
  • Màn hình phụ được phân loại chính xác là Display.TYPE_INTERNAL hoặc Display.TYPE_EXTERNAL (trước đây lần lượt là Display.TYPE_BUILT_INDisplay.TYPE_HDMI) tuỳ thuộc vào loại kết nối của màn hình.

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 đại diện cho 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 được cấp mã nhận dạng ổn định và liên tục, cho phép SurfaceFlinger và DisplayManagerService theo dõi hơn 2 màn hình và nhận dạng các màn hình đã từng xuất hiện. Nếu HWC hỗ trợ IComposerClient.getDisplayIdentificationData và cung cấp dữ liệu nhận dạng màn hình, SurfaceFlinger sẽ phân tích cấu trúc EDID và phân bổ các mã nhận dạng màn hình 64 bit ổn định cho màn hình thực và màn hình ảo 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ột 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ó sự hỗ trợ của 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ợ nhiều nguồn đầu vào nhắm đến từng màn hình riêng lẻ 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 tiêu điểm, tối đa một cửa sổ cho mỗi màn hình. Tính năng 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 một lúc và sử dụng các phương thức hoặc thiết bị đầu vào khác nhau, 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ị có nhiều màn hình hoặc thiết bị được dùng cho trải nghiệm tương tự như trên máy tính. Nguyên nhân chính là do lo ngại về vấn đề 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 bảo mật vào một trường nhập văn bản, có thể là đăng nhập vào một ứng dụng ngân hàng hoặc nhập văn bản có chứa thông tin nhạy cảm. Một ứng dụng độc hại có thể tạo ra một màn hình ảo bên ngoài để thực thi một hoạt động, cũng như một trường nhập văn bản. Các hoạt động hợp pháp và độc hại đều có tiêu điểm và hiển thị một 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 chạy gần đây nhất), nên bằng cách tạo một 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 cho mỗi màn hình.

Khả năng tương thích

Vấn đề: Trong Android 9 trở xuống, tại một thời điểm, hệ thống có tối đa một cửa sổ 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 sẽ chỉ cung cấp tiêu điểm cho cửa sổ có thứ tự Z cao hơn. 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 tiêu đ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 dùng thay vì tính năng theo dõi trên toàn cầu 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() có cách tiếp cận tương tự cho những trường hợp mà ngăn xếp được lấy tiêu điểm trên cùng trong hệ thống phải được xác định. Các ngăn xếp được duyệt qua 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ỗi cửa sổ cho một màn hình). Nếu một sự kiện đầu vào 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, sự kiện này 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::mFocusedWindowHandlesByDisplayInputDispatcher::setFocusedDisplay(). Các ứng dụng được lấy làm tiêu đ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. Hãy xem DisplayContent#mCurrentFocusDisplayContent#mFocusedApp cũng như các cách sử dụng tương ứng. Các phương thức theo dõi và cập nhật tiêu điểm có liên quan đã được di chuyển từ WindowManagerService sang DisplayContent.