Nhúng hoạt động

Tính năng nhúng hoạt động tối ưu hoá các ứng dụng trên thiết bị có màn hình lớn bằng cách chia đôi cửa sổ tác vụ của ứng dụng để hiển thị 2 hoạt động hoặc 2 thực thể của cùng một hoạt động.

Hình 1. Các hoạt động trong ứng dụng Cài đặt xuất hiện song song nhau.

Nếu ứng dụng bao gồm nhiều hoạt động, tính năng nhúng hoạt động sẽ cho phép bạn nâng cao trải nghiệm cho người dùng trên máy tính bảng, thiết bị có thể gập và ChromeOS.

Tính năng nhúng hoạt động không cần tái cấu trúc mã. Bạn sẽ xác định cách ứng dụng hiển thị các hoạt động (cạnh nhau hoặc xếp chồng lên nhau) bằng cách tạo tệp cấu hình XML, hoặc thực hiện lệnh gọi API Jetpack WindowManager.

Hoạt động hỗ trợ màn hình nhỏ được duy trì tự động. Khi ứng dụng đang chạy trên thiết bị có màn hình nhỏ, các hoạt động được xếp chồng lên nhau. Trên màn hình lớn, hoạt động được hiển thị cạnh nhau. Hệ thống sẽ xác định cách hiển thị dựa trên cấu hình bạn đã tạo mà không cần đến logic phân nhánh.

Tính năng nhúng hoạt động phù hợp với thay đổi về hướng của thiết bị và hoạt động trơn tru trên thiết bị có thể gập: xếp các hoạt động khi thiết bị gập lại và huỷ xếp các hoạt động khi thiết bị mở ra.

Tính năng nhúng hoạt động được hỗ trợ trên hầu hết các thiết bị có màn hình lớn chạy Android 12L (API cấp 32) trở lên.

Chia cửa sổ tác vụ

Tính năng nhúng hoạt động chia cửa sổ tác vụ của ứng dụng thành hai vùng chứa: chính và phụ. Các vùng chứa giữ hoạt động được khởi chạy từ hoạt động chính hoặc từ các hoạt động đã ở sẵn trong vùng chứa.

Với thiết bị có màn hình nhỏ, hoạt động được xếp chồng trong vùng chứa phụ khi khởi chạy, đồng thời vùng chứa phụ được xếp chồng trên vùng chứa chính, vì vậy việc xếp chồng hoạt động và điều hướng quay lại luôn nhất quán với thứ tự hoạt động được tích hợp sẵn trong ứng dụng.

Tính năng nhúng hoạt động giúp hiển thị hoạt động theo nhiều cách khác nhau. Ứng dụng có thể chia cửa sổ tác vụ bằng cách chạy đồng thời hai hoạt động cùng lúc:

Hình 2. Hai hoạt động hiển thị cạnh nhau.

Hoặc, một hoạt động chiếm toàn bộ cửa sổ tác vụ có thể tạo thao tác tách bằng cách khởi chạy một hoạt động mới song song:

Hình 3. Hoạt động A khởi chạy hoạt động B bên cạnh.

Các hoạt động đã được chia nhỏ và dùng chung cửa sổ tác vụ có thể chạy các hoạt động khác theo cách sau:

  • Ở vùng bên cạnh và chồng lên một hoạt động khác:

    Hình 4. Hoạt động A khởi chạy hoạt động C ở vùng bên cạnh và chồng lên hoạt động B.
  • Ở vùng bên cạnh và đưa hoạt động đã tách sang một bên, che đi hoạt động chính trước đó:

    Hình 5. Hoạt động B khởi chạy hoạt động C ở vùng bên cạnh và đưa hoạt động đã tách sang một bên.
  • Khởi chạy một hoạt động và xếp hoạt động đó lên trên cùng ngăn xếp hoạt động:

    Hình 6. Hoạt động B khởi chạy hoạt động C mà không thêm bất kỳ cờ ý định nào.
  • Khởi chạy hoạt động trong chế độ cửa sổ toàn màn hình trong cùng một tác vụ:

    Hình 7. Hoạt động A hoặc hoạt động B khởi chạy hoạt động C và lấp đầy cửa sổ tác vụ.

Tính năng điều hướng quay lại

Các ứng dụng khác nhau có thể có quy tắc điều hướng ngược khác nhau trong trạng thái phân tách cửa sổ tác vụ, tuỳ thuộc vào sự phụ thuộc giữa các hoạt động hoặc cách người dùng kích hoạt sự kiện quay lại, ví dụ:

  • Liên quan đến nhau: Nếu các hoạt động có liên quan với nhau và một hoạt động sẽ không xuất hiện nếu không có hoạt động kia, bạn có thể điều chỉnh để tính năng điều hướng quay lại kết thúc cả hai.
  • Riêng lẻ: Nếu các hoạt động hoàn toàn độc lập, việc điều hướng quay lại một hoạt động sẽ không ảnh hưởng đến trạng thái của hoạt động khác trong cửa sổ tác vụ.

Nếu sử dụng nút điều hướng, sự kiện quay lại sẽ được gửi tới hoạt động được làm tâm điểm gần đây nhất. Với thao tác bằng cử chỉ, sự kiện quay lại sẽ được gửi đến hoạt động xảy ra cử chỉ.

Bố cục nhiều ngăn

Jetpack WindowsManager cho phép bạn tạo bố cục nhiều ngăn để nhúng hoạt động trên các thiết bị màn hình lớn chạy Android 12L (API cấp 32) trở lên và trên một số thiết bị có phiên bản nền tảng cũ. Với những ứng dụng hiện tại đang dựa trên đa hoạt động thay vì các mảnh hoặc bố cục dựa trên khung hiển thị (ví dụ như SlidingPaneLayout), nhà phát triển có thể cải thiện trải nghiệm người dùng trên thiết bị có màn hình lớn mà không cần tái cấu trúc mã nguồn.

Một ví dụ thường gặp là phân chia danh sách-chi tiết. Để đảm bảo việc trình bày giao diện chất lượng cao, hệ thống sẽ khởi chạy hoạt động danh sách, sau đó ứng dụng ngay lập tức khởi chạy hoạt động chi tiết. Hệ thống chuyển đổi sẽ chờ đến khi cả hai hoạt động được vẽ xong rồi hiển thị các hoạt động đó cùng lúc. Về phía người dùng, hai hoạt động trông giống như một.

Hình 8. Hai hoạt động bắt đầu cùng lúc trong một bố cục nhiều ngăn.

Thuộc tính phần chia tách

Bạn có thể chỉ định tỷ lệ của cửa sổ tác vụ của các vùng chứa phần được chia tách và cách bố trí các vùng chứa cùng nhau.

Đối với quy tắc được xác định trong tệp cấu hình XML, hãy thiết lập các thuộc tính sau:

  • splitRatio: Thiết lập tỷ lệ vùng chứa. Giá trị này là một số thực dấu phẩy động trong khoảng mở (0,0, 1,0).
  • splitLayoutDirection: Chỉ định cách bố trí các vùng chứa phần phân tách với nhau. Sau đây là một số giá trị:
    • ltr: Trái sang phải
    • rtl: Phải sang trái
    • locale: ltr hoặc rtl, giá trị được xác định trong chế độ cài đặt nội bộ

Xem phần cấu hình XML bên dưới để biết thêm ví dụ.

Đối với các quy tắc được tạo bằng API WindowManager, hãy tạo đối tượng SplitAttributes bằng SplitAttributes.Builder và gọi các phương thức trình tạo sau:

Xem phần API WindowManager bên dưới để biết thêm ví dụ.

Hình 9. Các hoạt động phân tách được bố trí từ trái sang phải, nhưng với tỷ lệ phân tách khác nhau.

Phần giữ chỗ

Hoạt động giữ chỗ là các hoạt động phụ trống chiếm một phần phân tách hoạt động. Một lúc nào đó, chúng sẽ được thay thế bằng một hoạt động khác có chứa nội dung. Ví dụ: hoạt động giữ chỗ có thể được đặt ở phần phụ khi phân tách hoạt động theo bố cục danh sách-chi tiết và chờ đến khi người dùng chọn một mục bất kỳ bên phần "danh sách". Khi người dùng chọn một mục bên phần "danh sách", hoạt động chứa thông tin "chi tiết" của mục đó sẽ thay thế cho phần giữ chỗ.

Theo mặc định, hệ thống chỉ hiện phần giữ chỗ khi có đủ không gian để phân tách hoạt động. Phần giữ chỗ sẽ tự động kết thúc khi chiều rộng hoặc chiều cao của khu vực hiển thị trở nên quá nhỏ để hiển thị phần phân tách. Khi đủ chỗ, hệ thống sẽ khởi chạy lại phần giữ chỗ với trạng thái khởi tạo lại.

Hình 10. Thiết bị có thể gập, khi gập lại và khi mở ra. Hoạt động giữ chỗ đã kết thúc và được tạo lại khi kích thước hiển thị thay đổi.

Tuy nhiên, thuộc tính stickyPlaceholder của phương thức SplitPlaceholderRule hoặc setSticky() của SplitPlaceholder.Builder có thể ghi đè thuộc tính mặc định. Khi thuộc tính hoặc phương thức chỉ định giá trị của true, hệ thống sẽ hiển thị hoạt động giữ chỗ là hoạt động trên cùng trong cửa sổ tác vụ khi màn hình được đổi kích thước từ màn hình hai ngăn xuống màn hình một ngăn (xem phần Cấu hình phần phân chia để biết ví dụ).

Hình 11. Thiết bị có thể gập, khi gập lại và khi mở ra. Hoạt động của trình giữ chỗ là cố định.

Thay đổi kích thước cửa sổ

Khi thay đổi cấu hình thiết bị, người dùng sẽ giảm chiều rộng cửa sổ tác vụ để nó không đủ lớn cho bố cục nhiều ngăn (ví dụ: khi một thiết bị, có thể gập lại với màn hình lớn, gập từ kích thước máy tính bảng thành kích thước điện thoại hoặc cửa sổ ứng dụng được điều chỉnh kích thước ở chế độ nhiều cửa sổ), các hoạt động không nằm trong trình giữ chỗ tại ngăn phụ của cửa sổ tác vụ được xếp chồng lên trên các hoạt động trong ngăn chính.

Hoạt động giữ chỗ chỉ hiển thị khi có đủ chiều rộng cho một phần phân chia. Trên màn hình nhỏ hơn, trình giữ chỗ sẽ tự động bị loại bỏ. Khi diện tích hiển thị lại đủ lớn, trình giữ chỗ sẽ được tạo lại. (Xem Phần giữ chỗ ở trên.)

Bạn có thể xếp chồng hoạt động vì WindowManager sắp xếp theo thứ tự z đối với các hoạt động trong ngăn phụ ở phía trên các hoạt động trong ngăn chính.

Nhiều hoạt động trong ngăn phụ

Hoạt động B khởi động hoạt động C tại vị trí không có cờ hiệu ý định:

Hoạt động phân chia có chứa các hoạt động A, B và C, trong đó C nằm chồng lên B.

dẫn đến thứ tự z của các hoạt động sau đây ở trong cùng một nhiệm vụ:

Ngăn xếp hoạt động phụ chứa hoạt động C xếp chồng lên trên B.
          Ngăn xếp phụ được xếp chồng lên trên ngăn xếp hoạt động chính chứa hoạt động A.

Vì vậy, trong cửa sổ tác vụ nhỏ hơn, ứng dụng sẽ thu gọn thành một hoạt động bằng C ở đầu ngăn xếp:

Cửa sổ nhỏ chỉ hiển thị hoạt động C.

Thao tác quay lại trong cửa sổ nhỏ hơn sẽ di chuyển qua các hoạt động xếp chồng lên nhau.

Nếu cấu hình cửa sổ tác vụ được khôi phục về kích thước lớn hơn có thể chứa nhiều ngăn, các hoạt động sẽ hiển thị cạnh nhau.

Chia ngăn xếp

Hoạt động B khởi động hoạt động C bên cạnh và di chuyển phần phân chia sang bên:

Cửa sổ tác vụ cho thấy các hoạt động A và B, sau đó là các hoạt động B và C.

Kết quả là các hoạt động sẽ tuân theo thứ tự z trong cùng một tác vụ:

Các hoạt động A, B và C trong một ngăn xếp. Các hoạt động được xếp chồng theo thứ tự từ trên xuống dưới: C, B, A.

Trong cửa sổ tác vụ nhỏ hơn, ứng dụng sẽ thu gọn thành một hoạt động duy nhất có C nằm trên cùng:

Cửa sổ nhỏ chỉ hiển thị hoạt động C.

Hướng dọc cố định

Chế độ cài đặt tệp kê khai android:screenOrientation cho phép ứng dụng ràng buộc các hoạt động theo hướng dọc hoặc ngang. Để cải thiện trải nghiệm người dùng trên các thiết bị có màn hình lớn như máy tính bảng và thiết bị có thể gập lại, nhà sản xuất thiết bị (OEM) có thể bỏ qua các yêu cầu về hướng màn hình và tạo khung viền hòm thư cho ứng dụng theo hướng dọc trên màn hình ngang hoặc hướng ngang trên màn hình dọc.

Hình 12. Hoạt động có khung viền hòm thư: hướng dọc cố định trên thiết bị ngang (bên trái), hướng ngang cố định trên thiết bị dọc (bên phải).

Tương tự, khi tính năng nhúng hoạt động được bật, OEM có thể tuỳ chỉnh thiết bị để tạo khung viền hòm thư cho các hoạt động dọc cố định theo hướng ngang trên màn hình lớn (chiều rộng ≥ 600 dp). Khi một hoạt động dọc cố định khởi chạy hoạt động thứ hai, thiết bị có thể hiển thị 2 hoạt động cạnh nhau trên một màn hình 2 ngăn.

Hình 13. Hoạt động dọc cố định A khởi động hoạt động B bên cạnh.

Luôn thêm thuộc tính android.window.PROPERTY_ACTIVITY_EMBEDDING_SPLITS_ENABLED vào tệp kê khai ứng dụng để cho các thiết bị biết rằng ứng dụng của bạn hỗ trợ tính năng nhúng hoạt động (xem phần Cấu hình phân tách ở bên dưới). Sau đó, thiết bị được OEM tuỳ chỉnh có thể xác định xem có tạo khung viền hòm thư cho các hoạt động dọc cố định hay không.

Cấu hình phân tách

Quy tắc phân tách định cấu hình quá trình phân tách hoạt động. Bạn xác định các quy tắc phân tách trong tệp cấu hình XML hoặc bằng cách thực hiện lệnh gọi API WindowManager trong Jetpack.

Trong từng trường hợp, ứng dụng của bạn phải truy cập vào thư viện WindowManager và phải thông báo cho hệ thống biết rằng ứng dụng đã triển khai tính năng nhúng hoạt động.

Hãy thực hiện như sau:

  1. Thêm phần phụ thuộc thư viện WindowManager mới nhất vào tệp build.gradle cấp mô-đun của ứng dụng, ví dụ:

    implementation 'androidx.window:window:1.1.0-beta02'

    Thư viện WindowManager cung cấp tất cả thành phần cần thiết để nhúng hoạt động.

  2. Thông báo cho hệ thống rằng ứng dụng của bạn đã triển khai tính năng nhúng hoạt động.

    Thêm thuộc tính android.window.PROPERTY_ACTIVITY_EMBEDDING_SPLITS_ENABLED vào phần tử <application> của tệp kê khai ứng dụng và đặt giá trị thành đúng, ví dụ như sau:

    <manifest xmlns:android="http://schemas.android.com/apk/res/android">
        <application>
            <property
                android:name="android.window.PROPERTY_ACTIVITY_EMBEDDING_SPLITS_ENABLED"
                android:value="true" />
        </application>
    </manifest>
    

    Trên bản phát hành WindowManager 1.1.0-alpha06 trở lên, chế độ phân tách của tính năng nhúng hoạt động sẽ bị tắt trừ phi thuộc tính được thêm vào tệp kê khai và đặt thành đúng.

    Ngoài ra, nhà sản xuất thiết bị cũng dùng chế độ cài đặt này để bật chức năng tuỳ chỉnh cho các ứng dụng hỗ trợ tính năng nhúng hoạt động. Ví dụ: các thiết bị có thể tạo khung viền hòm thư cho một hoạt động chỉ ở chế độ dọc trên màn hình ngang để định hướng cho hoạt động nhằm chuyển đổi sang bố cục 2 ngăn khi hoạt động thứ hai bắt đầu (xem phần Hướng dọc cố định).

Cấu hình XML

Để tạo quy trình triển khai dựa trên XML cho tính năng nhúng hoạt động, hãy hoàn tất các bước sau:

  1. Tạo tệp tài nguyên XML thực hiện những việc sau:

    • Xác định các hoạt động có chung phần phân tách
    • Định cấu hình các tuỳ chọn phân tách
    • Tạo một phần giữ chỗ cho vùng chứa phụ của phần phân tách khi không có nội dung
    • Chỉ định các hoạt động tuyệt đối không thuộc phần phân tách

    Ví dụ:

    <!-- main_split_config.xml -->
    
    <resources
        xmlns:window="http://schemas.android.com/apk/res-auto">
    
        <!-- Define a split for the named activities. -->
        <SplitPairRule
            window:splitRatio="0.33"
            window:splitLayoutDirection="locale"
            window:splitMinWidthDp="840"
            window:splitMaxAspectRatioInPortrait="alwaysAllow"
            window:finishPrimaryWithSecondary="never"
            window:finishSecondaryWithPrimary="always"
            window:clearTop="false">
            <SplitPairFilter
                window:primaryActivityName=".ListActivity"
                window:secondaryActivityName=".DetailActivity"/>
        </SplitPairRule>
    
        <!-- Specify a placeholder for the secondary container when content is
             not available. -->
        <SplitPlaceholderRule
            window:placeholderActivityName=".PlaceholderActivity"
            window:splitRatio="0.33"
            window:splitLayoutDirection="locale"
            window:splitMinWidthDp="840"
            window:splitMaxAspectRatioInPortrait="alwaysAllow"
            window:stickyPlaceholder="false">
            <ActivityFilter
                window:activityName=".ListActivity"/>
        </SplitPlaceholderRule>
    
        <!-- Define activities that should never be part of a split. Note: Takes
             precedence over other split rules for the activity named in the
             rule. -->
        <ActivityRule
            window:alwaysExpand="true">
            <ActivityFilter
                window:activityName=".ExpandedActivity"/>
        </ActivityRule>
    
    </resources>
    
  2. Tạo trình khởi tạo.

    Thành phần WindowManager RuleController sẽ phân tích cú pháp tệp cấu hình XML và cung cấp các quy tắc cho hệ thống. Thư viện Startup Jetpack Initializer sẽ cung cấp tệp XML cho RuleController khi khởi động ứng dụng để có thể áp dụng các quy tắc này khi hoạt động bắt đầu.

    Để tạo trình khởi tạo, hãy làm như sau:

    1. Thêm phần phụ thuộc mới nhất của thư viện Startup Jetpack vào tệp build.gradle cấp mô-đun, ví dụ:

      implementation 'androidx.startup:startup-runtime:1.1.1'

    2. Tạo một lớp giúp triển khai giao diện Initializer.

      Trình khởi tạo sẽ cung cấp các quy tắc phân chia cho RuleController bằng cách truyền mã nhận dạng của tệp cấu hình XML (main_split_config.xml) vào phương thức RuleController.parseRules().

      Kotlin

      class SplitInitializer : Initializer<RuleController> {
      
       override fun create(context: Context): RuleController {
           return RuleController.getInstance(context).apply {
               setRules(RuleController.parseRules(context, R.xml.main_split_config))
           }
       }
      
       override fun dependencies(): List<Class<out Initializer<*>>> {
           return emptyList()
       }
      }
      

      Java

      public class SplitInitializer implements Initializer<RuleController> {
      
        @NonNull
        @Override
        public RuleController create(@NonNull Context context) {
            RuleController ruleController = RuleController.getInstance(context);
            ruleController.setRules(
                RuleController.parseRules(context, R.xml.main_split_config)
            );
            return ruleController;
        }
      
        @NonNull
        @Override
        public List<Class<? extends Initializer<?>>> dependencies() {
            return Collections.emptyList();
        }
      }
      
  3. Tạo trình cung cấp nội dung cho định nghĩa quy tắc.

    Thêm androidx.startup.InitializationProvider vào tệp kê khai ứng dụng dưới dạng <provider>. Thêm thông tin tham chiếu vào cách triển khai trình khởi tạo RuleController, SplitInitializer:

    <!-- AndroidManifest.xml -->
    
    <provider android:name="androidx.startup.InitializationProvider"
        android:authorities="${applicationId}.androidx-startup"
        android:exported="false"
        tools:node="merge">
        <!-- Make SplitInitializer discoverable by InitializationProvider. -->
        <meta-data android:name="${applicationId}.SplitInitializer"
            android:value="androidx.startup" />
    </provider>
    

    InitializationProvider phát hiện và khởi chạy SplitInitializer trước khi phương thức onCreate() của ứng dụng được gọi. Do đó, các quy tắc phân chia sẽ có hiệu lực khi hoạt động chính của ứng dụng bắt đầu.

API WindowManager

Bạn có thể triển khai tính năng nhúng hoạt động theo cách lập trình bằng một số lệnh gọi API. Hãy thực hiện lệnh gọi trong phương thức onCreate() của một lớp con của Application để đảm bảo các quy tắc này có hiệu lực trước khi chạy bất cứ hoạt động nào.

Để tạo hoạt động phân tách bằng cách lập trình, hãy làm như sau:

  1. Tạo một quy tắc phân tách:

    1. Tạo SplitPairFilter giúp xác định hoạt động có chung phần phân tách:

      Kotlin

      val splitPairFilter = SplitPairFilter(
         ComponentName(this, ListActivity::class.java),
         ComponentName(this, DetailActivity::class.java),
         null
      )
      

      Java

      SplitPairFilter splitPairFilter = new SplitPairFilter(
         new ComponentName(this, ListActivity.class),
         new ComponentName(this, DetailActivity.class),
         null
      );
      
    2. Thêm bộ lọc vào một nhóm bộ lọc:

      Kotlin

      val filterSet = setOf(splitPairFilter)
      

      Java

      Set<SplitPairFilter> filterSet = new HashSet<>();
      filterSet.add(splitPairFilter);
      
    3. Tạo thuộc tính bố cục cho phần phân chia:

      Kotlin

      val splitAttributes: SplitAttributes = SplitAttributes.Builder()
          .setSplitType(SplitAttributes.SplitType.ratio(0.33f))
          .setLayoutDirection(SplitAttributes.LayoutDirection.LEFT_TO_RIGHT)
          .build()
      

      Java

      final SplitAttributes splitAttributes = new SplitAttributes.Builder()
            .setSplitType(SplitAttributes.SplitType.ratio(0.33f))
            .setLayoutDirection(SplitAttributes.LayoutDirection.LEFT_TO_RIGHT)
            .build();
      

      SplitAttributes.Builder sẽ tạo một đối tượng chứa các thuộc tính bố cục:

      • setSplitType: Xác định cách phân bổ khu vực màn hình có sẵn cho từng vùng chứa hoạt động. Kiểu tỷ lệ phân chia chỉ định tỷ lệ của khu vực hiển thị có sẵn được phân bổ cho vùng chứa chính; vùng chứa phụ sẽ chiếm phần còn lại của khu vực hiển thị có sẵn.
      • setLayoutDirection: Chỉ định cách bố trí các vùng chứa hoạt động so với một vùng chứa khác, vùng chứa chính được ưu tiên.
    4. Tạo SplitPairRule:

      Kotlin

      val splitPairRule = SplitPairRule.Builder(filterSet)
          .setDefaultSplitAttributes(splitAttributes)
          .setMinWidthDp(840)
          .setMinSmallestWidthDp(600)
          .setMaxAspectRatioInPortrait(EmbeddingAspectRatio.ratio(1.5f))
          .setFinishPrimaryWithSecondary(SplitRule.FinishBehavior.NEVER)
          .setFinishSecondaryWithPrimary(SplitRule.FinishBehavior.ALWAYS)
          .setClearTop(false)
          .build()
      

      Java

      SplitPairRule splitPairRule = new SplitPairRule.Builder(filterSet)
          .setDefaultSplitAttributes(splitAttributes)
          .setMinWidthDp(840)
          .setMinSmallestWidthDp(600)
          .setMaxAspectRatioInPortrait(EmbeddingAspectRatio.ratio(1.5f))
          .setFinishPrimaryWithSecondary(SplitRule.FinishBehavior.NEVER)
          .setFinishSecondaryWithPrimary(SplitRule.FinishBehavior.ALWAYS)
          .setClearTop(false)
          .build();
      

      SplitPairRule.Builder tạo và định cấu hình cho quy tắc:

      • filterSet: Chứa các bộ lọc cặp phân chia xác định thời điểm áp dụng quy tắc bằng cách xác định các hoạt động có cùng phần phân chia.
      • setDefaultSplitAttributes: Áp dụng các thuộc tính bố cục cho quy tắc.
      • setMinWidthDp: Thiết lập chiều rộng màn hình tối thiểu (tính bằng pixel không phụ thuộc vào mật độ, dp) cho phép phân chia.
      • setMinSmallestWidthDp: Thiết lập giá trị tối thiểu (tính bằng dp) mà giá trị nhỏ hơn trong hai kích thước màn hình phải cho phép phân chia, bất kể hướng của thiết bị.
      • setMaxAspectRatioInPortrait: Thiết lập tỷ lệ khung hình hiển thị tối đa (height:width) theo hướng dọc cho các phần phân chia của hoạt động được hiển thị. Nếu tỷ lệ khung hình của màn hình dọc vượt quá giá trị tỷ lệ khung hình tối đa, thì các phần phân chia sẽ bị tắt, bất kể chiều rộng màn hình là bao nhiêu. Lưu ý: Giá trị mặc định là 1,4, vì vậy, các hoạt động sẽ chiếm toàn bộ cửa sổ tác vụ theo hướng dọc trên hầu hết máy tính bảng. Xem thêm SPLIT_MAX_ASPECT_RATIO_PORTRAIT_DEFAULTsetMaxAspectRatioInLandscape. Giá trị mặc định cho hướng ngang là ALWAYS_ALLOW.
      • setFinishPrimaryWithSecondary: Việc hoàn tất mọi hoạt động trong vùng chứa phụ ảnh hưởng đến các hoạt động trong vùng chứa chính NEVER cho biết hệ thống không nên kết thúc các hoạt động chính khi tất cả các hoạt động trong vùng chứa phụ kết thúc (xem phần Kết thúc hoạt động).
      • setFinishSecondaryWithPrimary: Việc hoàn tất mọi hoạt động trong vùng chứa chính ảnh hưởng đến các hoạt động trong vùng chứa phụ ALWAYS cho biết hệ thống phải luôn kết thúc các hoạt động trong vùng chứa phụ khi tất cả hoạt động trong vùng chứa chính kết thúc (xem phần Kết thúc hoạt động).
      • setClearTop: Chỉ định xem liệu tất cả các hoạt động trong vùng chứa phụ đã hoàn tất hay chưa khi một hoạt động mới được chạy trong vùng chứa đó Giá trị False chỉ định rằng các hoạt động mới được xếp chồng lên trên các hoạt động đã có trong vùng chứa phụ.
    5. Lấy thực thể singleton RuleController của WindowManager và thêm quy tắc:

      Kotlin

      val ruleController = RuleController.getInstance(this)
      ruleController.addRule(splitPairRule)
      

      Java

      RuleController ruleController = RuleController.getInstance(this);
      ruleController.addRule(splitPairRule);
      
  2. Tạo một phần giữ chỗ cho vùng chứa phụ khi không có nội dung:

    1. Tạo một ActivityFilter giúp xác định hoạt động mà phần giữ chỗ sẽ dùng chung phần phân tách cửa sổ tác vụ:

      Kotlin

      val placeholderActivityFilter = ActivityFilter(
          ComponentName(this, ListActivity::class.java),
          null
      )
      

      Java

      ActivityFilter placeholderActivityFilter = new ActivityFilter(
          new ComponentName(this, ListActivity.class),
          null
      );
      
    2. Thêm bộ lọc vào một nhóm bộ lọc:

      Kotlin

      val placeholderActivityFilterSet = setOf(placeholderActivityFilter)
      

      Java

      Set<ActivityFilter> placeholderActivityFilterSet = new HashSet<>();
      placeholderActivityFilterSet.add(placeholderActivityFilter);
      
    3. Tạo một SplitPlaceholderRule:

      Kotlin

      val splitPlaceholderRule = SplitPlaceholderRule.Builder(
            placeholderActivityFilterSet,
            Intent(context, PlaceholderActivity::class.java)
          ).setDefaultSplitAttributes(splitAttributes)
           .setMinWidthDp(840)
           .setMinSmallestWidthDp(600)
           .setMaxAspectRatioInPortrait(EmbeddingAspectRatio.ratio(1.5f))
           .setFinishPrimaryWithPlaceholder(SplitRule.FinishBehavior.ALWAYS)
           .setSticky(false)
           .build()
      

      Java

      SplitPlaceholderRule splitPlaceholderRule = new SplitPlaceholderRule.Builder(
            placeholderActivityFilterSet,
            new Intent(context, PlaceholderActivity.class)
          ).setDefaultSplitAttributes(splitAttributes)
           .setMinWidthDp(840)
           .setMinSmallestWidthDp(600)
           .setMaxAspectRatioInPortrait(EmbeddingAspectRatio.ratio(1.5f))
           .setFinishPrimaryWithPlaceholder(SplitRule.FinishBehavior.ALWAYS)
           .setSticky(false)
           .build();
      

      SplitPlaceholderRule.Builder tạo và định cấu hình cho quy tắc:

      • placeholderActivityFilterSet: Chứa các bộ lọc hoạt động xác định thời điểm áp dụng quy tắc bằng cách xác định các hoạt động liên kết với hoạt động giữ chỗ.
      • Intent: Chỉ định việc khởi chạy hoạt động giữ chỗ.
      • setDefaultSplitAttributes: Áp dụng các thuộc tính bố cục cho quy tắc.
      • setMinWidthDp: Thiết lập chiều rộng hiển thị tối thiểu (tính bằng pixel không phụ thuộc vào mật độ, dp) cho phép phân chia.
      • setMinSmallestWidthDp: Thiết lập giá trị tối thiểu (tính bằng dp) mà giá trị nhỏ hơn trong hai kích thước màn hình phải cho phép phân chia, bất kể hướng của thiết bị.
      • setMaxAspectRatioInPortrait: Thiết lập tỷ lệ khung hình hiển thị tối đa (height:width) theo hướng dọc cho các phần phân chia của hoạt động được hiển thị. Lưu ý: Giá trị mặc định là 1,4, vì vậy, các hoạt động sẽ lấp đầy cửa sổ tác vụ theo hướng dọc trên hầu hết máy tính bảng. Xem thêm SPLIT_MAX_ASPECT_RATIO_PORTRAIT_DEFAULTsetMaxAspectRatioInLandscape. Giá trị mặc định cho hướng ngang là ALWAYS_ALLOW.
      • setFinishPrimaryWithPlaceholder: Thiết lập mức độ ảnh hưởng của việc hoàn tất hoạt động giữ chỗ đến các hoạt động trong vùng chứa chính. LUÔN cho biết rằng hệ thống phải luôn kết thúc các hoạt động trong vùng chứa chính khi hoạt động giữ chỗ kết thúc (xem phần Kết thúc hoạt động).
      • setSticky: Xác định xem hoạt động giữ chỗ có xuất hiện ở đầu ngăn xếp hoạt động trên các màn hình nhỏ hay không sau khi hoạt động giữ chỗ xuất hiện lần đầu trong phần phân chia có đủ chiều rộng tối thiểu.
    4. Thêm quy tắc vào RuleController của WindowManager:

      Kotlin

      ruleController.addRule(splitPlaceholderRule)
      

      Java

      ruleController.addRule(splitPlaceholderRule);
      
  3. Chỉ định các hoạt động tuyệt đối không thuộc phần phân chia:

    1. Tạo một ActivityFilter giúp xác định một hoạt động phải luôn chiếm toàn bộ vùng hiển thị tác vụ:

      Kotlin

      val expandedActivityFilter = ActivityFilter(
        ComponentName(this, ExpandedActivity::class.java),
        null
      )
      

      Java

      ActivityFilter expandedActivityFilter = new ActivityFilter(
        new ComponentName(this, ExpandedActivity.class),
        null
      );
      
    2. Thêm bộ lọc vào một nhóm bộ lọc:

      Kotlin

      val expandedActivityFilterSet = setOf(expandedActivityFilter)
      

      Java

      Set<ActivityFilter> expandedActivityFilterSet = new HashSet<>();
      expandedActivityFilterSet.add(expandedActivityFilter);
      
    3. Tạo một ActivityRule:

      Kotlin

      val activityRule = ActivityRule.Builder(expandedActivityFilterSet)
          .setAlwaysExpand(true)
          .build()
      

      Java

      ActivityRule activityRule = new ActivityRule.Builder(
          expandedActivityFilterSet
      ).setAlwaysExpand(true)
       .build();
      

      ActivityRule.Builder tạo và định cấu hình quy tắc:

      • expandedActivityFilterSet: Chứa các bộ lọc hoạt động xác định thời điểm áp dụng quy tắc bằng cách xác định các hoạt động mà bạn muốn loại trừ khỏi phần phân chia.
      • setAlwaysExpand: Chỉ định xem liệu hoạt động có lấp đầy toàn bộ cửa sổ tác vụ hay không
    4. Thêm quy tắc vào RuleController của WindowManager:

      Kotlin

      ruleController.addRule(activityRule)
      

      Java

      ruleController.addRule(activityRule);
      

Nhúng trên nhiều ứng dụng

Trên Android 13 (API cấp 33) trở lên, ứng dụng có thể nhúng hoạt động từ các ứng dụng khác. Chế độ nhúng hoạt động trên nhiều ứng dụng hoặc trên nhiều UID cho phép tích hợp trực quan các hoạt động từ nhiều ứng dụng Android. Hệ thống hiển thị một hoạt động của ứng dụng lưu trữ và một hoạt động được nhúng từ một ứng dụng khác trên các màn hình cạnh nhau hoặc trên cùng và dưới cùng giống như khi nhúng một ứng dụng.

Ví dụ: ứng dụng Cài đặt có thể nhúng hoạt động của bộ chọn hình nền từ ứng dụng WallpaperPicker:

Hình 14. Ứng dụng Cài đặt (trình đơn ở bên trái) với bộ chọn hình nền là hoạt động được nhúng (bên phải).

Mô hình tin cậy

Những quy trình lưu trữ nhúng hoạt động từ các ứng dụng khác có thể xác định lại cách trình bày các hoạt động được nhúng, bao gồm kích thước, vị trí, thao tác cắt và độ trong suốt. Các máy chủ lưu trữ độc hại có thể tận dụng khả năng này để gây hiểu lầm cho người dùng và tạo lỗ hổng clickjacking hoặc các cuộc tấn công khác liên quan đến việc sửa đổi giao diện người dùng.

Để ngăn chặn việc sử dụng sai tính năng nhúng hoạt động trên nhiều ứng dụng, Android yêu cầu các ứng dụng chọn cho phép nhúng hoạt động. Các ứng dụng có thể chỉ định máy chủ là đáng tin cậy hoặc không đáng tin cậy.

Máy chủ đáng tin cậy

Để cho phép các ứng dụng khác nhúng và kiểm soát hoàn toàn cách trình bày các hoạt động từ ứng dụng của bạn, hãy chỉ định chứng chỉ SHA-256 của ứng dụng lưu trữ trong thuộc tính android:knownActivityEmbeddingCerts của phần tử <activity> hoặc <application> trong tệp kê khai của ứng dụng.

Đặt giá trị của android:knownActivityEmbeddingCerts ở dạng chuỗi:

<activity
    android:name=".MyEmbeddableActivity"
    android:knownActivityEmbeddingCerts="@string/known_host_certificate_digest"
    ... />

hoặc để chỉ định nhiều chứng chỉ, một mảng chuỗi:

<activity
    android:name=".MyEmbeddableActivity"
    android:knownActivityEmbeddingCerts="@array/known_host_certificate_digests"
    ... />

tham chiếu đến một tài nguyên như sau:

<resources>
    <string-array name="known_host_certificate_digests">
      <item>cert1</item>
      <item>cert2</item>
      ...
    </string-array>
</resources>

Chủ sở hữu ứng dụng có thể chạy tác vụ signingReport của Gradle để nhận thông báo chứng chỉ SHA. Thông báo chứng chỉ là dấu vân tay SHA-256 không có dấu hai chấm phân tách. Để biết thêm thông tin, hãy xem bài viết Chạy báo cáo kýXác thực ứng dụng của bạn.

Máy chủ không đáng tin cậy

Để cho phép ứng dụng nhúng hoạt động của ứng dụng và kiểm soát cách trình bày trong ứng dụng đó, hãy chỉ định thuộc tính android:allowUntrustedActivityEmbedding trong các phần tử <activity> hoặc <application> trong tệp kê khai ứng dụng, ví dụ:

<activity
    android:name=".MyEmbeddableActivity"
    android:allowUntrustedActivityEmbedding="true"
    ... />

Giá trị mặc định của thuộc tính là false, để ngăn chặn việc nhúng hoạt động trên nhiều ứng dụng.

Xác thực tuỳ chỉnh

Để giảm thiểu rủi ro khi nhúng hoạt động không đáng tin cậy, hãy tạo một cơ chế xác thực tuỳ chỉnh nhằm xác minh danh tính của máy chủ. Nếu bạn biết các chứng chỉ máy chủ, hãy sử dụng thư viện androidx.security.app.authenticator để xác thực. Nếu máy chủ xác thực sau khi nhúng hoạt động, thì bạn có thể hiển thị nội dung thực tế. Nếu không, bạn có thể thông báo cho người dùng rằng hành động này không được cho phép và chặn nội dung.

Sử dụng phương thức ActivityEmbeddingController#isActivityEmbedded() từ thư viện Jetpack WindowManager để kiểm tra xem một máy chủ có đang nhúng hoạt động của bạn hay không, ví dụ:

Kotlin

fun isActivityEmbedded(activity: Activity): Boolean {
    return ActivityEmbeddingController.getInstance(this).isActivityEmbedded(activity)
}

Java

boolean isActivityEmbedded(Activity activity) {
    return ActivityEmbeddingController.getInstance(this).isActivityEmbedded(activity);
}

Giới hạn kích thước tối thiểu

Hệ thống Android áp dụng chiều cao và chiều rộng tối thiểu được chỉ định trong phần tử tệp kê khai ứng dụng <layout> cho các hoạt động được nhúng. Nếu ứng dụng không chỉ định chiều cao và chiều rộng tối thiểu, thì giá trị mặc định của hệ thống sẽ được áp dụng (sw220dp).

Nếu máy chủ lưu trữ cố gắng đổi kích thước vùng chứa đã nhúng thành kích thước nhỏ hơn kích thước tối thiểu, thì vùng chứa đã nhúng sẽ mở rộng để chiếm toàn bộ giới hạn của tác vụ.

<activity-alias>

Để tính năng nhúng hoạt động đáng tin cậy hoặc không đáng tin cậy hoạt động với phần tử <activity-alias>, bạn phải áp dụng android:knownActivityEmbeddingCerts hoặc android:allowUntrustedActivityEmbedding cho hoạt động mục tiêu thay vì bí danh. Chính sách xác minh tính bảo mật trên máy chủ hệ thống dựa trên các cờ được đặt trên mục tiêu chứ không phải bí danh.

Ứng dụng lưu trữ

Các ứng dụng lưu trữ triển khai tính năng nhúng hoạt động trên nhiều ứng dụng theo cách tương tự như cách triển khai tính năng nhúng hoạt động trong một ứng dụng. Các đối tượng SplitPairRuleSplitPairFilter hoặc ActivityRuleActivityFilter chỉ định các hoạt động được nhúng và phần phân tách cửa sổ tác vụ. Các quy tắc phân tách được xác định tĩnh trong XML hoặc trong thời gian chạy bằng cách sử dụng lệnh gọi API Jetpack WindowManager.

Nếu một ứng dụng lưu trữ cố gắng nhúng hoạt động mà không chọn nhúng trên nhiều ứng dụng, thì hoạt động đó sẽ chiếm toàn bộ giới hạn của tác vụ. Do đó, ứng dụng lưu trữ cần biết liệu hoạt động mục tiêu có cho phép nhúng trên nhiều ứng dụng hay không.

Nếu một hoạt động được nhúng bắt đầu một hoạt động mới trong cùng một tác vụ và hoạt động mới đó không chọn nhúng trên nhiều ứng dụng, thì hoạt động đó sẽ chiếm toàn bộ giới hạn tác vụ thay vì che phủ hoạt động trong vùng chứa được nhúng.

Ứng dụng lưu trữ có thể nhúng các hoạt động của chính mình mà không bị hạn chế, miễn là các hoạt động khởi chạy trong cùng một tác vụ.

Ví dụ về cách phân tách

Tách khỏi toàn bộ cửa sổ

Hình 15. Hoạt động A khởi chạy hoạt động B bên cạnh.

Không cần tái cấu trúc. Có thể xác định cấu hình cho chế độ phân tách tĩnh hoặc trong thời gian chạy, sau đó gọi Context#startActivity() mà không cần thêm tham số nào.

<SplitPairRule>
    <SplitPairFilter
        window:primaryActivityName=".A"
        window:secondaryActivityName=".B"/>
</SplitPairRule>

Tách theo mặc định

Khi trang đích của ứng dụng được thiết kế để chia thành hai vùng chứa trên màn hình lớn, trải nghiệm người dùng được cho là tối ưu nhất khi cả hai hoạt động được tạo và trình bày đồng thời. Tuy nhiên, nội dung có thể không có sẵn cho vùng chứa phụ của phần đã tách ra cho đến khi người dùng tương tác với hoạt động trong vùng chứa chính (ví dụ: người dùng chọn một mục từ menu điều hướng). Hoạt động của phần giữ chỗ có thể lấp đầy khoảng trống cho đến khi nội dung có thể hiển thị trong vùng chứa phụ của phần phân tách (xem Phần giữ chỗ ở trên).

Hình 16. Bạn có thể tạo phần phân tách bằng cách mở đồng thời 2 hoạt động. Mỗi hoạt động là một trình giữ chỗ.

Để dùng hoạt động giữ chỗ làm hoạt động phân chia, hãy tạo hoạt động giữ chỗ rồi liên kết hoạt động đó với hoạt động chính:

<SplitPlaceholderRule
    window:placeholderActivityName=".PlaceholderActivity">
    <ActivityFilter
        window:activityName=".MainActivity"/>
</SplitPlaceholderRule>

Khi một ứng dụng nhận được ý định, hoạt động mục tiêu có thể được hiển thị dưới dạng phần phụ của hoạt động phân chia. Ví dụ: yêu cầu hiển thị một màn hình chi tiết có thông tin về một mục trong danh sách. Trên màn hình nhỏ, thông tin chi tiết sẽ hiển thị trong cửa sổ tác vụ tràn màn hình; trên các thiết bị lớn hơn và bên cạnh danh sách.

Hình 17. Hoạt động Chi tiết của đường liên kết sâu chỉ hiển thị trên một màn hình nhỏ, nhưng cùng với hoạt động Danh sách ở một màn hình lớn.

Bạn phải chuyển yêu cầu chạy đến hoạt động chính và đưa hoạt động Chi tiết mục tiêu vào hoạt động phân chia. Hệ thống sẽ tự động chọn cách trình bày đúng (xếp chồng hoặc xếp cạnh nhau) dựa trên chiều rộng màn hình có sẵn.

Kotlin

override fun onCreate(savedInstanceState Bundle?) {
    . . .
    RuleController.getInstance(this)
        .addRule(SplitPairRule.Builder(filterSet).build())
    startActivity(Intent(this, DetailActivity::class.java))
}

Java

@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
    . . .
    RuleController.getInstance(this)
        .addRule(new SplitPairRule.Builder(filterSet).build());
    startActivity(new Intent(this, DetailActivity.class));
}

Đích liên kết sâu có thể là hoạt động duy nhất hiển thị cho người dùng trong ngăn điều hướng phía sau, bạn nên tránh loại bỏ hoạt động chi tiết và chỉ để lại hoạt động chính:

Màn hình lớn hiển thị cùng với hoạt động liên quan đến danh sách và hoạt động chi tiết.
          Thao tác quay lại không thể đóng hoạt động chi tiết và rời khỏi hoạt động danh sách trên màn hình.

Màn hình nhỏ chỉ hoạt động chi tiết. Thao tác quay lại không thể bỏ qua hoạt động chi tiết và hiển thị hoạt động của danh sách.

Thay vào đó, bạn có thể hoàn tất cả hai hoạt động cùng một lúc bằng thuộc tính finishPrimaryWithSecondary:

<SplitPairRule
    window:finishPrimaryWithSecondary="always">
    <SplitPairFilter
        window:primaryActivityName=".ListActivity"
        window:secondaryActivityName=".DetailActivity"/>
</SplitPairRule>

Xem phần Thuộc tính cấu hình ở bên dưới.

Nhiều hoạt động trong vùng chứa phân tách

Việc xếp chồng nhiều hoạt động trong một vùng chứa phân tách cho phép người dùng truy cập vào nội dung chuyên sâu. Ví dụ: với cách phân tách chi tiết danh sách, người dùng có thể chuyển đến mục chi tiết phụ nhưng vẫn giữ nguyên hoạt động chính:

Hình 18. Hoạt động đã mở tại ngăn phụ của cửa sổ tác vụ.

Kotlin

class DetailActivity {
    . . .
    fun onOpenSubDetail() {
      startActivity(Intent(this, SubDetailActivity::class.java))
    }
}

Java

public class DetailActivity {
    . . .
    void onOpenSubDetail() {
        startActivity(new Intent(this, SubDetailActivity.class));
    }
}

Hoạt động chi tiết phụ được đặt ở đầu hoạt động chi tiết, che đi hoạt động đó:

Sau đó, người dùng có thể quay lại cấp chi tiết trước đó thông qua ngăn xếp:

Hình 19. Hoạt động bị xoá khỏi đầu ngăn xếp.

Hoạt động xếp chồng lên nhau là hành vi mặc định khi các hoạt động bắt đầu từ một hoạt động trong cùng vùng chứa phụ. Các hoạt động khởi chạy từ vùng chứa chính trong hoạt động phân chia cũng sẽ kết thúc trong vùng chứa phụ ở đầu ngăn xếp hoạt động.

Hoạt động trong một nhiệm vụ mới

Khi các hoạt động trong cửa sổ tác vụ chia tách khởi động các hoạt động trong một tác vụ mới, tác vụ mới đó sẽ tách biệt khỏi tác vụ chứa phần chia tách và được hiển thị trong cửa sổ tràn màn hình. Màn hình Gần đây cho thấy 2 nhiệm vụ: nhiệm vụ dạng phân tách và nhiệm vụ mới.

Hình 20. Khởi động hoạt động C trong một nhiệm vụ mới từ hoạt động B.

Thay thế hoạt động

Bạn có thể thay thế hoạt động trong ngăn xếp vùng chứa phụ; ví dụ: khi hoạt động chính được dùng cho thanh điều hướng cấp cao nhất và hoạt động phụ là điểm đến được chọn. Mỗi lựa chọn từ thanh điều hướng cấp cao nhất sẽ bắt đầu một hoạt động mới trong vùng chứa phụ và xoá hoạt động hoặc hoạt động đã có trước đó.

Hình 21. Hoạt động điều hướng cấp cao nhất trong ngăn chính sẽ thay thế hoạt động đích trong ngăn phụ.

Nếu ứng dụng không hoàn tất hoạt động trong vùng chứa phụ khi lựa chọn điều hướng thay đổi, thao tác điều hướng quay lại có thể nhầm lẫn khi thu gọn phần phân chia (khi thiết bị được gập). Ví dụ: nếu bạn có một trình đơn trong ngăn chính, các màn hình A và B được xếp chồng trong ngăn phụ, khi người dùng gấp điện thoại, B nằm phía trên A và A ở phía trên trình đơn. Khi người dùng quay lại từ B, A sẽ xuất hiện thay vì trình đơn.

Màn hình A cần được xoá khỏi ngăn xếp lui trong những trường hợp như vậy.

Hành vi mặc định khi khởi chạy sang một bên trong vùng chứa mới trên phần phân chia hiện có là đặt các vùng chứa phụ mới lên trên và giữ lại các vùng chứa cũ trong ngăn xếp phía sau. Bạn có thể định cấu hình các phần phân tách để xoá vùng chứa phụ trước đó bằng clearTop và khởi chạy các hoạt động mới như bình thường.

<SplitPairRule
    window:clearTop="true">
    <SplitPairFilter
        window:primaryActivityName=".Menu"
        window:secondaryActivityName=".ScreenA"/>
    <SplitPairFilter
        window:primaryActivityName=".Menu"
        window:secondaryActivityName=".ScreenB"/>
</SplitPairRule>

Kotlin

class MenuActivity {
    . . .
    fun onMenuItemSelected(selectedMenuItem: Int) {
        startActivity(Intent(this, classForItem(selectedMenuItem)))
    }
}

Java

public class MenuActivity {
    . . .
    void onMenuItemSelected(int selectedMenuItem) {
        startActivity(new Intent(this, classForItem(selectedMenuItem)));
    }
}

Ngoài ra, hãy sử dụng cùng một hoạt động phụ gửi các ý định mới giải quyết cho cùng một trường hợp từ hoạt động chính (menu) nhưng kích hoạt cập nhật trạng thái hoặc giao diện người dùng trong vùng chứa phụ.

Nhiều phần phân chia

Các ứng dụng có thể cung cấp điều hướng sâu nhiều cấp độ bằng cách đưa ra các hoạt động bổ sung bên cạnh.

Khi một hoạt động trong vùng chứa phụ kích hoạt một hoạt động mới bên cạnh, phần phân tách mới sẽ được tạo ra phía trên phần phân tách hiện có.

Hình 22. Hoạt động B khởi động hoạt động C sang một bên.

Ngăn xếp lui chứa tất cả các hoạt động đã mở trước đó, vì vậy người dùng có thể chuyển đến phần phân chia A/B sau khi hoàn tất C.

Các hoạt động A, B và C trong một ngăn xếp. Các hoạt động được sắp xếp theo thứ tự từ trên xuống dưới: C, B, A.

Để tạo phần phân chia mới, hãy chạy hoạt động mới bên cạnh vùng chứa phụ hiện có. Khai báo cấu hình cho phần phân chia A/B và B/C đồng thời khởi chạy hoạt động C từ B:

<SplitPairRule>
    <SplitPairFilter
        window:primaryActivityName=".A"
        window:secondaryActivityName=".B"/>
    <SplitPairFilter
        window:primaryActivityName=".B"
        window:secondaryActivityName=".C"/>
</SplitPairRule>

Kotlin

class B {
    . . .
    fun onOpenC() {
        startActivity(Intent(this, C::class.java))
    }
}

Java

public class B {
    . . .
    void onOpenC() {
        startActivity(new Intent(this, C.class));
    }
}

Phản hồi để phân tách các thay đổi ở trạng thái

Nhiều hoạt động trong một ứng dụng có thể có thành phần trên giao diện người dùng thực hiện cùng một chức năng; ví dụ: một chế độ kiểm soát mở ra một cửa sổ chứa chế độ cài đặt tài khoản.

Hình 23. Hoạt động khác nhau với các thành phần trên giao diện người dùng có chức năng giống hệt nhau.

Nếu 2 hoạt động bị chia tách và có chung một thành phần trên giao diện người dùng, thì việc hiển thị thành phần này trong cả 2 hoạt động sẽ không cần thiết và có thể gây nhầm lẫn.

Hình 24. Các thành phần trên giao diện người dùng trùng lặp trong hoạt động phân chia.

Để biết khi nào các hoạt động diễn ra trong phần phân chia, hãy kiểm tra luồng SplitController.splitInfoList hoặc đăng ký một trình nghe với SplitControllerCallbackAdapter để biết các thay đổi về trạng thái phân chia. Sau đó, điều chỉnh giao diện người dùng như sau:

Kotlin

val layout = layoutInflater.inflate(R.layout.activity_main, null)
val view = layout.findViewById<View>(R.id.infoButton)
lifecycleScope.launch {
    lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED) {
        splitController.splitInfoList(this@SplitDeviceActivity) // The activity instance.
            .collect { list ->
                view.visibility = if (list.isEmpty()) View.VISIBLE else View.GONE
            }
    }
}

Java

@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
    . . .
    new SplitControllerCallbackAdapter(SplitController.getInstance(this))
        .addSplitListener(
            this,
            Runnable::run,
            splitInfoList -> {
                View layout = getLayoutInflater().inflate(R.layout.activity_main, null);
                layout.findViewById(R.id.infoButton).setVisibility(
                    splitInfoList.isEmpty() ? View.VISIBLE : View.GONE);
            });
}

Coroutine có thể được khởi chạy ở mọi trạng thái vòng đời, nhưng thường được khởi chạy ở trạng thái STARTED để bảo toàn tài nguyên (xem Sử dụng coroutine Kotlin với các thành phần nhận biết vòng đời để biết thêm thông tin).

Bạn có thể thực hiện các lệnh gọi lại ở bất kỳ trạng thái nào trong vòng đời hoạt động, kể cả khi một hoạt động ngừng diễn ra. Bạn thường phải đăng ký trình nghe này trong onStart() và huỷ đăng ký tại onStop().

Chế độ cửa sổ toàn màn hình

Một số hoạt động chặn người dùng tương tác với ứng dụng cho đến khi thực hiện hành động được chỉ định; ví dụ: hoạt động trên màn hình đăng nhập, màn hình xác nhận chính sách hoặc thông báo lỗi. Cần chặn các hoạt động mô-đun xuất hiện trong phần phân tách.

Một hoạt động có thể buộc phải luôn lấp đầy cửa sổ tác vụ bằng cách sử dụng cấu hình mở rộng:

<ActivityRule
    window:alwaysExpand="true">
    <ActivityFilter
        window:activityName=".FullWidthActivity"/>
</ActivityRule>

Hoàn tất hoạt động

Người dùng có thể hoàn tất các hoạt động ở một trong hai cạnh của phần phân tách bằng cách vuốt từ cạnh của màn hình:

Hình 25. Cử chỉ vuốt kết thúc hoạt động B.
Hình 26. Cử chỉ vuốt kết thúc hoạt động A.

Nếu thiết bị được thiết lập để sử dụng nút quay lại thay vì thao tác bằng cử chỉ, phương thức nhập sẽ được gửi đến hoạt động tập trung (hoạt động được nhấn hoặc chạy lần cuối).

Việc kết thúc tất cả hoạt động trong vùng chứa sẽ ảnh hưởng đến vùng chứa đối diện phụ thuộc vào cấu hình phần phân chia.

Thuộc tính cấu hình

Bạn có thể chỉ định các thuộc tính quy tắc ghép nối để định cấu hình cách ảnh hưởng của việc hoàn tất mọi hoạt động ở một bên của phần phân tách đối với các hoạt động ở bên kia của phần phân tách. Các thuộc tính đó bao gồm:

  • window:finishPrimaryWithSecondary — Việc hoàn tất mọi hoạt động trong vùng chứa phụ ảnh hưởng đến các hoạt động trong vùng chứa chính
  • window:finishSecondaryWithPrimary — Việc hoàn tất mọi hoạt động trong vùng chứa chính ảnh hưởng đến các hoạt động trong vùng chứa phụ

Các thuộc tính có thể có các giá trị sau đây:

  • always — Luôn hoàn tất các hoạt động trong vùng chứa liên kết
  • never — Không bao giờ hoàn tất các hoạt động trong vùng chứa liên kết
  • adjacent — Hoàn thành các hoạt động trong vùng chứa liên kết khi 2 vùng chứa hiển thị cạnh nhau, nhưng không hoàn tất khi 2 vùng chứa này được xếp chồng

Ví dụ:

<SplitPairRule
    <!-- Do not finish primary container activities when all secondary container activities finish. -->
    window:finishPrimaryWithSecondary="never"
    <!-- Finish secondary container activities when all primary container activities finish. -->
    window:finishSecondaryWithPrimary="always">
    <SplitPairFilter
        window:primaryActivityName=".A"
        window:secondaryActivityName=".B"/>
</SplitPairRule>

Cấu hình mặc định

Khi tất cả hoạt động trong một vùng chứa của phần phân chia đã kết thúc, vùng chứa còn lại sẽ chiếm toàn bộ cửa sổ:

<SplitPairRule>
    <SplitPairFilter
        window:primaryActivityName=".A"
        window:secondaryActivityName=".B"/>
</SplitPairRule>

Phần phân chia chứa các hoạt động A và B. A kết thúc để lại B lấp đầy toàn bộ cửa sổ.

Phần phân chia chứa các hoạt động A và B. B kết thúc để lại A lấp đầy toàn bộ cửa sổ.

Cùng hoàn tất hoạt động

Tự động kết thúc các hoạt động trong vùng chứa chính khi tất cả các hoạt động trong vùng chứa phụ kết thúc:

<SplitPairRule
    window:finishPrimaryWithSecondary="always">
    <SplitPairFilter
        window:primaryActivityName=".A"
        window:secondaryActivityName=".B"/>
</SplitPairRule>

Phần phân chia chứa các hoạt động A và B. B đã kết thúc và A cũng hoàn tất, để trống cửa sổ tác vụ.

Phần phân chia chứa các hoạt động A và B. A đã kết thúc để lại B trong cửa sổ tác vụ.

Tự động kết thúc các hoạt động trong vùng chứa phụ khi tất cả hoạt động trong vùng chứa chính kết thúc:

<SplitPairRule
    window:finishSecondaryWithPrimary="always">
    <SplitPairFilter
        window:primaryActivityName=".A"
        window:secondaryActivityName=".B"/>
</SplitPairRule>

Phần phân chia chứa các hoạt động A và B. A đã kết thúc và B cũng hoàn tất, để trống cửa sổ tác vụ.

Phần phân chia chứa các hoạt động A và B. B đã kết thúc để lại A trong cửa sổ tác vụ.

Kết thúc các hoạt động cùng nhau khi tất cả hoạt động trong vùng chứa chính hoặc phụ kết thúc:

<SplitPairRule
    window:finishPrimaryWithSecondary="always"
    window:finishSecondaryWithPrimary="always">
    <SplitPairFilter
        window:primaryActivityName=".A"
        window:secondaryActivityName=".B"/>
</SplitPairRule>

Phần phân chia chứa các hoạt động A và B. A đã kết thúc và B cũng hoàn tất, để trống cửa sổ tác vụ.

Phần phân chia chứa các hoạt động A và B. B đã kết thúc và A cũng hoàn tất, để trống cửa sổ tác vụ.

Hoàn tất nhiều hoạt động trong vùng chứa

Nếu nhiều hoạt động được xếp chồng trong một vùng chứa phân tách, việc hoàn tất một hoạt động ở cuối ngăn xếp không tự động hoàn thành các hoạt động ở trên cùng.

Ví dụ: nếu hai hoạt động nằm trong vùng chứa phụ, C nằm phía trên B:

Ngăn xếp hoạt động phụ chứa hoạt động C xếp chồng lên trên B được xếp chồng ở đầu ngăn xếp hoạt động chính có chứa hoạt động A.

và cấu hình phần phân chia được xác định bởi cấu hình của hoạt động A và B:

<SplitPairRule>
    <SplitPairFilter
        window:primaryActivityName=".A"
        window:secondaryActivityName=".B"/>
</SplitPairRule>

khi kết thúc hoạt động trên cùng, ứng dụng sẽ tách ra.

Phần tách hoạt động A trong vùng chứa chính với các hoạt động B và C ở vùng phụ, C được xếp chồng lên trên B. C kết thúc để lại A và B trong hoạt động phân chia.

Việc hoàn tất hoạt động ở dưới cùng (gốc) của vùng chứa phụ sẽ không xoá các hoạt động phía trên vùng chứa đó; đồng thời giữ nguyên cách phân chia.

Phần tách hoạt động A trong vùng chứa chính với các hoạt động B và C ở vùng phụ, C được xếp chồng lên trên B. B kết thúc, để lại A và C trong hoạt động phân chia.

Mọi quy tắc bổ sung để cùng hoàn tất các hoạt động, chẳng hạn như hoàn tất hoạt động phụ với hoạt động chính, cũng được thực thi:

<SplitPairRule
    window:finishSecondaryWithPrimary="always">
    <SplitPairFilter
        window:primaryActivityName=".A"
        window:secondaryActivityName=".B"/>
</SplitPairRule>

Tách hoạt động A trong vùng chứa chính với các hoạt động B và C ở vùng phụ, C được xếp chồng lên trên B. A kết thúc cũng hoàn tất luôn B và C.

Và khi cách phân chia này được định cấu hình để cùng hoàn tất mục chính và phụ:

<SplitPairRule
    window:finishPrimaryWithSecondary="always"
    window:finishSecondaryWithPrimary="always">
    <SplitPairFilter
        window:primaryActivityName=".A"
        window:secondaryActivityName=".B"/>
</SplitPairRule>

Phần tách hoạt động A trong vùng chứa chính với các hoạt động B và C ở vùng phụ, C được xếp chồng lên trên B. C kết thúc để lại A và B trong hoạt động phân chia.

Phần tách hoạt động A trong vùng chứa chính với các hoạt động B và C ở vùng phụ, C được xếp chồng lên trên B. B kết thúc, để lại A và C trong hoạt động phân chia.

Phần tách hoạt động A trong vùng chứa chính với các hoạt động B và C ở vùng phụ, C được xếp chồng lên trên B. A kết thúc cũng hoàn tất luôn B và C.

Thay đổi thuộc tính chia tách trong thời gian chạy

Bạn không thể thay đổi thuộc tính của phần phân tách hiện đang hoạt động và hiển thị. Việc thay đổi quy tắc phân tách sẽ ảnh hưởng đến các hoạt động khởi chạy khác và vùng chứa mới, tuy nhiên nó không ảnh hưởng đến phần phân tách hiện có và đang hoạt động.

Để thay đổi thuộc tính của phần phân tách đang hoạt động, hãy hoàn tất hoạt động hoặc các hoạt động bên trong phần phân tách đồng thời khởi chạy lại sang bên cạnh bằng cấu hình mới.

Trích xuất hoạt động từ chế độ phân tách thành toàn bộ cửa sổ

Tạo một cấu hình mới sẽ hiển thị hoạt động bên cạnh trên cửa sổ toàn màn hình, sau đó khởi chạy lại hoạt động với ý định giải quyết cùng một đối tượng.

Kiểm tra chế độ hỗ trợ phân tách trong thời gian chạy

Tính năng nhúng hoạt động được hỗ trợ trên Android 12L (API cấp 32) trở lên, nhưng cũng có trên một số thiết bị sử dụng các phiên bản nền tảng cũ. Để kiểm tra phạm vi cung cấp tính năng trong thời gian chạy, hãy sử dụng thuộc tính SplitController.splitSupportStatus hoặc phương thức SplitController.getSplitSupportStatus():

Kotlin

if (SplitController.getInstance(this).splitSupportStatus ==
     SplitController.SplitSupportStatus.SPLIT_AVAILABLE) {
     // Device supports split activity features.
}

Java

if (SplitController.getInstance(this).getSplitSupportStatus() ==
     SplitController.SplitSupportStatus.SPLIT_AVAILABLE) {
     // Device supports split activity features.
}

Nếu phần phân chia không được hỗ trợ, các hoạt động sẽ được khởi chạy trên ngăn xếp hoạt động (theo mô hình nhúng phi hoạt động).

Không cho ghi đè hệ thống

Các nhà sản xuất thiết bị Android (nhà sản xuất thiết bị gốc hay OEM) có thể triển khai việc nhúng hoạt động dưới dạng một chức năng hệ thống của thiết bị. Hệ thống sẽ chỉ định quy tắc phân tách cho các ứng dụng nhiều hoạt động, ghi đè hành vi kết xuất cửa sổ của các ứng dụng. Cơ chế ghi đè hệ thống buộc các ứng dụng nhiều hoạt động chuyển sang chế độ nhúng hoạt động do hệ thống xác định.

Tính năng nhúng hoạt động hệ thống có thể cải thiện bản trình bày ứng dụng thông qua các bố cục nhiều ngăn, chẳng hạn như list-detail mà không cần thay đổi ứng dụng. Tuy nhiên, hoạt động nhúng của hệ thống cũng có thể gây ra sự cố bố cục ứng dụng không chính xác, lỗi hoặc xung đột với tính năng nhúng hoạt động do ứng dụng triển khai.

Ứng dụng của bạn có thể ngăn chặn hoặc cho phép nhúng hoạt động hệ thống bằng cách thiết lập thuộc tính trong tệp kê khai ứng dụng, ví dụ:

<manifest xmlns:android="http://schemas.android.com/apk/res/android">
    <application>
        <property
            android:name="android.window.PROPERTY_ACTIVITY_EMBEDDING_ALLOW_SYSTEM_OVERRIDE"
            android:value="true|false" />
    </application>
</manifest>

Tên thuộc tính được xác định trong đối tượng WindowProperties của Jetpack WindowManager. Thiết lập giá trị thành false nếu ứng dụng của bạn triển khai tính năng nhúng hoạt động hoặc nếu bạn muốn ngăn hệ thống áp dụng các quy tắc nhúng hoạt động cho ứng dụng. Thiết lập giá trị thành true để cho phép hệ thống áp dụng hoạt động nhúng do hệ thống xác định cho ứng dụng của bạn.

Giới hạn, hạn chế và cảnh báo

  • Chỉ có ứng dụng máy chủ lưu trữ của tác vụ được xác định là chủ sở hữu của hoạt động gốc trong tác vụ, có thể sắp xếp và nhúng các hoạt động khác trong nhiệm vụ. Nếu các hoạt động hỗ trợ tính năng nhúng và phân tách chạy trong một thao tác thuộc về ứng dụng khác, thì việc nhúng và phân tách sẽ không hiệu quả đối với các hoạt động đó.
  • Các hoạt động chỉ có thể được tổ chức trong một nhiệm vụ duy nhất. Khi chạy hoạt động trong một tác vụ mới, hoạt động đó sẽ luôn được mở trong cửa sổ mở rộng mới bên ngoài mọi hoạt động phân tách hiện có.
  • Bạn chỉ có thể sắp xếp và tách riêng các hoạt động trong cùng một quy trình. Lệnh gọi lại SplitInfo chỉ báo cáo các hoạt động thuộc cùng một quy trình vì không có cách nào nhận biết các hoạt động ở các quy trình khác nhau.
  • Mỗi cặp hoặc quy tắc hoạt động đơn lẻ chỉ áp dụng cho các lần khởi chạy hoạt động xảy ra sau khi quy tắc được đăng ký. Hiện chưa có cách cập nhật phần phân chia hiện có hoặc thuộc tính trực quan của các phần phân chia đó.
  • Cấu hình bộ lọc cặp phân tách phải khớp với ý định dùng khi khởi chạy các hoạt động hoàn toàn. Việc so khớp xảy ra tại thời điểm một hoạt động mới bắt đầu từ quá trình ứng dụng. Vì vậy, tính năng này có thể không nhận biết tên các thành phần sẽ được phân giải sau này trong quá trình hệ thống sử dụng các ý định ngầm ẩn. Nếu không xác định tên thành phần tại thời điểm khởi chạy, bạn có thể sử dụng ký tự đại diện ("*/*") và thao tác lọc hành động theo ý định.
  • Hiện tại, không có cách nào để di chuyển hoạt động giữa các vùng chứa hoặc trong và ngoài các phần phân chia sau khi đã tạo. Tính năng phân tách chỉ do thư viện WindowManager tạo ra khi các hoạt động mới có quy tắc so khớp được khởi chạy và các phần phân tách bị huỷ khi hoạt động gần đây nhất trong vùng chứa phân tách đã hoàn tất.
  • Các hoạt động có thể được chạy lại khi cấu hình thay đổi, vì vậy, khi phân tách được tạo hoặc loại bỏ và giới hạn hoạt động thay đổi, hoạt động có thể huỷ bỏ hoàn toàn đối tượng trước đó và tạo một đối tượng mới. Do đó, các nhà phát triển ứng dụng nên thận trọng với những việc như khởi chạy hoạt động mới từ các phương thức gọi lại trong vòng đời.
  • Thiết bị phải có giao diện tiện ích cửa sổ để hỗ trợ tính năng nhúng hoạt động. Gần như mọi thiết bị có màn hình lớn chạy Android 12L (API cấp 32) trở lên đều có giao diện. Tuy nhiên, một số thiết bị có màn hình lớn (không chạy được nhiều hoạt động) không bao gồm giao diện tiện ích cửa sổ. Nếu thiết bị có màn hình lớn không hỗ trợ chế độ nhiều cửa sổ, thì thiết bị đó có thể không hỗ trợ tính năng nhúng hoạt động.

Tài nguyên khác