Đáp ứng các trường hợp sử dụng phổ biến trong khi có chế độ hiển thị gói hạn chế

Tài liệu này trình bày một số trường hợp sử dụng phổ biến mà trong đó một ứng dụng tương tác với các ứng dụng khác. Mỗi phần sẽ có hướng dẫn về cách thực hiện chức năng của ứng dụng này bằng một chế độ hiển thị gói có giới hạn mà bạn cần xem xét nếu ứng dụng của mình nhắm đến Android 11 (API cấp 30) trở lên.

Khi một ứng dụng nhắm đến Android 11 trở lên sử dụng ý định để bắt đầu một hoạt động trong ứng dụng khác, thì phương pháp đơn giản nhất là gọi ý định và xử lý ngoại lệ ActivityNotFoundException nếu không có ứng dụng nào.

Nếu một phần ứng dụng của bạn phụ thuộc vào việc biết liệu lệnh gọi đến startActivity() có thể thành công hay không, chẳng hạn như hiển thị giao diện người dùng, hãy thêm một phần tử vào phần tử <queries> của tệp kê khai ứng dụng. Thông thường, đây là phần tử <intent>.

Mở URL

Phần này mô tả các cách để mở URL trong một ứng dụng nhắm đến Android 11 trở lên.

Mở URL trong trình duyệt hoặc ứng dụng khác

Để mở URL, hãy sử dụng một ý định chứa thao tác theo ý định ACTION_VIEW, như mô tả trong hướng dẫn về cách tải một URL web. Sau khi bạn gọi startActivity() bằng ý định này, một trong các trường hợp sau sẽ xảy ra:

  • URL sẽ mở trong một ứng dụng trình duyệt web.
  • URL sẽ mở trong một ứng dụng hỗ trợ URL ở dạng đường liên kết sâu.
  • Hộp thoại phân định sẽ xuất hiện, cho phép người dùng chọn ứng dụng sẽ mở URL.
  • ActivityNotFoundException xuất hiện vì không có ứng dụng nào được cài đặt trên thiết bị có thể mở URL. (Đây là điều bất thường.)

    Ứng dụng của bạn nên nắm bắt và xử lý ActivityNotFoundException nếu ngoại lệ đó xuất hiện.

Bởi vì phương thức startActivity() không yêu cầu chế độ hiển thị gói để bắt đầu hoạt động của một ứng dụng khác, nên bạn không cần thêm <queries> vào tệp kê khai của ứng dụng hoặc thực hiện bất kỳ thay đổi nào đối với phần tử <queries> hiện có. Nguyên tắc này áp dụng cho cả ý định ngầm ẩn và ý định tường minh mở ra một URL.

Kiểm tra xem có trình duyệt nào không

Trong một số trường hợp, ứng dụng của bạn nên xác minh rằng có ít nhất một trình duyệt trên thiết bị, hoặc rằng một trình duyệt cụ thể là trình duyệt mặc định, trước khi cố mở URL. Trong những trường hợp đó, hãy thêm phần tử <intent> sau đây vào phần tử <queries> của tệp kê khai:

<!-- Place inside the <queries> element. -->
<intent>
  <action android:name="android.intent.action.VIEW" />
  <category android:name="android.intent.category.BROWSABLE" />
  <data android:scheme="https" />
</intent>

Khi bạn gọi queryIntentActivities() và chuyển một ý định trên web làm đối số, trong một số trường hợp, danh sách được trả về sẽ bao gồm các ứng dụng trình duyệt có sẵn. Danh sách này sẽ không bao gồm các ứng dụng trình duyệt nếu người dùng đã thiết lập URL để mở trong một ứng dụng không phải là trình duyệt theo mặc định.

Mở URL trong Thẻ tuỳ chỉnh

Thẻ tuỳ chỉnh cho phép một ứng dụng tuỳ chỉnh giao diện của trình duyệt. Bạn có thể mở một URL trong Thẻ tuỳ chỉnh mà không cần thêm hay thay đổi phần tử <queries> trong tệp kê khai ứng dụng.

Tuy nhiên, bạn nên kiểm tra xem thiết bị có một trình duyệt hỗ trợ Thẻ tuỳ chỉnh hay không, hoặc chọn một trình duyệt cụ thể để mở bằng Thẻ tuỳ chỉnh thông qua CustomTabsClient.getPackageName(). Trong những trường hợp đó, hãy thêm phần tử <intent> sau đây vào phần tử <queries> của tệp kê khai:

<!-- Place inside the <queries> element. -->
<intent>
  <action android:name="android.support.customtabs.action.CustomTabsService" />
</intent>

Cho phép các ứng dụng không phải là trình duyệt xử lý URL

Ngay cả khi ứng dụng của bạn có thể mở URL bằng Thẻ tuỳ chỉnh, bạn vẫn nên cho phép một ứng dụng không phải là trình duyệt mở một URL nếu có thể. Để cung cấp chức năng này trong ứng dụng của bạn, hãy thực hiện lệnh gọi đến startActivity() bằng cách sử dụng một ý định đặt cờ ý định FLAG_ACTIVITY_REQUIRE_NON_BROWSER. Nếu hệ thống trả về ActivityNotFoundException, thì ứng dụng của bạn có thể mở URL trong Thẻ tuỳ chỉnh.

Nếu một ý định có cờ này, lệnh gọi đến startActivity() sẽ trả về ActivityNotFoundException khi một trong hai điều kiện sau đây xảy ra:

  • Lệnh gọi đã chạy trực tiếp một ứng dụng trình duyệt.
  • Lệnh gọi đã hiển thị cho người dùng hộp thoại phân định, trong đó tuỳ chọn duy nhất là ứng dụng trình duyệt.

Đoạn mã sau đây cho biết cách cập nhật logic để sử dụng cờ ý định FLAG_ACTIVITY_REQUIRE_NON_BROWSER:

Kotlin

try {
    val intent = Intent(ACTION_VIEW, Uri.parse(url)).apply {
        // The URL should either launch directly in a non-browser app (if it's
        // the default) or in the disambiguation dialog.
        addCategory(CATEGORY_BROWSABLE)
        flags = FLAG_ACTIVITY_NEW_TASK or FLAG_ACTIVITY_REQUIRE_NON_BROWSER
    }
    startActivity(intent)
} catch (e: ActivityNotFoundException) {
    // Only browser apps are available, or a browser is the default.
    // So you can open the URL directly in your app, for example in a
    // Custom Tab.
    openInCustomTabs(url)
}

Java

try {
    Intent intent = new Intent(ACTION_VIEW, Uri.parse(url));
    // The URL should either launch directly in a non-browser app (if it's the
    // default) or in the disambiguation dialog.
    intent.addCategory(CATEGORY_BROWSABLE);
    intent.setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_REQUIRE_NON_BROWSER);
    startActivity(intent);
} catch (ActivityNotFoundException e) {
    // Only browser apps are available, or a browser is the default.
    // So you can open the URL directly in your app, for example in a
    // Custom Tab.
    openInCustomTabs(url);
}

Tránh hiện hộp thoại phân định

Nếu muốn tránh hiện hộp thoại phân định mà người dùng có thể thấy khi họ mở một URL, cũng như muốn tự xử lý URL trong các tình huống này, thì bạn có thể sử dụng một ý định đặt cờ ý định FLAG_ACTIVITY_REQUIRE_DEFAULT.

Nếu một ý định có cờ này, lệnh gọi đến startActivity() sẽ trả về ActivityNotFoundException khi lệnh gọi đã hiển thị một hộp thoại phân định cho người dùng.

Nếu một ý định có cả cờ này và cờ ý định FLAG_ACTIVITY_REQUIRE_NON_BROWSER, lệnh gọi đến startActivity() sẽ trả về ActivityNotFoundException khi xảy ra một trong hai điều kiện sau đây:

  • Lệnh gọi đã chạy trực tiếp ứng dụng trình duyệt.
  • Lệnh gọi đã hiển thị hộp thoại phân định cho người dùng.

Đoạn mã sau đây cho biết cách sử dụng kết hợp cờ FLAG_ACTIVITY_REQUIRE_NON_BROWSER và cờ FLAG_ACTIVITY_REQUIRE_DEFAULT:

Kotlin

val url = URL_TO_LOAD
try {
    // For this intent to be invoked, the system must directly launch a
    // non-browser app.
    val intent = Intent(ACTION_VIEW, Uri.parse(url)).apply {
        addCategory(CATEGORY_BROWSABLE)
        flags = FLAG_ACTIVITY_NEW_TASK or FLAG_ACTIVITY_REQUIRE_NON_BROWSER or
                FLAG_ACTIVITY_REQUIRE_DEFAULT
    }
    startActivity(intent)
} catch (e: ActivityNotFoundException) {
    // This code executes in one of the following cases:
    // 1. Only browser apps can handle the intent.
    // 2. The user has set a browser app as the default app.
    // 3. The user hasn't set any app as the default for handling this URL.
    openInCustomTabs(url)
}

Java

String url = URL_TO_LOAD;
try {
    // For this intent to be invoked, the system must directly launch a
    // non-browser app.
    Intent intent = new Intent(ACTION_VIEW, Uri.parse(url));
    intent.addCategory(CATEGORY_BROWSABLE);
    intent.setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_REQUIRE_NON_BROWSER |
            FLAG_ACTIVITY_REQUIRE_DEFAULT);
    startActivity(intent);
} catch (ActivityNotFoundException e) {
    // This code executes in one of the following cases:
    // 1. Only browser apps can handle the intent.
    // 2. The user has set a browser app as the default app.
    // 3. The user hasn't set any app as the default for handling this URL.
    openInCustomTabs(url);
}

Mở tệp

Nếu ứng dụng của bạn xử lý tệp hoặc tệp đính kèm, chẳng hạn như kiểm tra xem thiết bị có thể mở một tệp nhất định hay không, thì thông thường, cách dễ nhất là thử bắt đầu một hoạt động có thể xử lý tệp đó. Để làm vậy, hãy sử dụng một ý định có thao tác theo ý định ACTION_VIEW và URI biểu thị tệp cụ thể. Nếu không có ứng dụng nào trên thiết bị, ứng dụng của bạn có thể nắm bắt ActivityNotFoundException. Trong logic xử lý ngoại lệ, bạn có thể hiển thị lỗi hoặc cố tự xử lý tệp.

Nếu ứng dụng của bạn phải biết trước liệu ứng dụng khác có thể mở một tệp nhất định hay không, hãy thêm phần tử <intent> trong đoạn mã sau đây vào phần tử <queries> của tệp kê khai. Bạn có thể thêm loại tệp nếu đã biết nội dung tệp vào thời gian biên dịch.

<!-- Place inside the <queries> element. -->
<intent>
  <action android:name="android.intent.action.VIEW" />
  <!-- If you don't know the MIME type in advance, set "mimeType" to "*/*". -->
  <data android:mimeType="application/pdf" />
</intent>

Sau đó, bạn có thể kiểm tra xem có ứng dụng nào không bằng cách gọi resolveActivity() cùng với ý định của mình.

Cấp quyền truy cập URI

Lưu ý: Việc khai báo quyền truy cập URI như mô tả trong phần này là bắt buộc đối với các ứng dụng nhắm đến Android 11 (API cấp 30) trở lên và được đề xuất cho tất cả các ứng dụng, bất kể phiên bản SDK mục tiêu là gì và liệu chúng có xuất trình cung cấp nội dung của mình hay không.

Để các ứng dụng nhắm đến Android 11 trở lên truy cập vào URI nội dung, ý định của ứng dụng phải khai báoQuyền truy cập URI bằng cách đặt một hoặc cả hai cờ ý định sau: FLAG_GRANT_READ_URI_PERMISSIONFLAG_GRANT_WRITE_URI_PERMISSION.

Trên Android 11 trở lên, quyền truy cập vào URI sẽ cung cấp chức năng sau đây cho ứng dụng nhận được ý định:

  • Đọc hoặc ghi dữ liệu mà URI nội dung biểu thị, tuỳ thuộc vào quyền URI được cấp.
  • Hiểu rõ về ứng dụng chứa trình cung cấp nội dung khớp với đơn vị quản lý URI. Ứng dụng chứa trình cung cấp nội dung có thể khác với ứng dụng gửi ý định.

Đoạn mã sau đây minh hoạ cách thêm cờ ý định quyền URI để ứng dụng khác nhắm đến Android 11 trở lên có thể xem dữ liệu trong URI nội dung:

Kotlin

val shareIntent = Intent(Intent.ACTION_VIEW).apply {
    flags = Intent.FLAG_GRANT_READ_URI_PERMISSION
    data = CONTENT_URI_TO_SHARE_WITH_OTHER_APP
}

Java

Intent shareIntent = new Intent(Intent.ACTION_VIEW);
shareIntent.setFlags(FLAG_GRANT_READ_URI_PERMISSION);
shareIntent.setData(CONTENT_URI_TO_SHARE_WITH_OTHER_APP);

Kết nối với các dịch vụ

Nếu ứng dụng của bạn cần tương tác với một dịch vụ không tự động hiển thị, thì bạn có thể khai báo thao tác theo ý định phù hợp trong phần tử <queries>. Những phần sau đây cung cấp ví dụ thông qua các dịch vụ thường được truy cập.

Kết nối với công cụ chuyển văn bản sang lời nói

Nếu ứng dụng của bạn tương tác với một công cụ chuyển văn bản sang lời nói (TTS), hãy thêm phần tử <intent> sau đây vào phần tử <queries> của tệp kê khai:

<!-- Place inside the <queries> element. -->
<intent>
  <action android:name="android.intent.action.TTS_SERVICE" />
</intent>

Kết nối với dịch vụ nhận dạng lời nói

Nếu ứng dụng của bạn tương tác với một dịch vụ nhận dạng lời nói, hãy thêm phần tử <intent> sau đây vào phần tử <queries> của tệp kê khai:

<!-- Place inside the <queries> element. -->
<intent>
  <action android:name="android.speech.RecognitionService" />
</intent>

Kết nối với các dịch vụ trình duyệt nội dung nghe nhìn

Nếu ứng dụng của bạn là ứng dụng trình duyệt nội dung nghe nhìn máy khách, hãy thêm phần tử <intent> sau đây vào phần tử <queries> của tệp kê khai:

<!-- Place inside the <queries> element. -->
<intent>
  <action android:name="android.media.browse.MediaBrowserService" />
</intent>

Cung cấp chức năng tuỳ chỉnh

Nếu ứng dụng của bạn cần thực hiện các thao tác có thể tuỳ chỉnh hoặc hiển thị thông tin có thể tuỳ chỉnh dựa trên hoạt động tương tác của ứng dụng đó với các ứng dụng khác, thì bạn có thể biểu thị hành vi tuỳ chỉnh đó bằng cách dùng chữ ký bộ lọc ý định trong phần tử <queries> của tệp kê khai. Những phần sau đây cung cấp hướng dẫn chi tiết cho một số trường hợp phổ biến.

Truy vấn các ứng dụng SMS

Nếu ứng dụng của bạn cần thông tin về tập hợp ứng dụng SMS được cài đặt trên một thiết bị, chẳng hạn như để kiểm tra xem ứng dụng nào là trình xử lý SMS mặc định trên thiết bị, hãy thêm phần tử <intent> sau đây vào phần tử <queries> của tệp kê khai:

<!-- Place inside the <queries> element. -->
<intent>
  <action android:name="android.intent.action.SENDTO"/>
  <data android:scheme="smsto" android:host="*" />
</intent>

Tạo trang chia sẻ nội dung tuỳ chỉnh

Bất cứ khi nào có thể, hãy sử dụng trang chia sẻ nội dung do hệ thống cung cấp. Ngoài ra, hãy thêm phần tử <intent> sau đây vào phần tử <queries> của tệp kê khai:

<!-- Place inside the <queries> element. -->
<intent>
  <action android:name="android.intent.action.SEND" />
  <!-- Replace with the MIME type that your app works with, if needed. -->
  <data android:mimeType="image/jpeg" />
</intent>

Quá trình tạo trang chia sẻ nội dung trong logic của ứng dụng, chẳng hạn như lệnh gọi đến queryIntentActivities(), sẽ không thay đổi so với các phiên bản Android trước Android 11.

Hiển thị thao tác lựa chọn văn bản tuỳ chỉnh

Khi người dùng chọn văn bản trong ứng dụng của bạn, thanh công cụ chọn văn bản sẽ hiển thị tập hợp thao tác có thể thực hiện trên văn bản đã chọn. Nếu thanh công cụ này hiển thị các thao tác tuỳ chỉnh từ những ứng dụng khác, hãy thêm phần tử <intent> sau đây vào phần tử <queries> của tệp kê khai:

<!-- Place inside the <queries> element. -->
<intent>
  <action android:name="android.intent.action.PROCESS_TEXT" />
  <data android:mimeType="text/plain" />
</intent>

Hiển thị hàng dữ liệu tuỳ chỉnh cho một người liên hệ

Các ứng dụng có thể thêm hàng dữ liệu tuỳ chỉnh vào Trình cung cấp danh bạ. Để một ứng dụng danh bạ hiển thị dữ liệu tuỳ chỉnh này, ứng dụng đó cần phải có khả năng thực hiện những việc sau:

  1. Đọc tệp contacts.xml từ các ứng dụng khác.
  2. Tải một biểu tượng tương ứng với loại MIME tuỳ chỉnh.

Nếu ứng dụng của bạn là một ứng dụng danh bạ, hãy thêm phần tử <intent> sau đây vào phần tử <queries> của tệp kê khai:

<!-- Place inside the <queries> element. -->
<!-- Lets the app read the contacts.xml file from other apps. -->
<intent>
  <action android:name="android.accounts.AccountAuthenticator" />
</intent>
<!-- Lets the app load an icon corresponding to the custom MIME type. -->
<intent>
  <action android:name="android.intent.action.VIEW" />
  <data android:scheme="content" android:host="com.android.contacts"
        android:mimeType="vnd.android.cursor.item/*" />
</intent>