欢迎参加我们将于 6 月 3 日举行的 #Android11:Beta 版发布会

Android 11 中的软件包可见性

Android 11 更改了应用查询同一设备上的其他已安装应用以及与之交互的方式。如果您的应用以 Android 11 为目标平台,您可能需要在应用的清单文件中添加 <queries> 元素,以便系统了解应向您的应用显示哪些其他应用。

<queries> 元素可用于描述您的应用可能需要与哪些其他应用交互。您可以在单个 <queries> 元素中按软件包名称指定应用,也可以按 intent 签名指定应用。

返回其他应用相关结果的 PackageManager 方法(如 queryIntentActivities())会根据发起调用的应用的 <queries> 声明进行过滤。与其他应用的显式交互(如 startService())还要求目标应用与 <queries> 中的某项声明相符。

我们期待能收到您的反馈!请填写这份简短的调查问卷,将您使用此功能的情况告知我们。特别是,请将受此功能影响的用例告知我们。

查询特定软件包及与之交互

如果您知道要查询或与之交互的一组特定应用(例如,与您的应用集成的应用或您使用其服务的应用),请将其软件包名称添加到 <queries> 标记内的一组 <package> 元素中:

<manifest package="com.example.game">
    <queries>
        <package android:name="com.example.store" />
        <package android:name="com.example.services" />
    </queries>
    ...
</manifest>

在给定 intent 过滤器的情况下查询应用及与之交互

您的应用可能需要查询一组具有特定用途的应用或与之交互,但您可能不知道要添加的具体软件包名称。在这种情况下,您可以在 <queries> 标记中列出 intent 过滤器签名。然后,您的应用就可以发现具有匹配的 <intent-filter> 标记的应用。

以下示例允许您的应用看到支持 JPEG 图片共享功能的已安装应用:

<manifest package="com.example.game">
    <queries>
        <intent>
            <action android:name="android.intent.action.SEND" />
            <data android:mimeType="image/jpeg" />
        </intent>
    </queries>
    ...
</manifest>

<intent> 元素有一些限制:

  • 您必须只添加一个 <action> 元素。
  • 您不能在 <data> 元素中使用 pathpathPrefixpathPatternport 属性。系统的行为就像您将每个属性的值都设为通用通配符 (*) 一样。
  • 您不能使用 <data> 元素的 mimeGroup 属性。
  • 在单个 <intent> 元素的 <data> 元素中,您可以使用以下每个属性最多一次:

    • mimeType
    • scheme
    • host

    您可以在多个 <data> 元素之间分配这些属性,也可以在单个 <data> 元素中使用这些属性。

<intent> 元素支持通用通配符 (*) 作为一些属性的值:

  • <action> 元素的 name 属性。
  • <data> 元素的 mimeType 属性的子类型 (image/*)。
  • <data> 元素的 mimeType 属性的类型和子类型 (*/*)。
  • <data> 元素的 scheme 属性。
  • <data> 元素的 host 属性。

除非前面列表中另有说明,否则系统不支持混合使用文本和通配符,如 prefix*

查询所有应用及与之交互

在极少数情况下,您的应用可能需要查询设备上的所有已安装应用或与之交互,不管这些应用包含哪些组件。例如,Google Play 等应用商店。为了允许您的应用看到其他所有已安装应用,Android 11 引入了 QUERY_ALL_PACKAGES 权限。

注意:在绝大多数情况下,可以通过声明 <queries> 标记实现应用的用例。

在即将推出的开发者预览版中,Google Play 会为需要此权限的应用提供相关指南,敬请期待。

软件包过滤的日志消息

如需详细了解软件包可见性的变更对您的应用有何影响,您可以启用软件包过滤的日志消息。如果您是在 Android Studio 中开发测试应用或可调试应用,系统会为您启用该功能。或者,您也可以在终端窗口中运行以下命令,手动启用该功能:

adb shell pm log-visibility --enable your-package-name

然后,每当从 PackageManager 对象的返回值中滤除软件包时,您都会在 Logcat 中看到类似于以下内容的消息:

I/AppsFilter: interaction: PackageSetting{7654321 \
  com.example.myapp/12345} -> PackageSetting{...} BLOCKED

测试变更

如需测试此行为变更是否已在您的应用中生效,请完成以下步骤:

  1. 安装 Android Studio 3.6.1 或更高版本。
  2. 安装 Android Studio 支持的最新版 Gradle。
  3. 将应用的 targetSdkVersion 设为 'R'
  4. 不要在应用的清单文件中添加 <queries> 元素。
  5. 调用 getInstalledApplications()getInstalledPackages()。这两种方法都应返回过滤后的列表。
  6. 查看应用的哪些功能无法正常使用。
  7. 引入适当的 <queries> 条目来修复这些功能。

不受变更影响的用例

以下列表包含几个不需要 <queries> 声明的用例示例:

  • 目标应用是您自己的应用。
  • 您可以使用隐式 intent 启动 Activity。您的应用可能会限制其使用隐式 intent 与其他应用交互的方式。
  • 您的应用与实现 Android 核心功能的某些系统软件包(如媒体提供程序)交互。
  • 其他应用期望从您的应用获得结果。当您的应用是内容提供程序时、当其他应用通过调用 startActivityForResult() 调用您的应用时,以及当您的应用是其他应用尝试启动或连接到的服务时,会出现这种情况。

例如,如果其他应用向您应用中的内容提供程序发出请求,系统将允许您的应用看到该其他应用。

为 Activity 开始时间添加限制

Android 11 增加了多个标记,可让您指定何时从应用调用 startActivity() 应产生ActivityNotFoundException,而不是让其他应用处理 intent。通过使用这些标记,您无需调用 resolveActivity()queryIntentActivities()

这些标记对于网络 intent 特别有用。当您的应用希望深层链接到已安装的应用且该应用无法访问时,您的应用可以通过在自定义标签页或应用内浏览器中加载网址自行处理 intent。

在非浏览器应用中启动网络 intent

如果您添加了 FLAG_ACTIVITY_REQUIRE_NON_BROWSER intent 标记,只有在以下情况下才会启动 intent:

  • 非浏览器应用会直接处理 intent。
  • 用户可以在消除歧义对话框中选择非浏览器应用。

否则,系统将抛出 ActivityNotFoundException,并且您的应用应自行处理 intent。

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.
    tryLoadInCustomTabs(url) // Or use an in-app browser.
}

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.
    tryLoadInCustomTabs(url); // Or use an in-app browser.
}

仅需一个匹配 Activity

如果您添加了 FLAG_ACTIVITY_REQUIRE_DEFAULT intent 标记,当设备上只有一个应用可以处理您应用的 intent 时,或者当某个应用是该 intent 的默认处理程序时,会调用您应用的 intent。此标记不会显示消除歧义对话框,而是允许应用选择如何向用户显示内容,从而省去一些麻烦。

Kotlin


val url = url-to-load
try {
    // In order 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_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 URLs.
    tryLoadInCustomTabs(url)
}

Java

String url = url-to-load;
try {
    // In order 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_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 URLs.
    tryLoadInCustomTabs(url);
}