Android 11 更改了应用查询用户已在设备上安装的其他应用以及与之交互的方式。使用新的 <queries>
元素,应用可以定义一组自身可访问的其他应用。通过告知系统应向您的应用显示哪些其他应用,此元素有助于鼓励最小权限原则。此外,此元素还可帮助 Google Play 等应用商店评估应用为用户提供的隐私权和安全性。
如果应用以 Android 11 为目标平台,您可能需要在应用的清单文件中添加 <queries>
元素。在 <queries>
元素中,您可以按软件包名称或按 intent 签名指定应用。
注意:在某些情况下,即使您的应用以 Android 11 为目标平台,您也根本不需要更新自己的应用来支持这一变更。
返回其他应用相关结果的 PackageManager
方法(如 queryIntentActivities()
)会根据发起调用的应用的 <queries>
声明进行过滤。与其他应用的显式交互(如 startService()
)还要求目标应用与 <queries>
中的某项声明相符。
设置您的环境
如需构建使用 <queries>
元素的应用,请使用以下工具:
- Android Studio 3.6.1 或更高版本
- 最新版本的 Android Gradle 插件
查询特定软件包及与之交互
如果您知道要查询或与之交互的一组特定应用(例如,与您的应用集成的应用或您使用其服务的应用),请将其软件包名称添加到 <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>
元素中使用path
、pathPrefix
、pathPattern
或port
属性。系统的行为就像您将每个属性的值都设为通用通配符 (*
) 一样。 - 您不能使用
<data>
元素的mimeGroup
属性。 -
在单个
<intent>
元素的<data>
元素中,您可以使用以下每个属性最多一次:mimeType
scheme
host
您可以在多个
<data>
元素之间分配这些属性,也可以在单个<data>
元素中使用这些属性。
<intent>
元素支持通用通配符 (*
) 作为一些属性的值:
<action>
元素的name
属性。<data>
元素的mimeType
属性的子类型 (image/*
)。<data>
元素的mimeType
属性的类型和子类型 (*/*
)。<data>
元素的scheme
属性。<data>
元素的host
属性。
除非前面列表中另有说明,否则系统不支持混合使用文本和通配符,如 prefix*
。
注意:如果您在应用清单中声明了 <package>
元素,那么,在查询与该应用中的组件匹配的 PackageManager
时,查询结果中会出现与该软件包名称关联的应用。
查询所有应用及与之交互
在极少数情况下,您的应用可能需要查询设备上的所有已安装应用或与之交互,不管这些应用包含哪些组件。为了允许您的应用看到其他所有已安装应用,Android 11 引入了 QUERY_ALL_PACKAGES
权限。
下面列出了适合添加 QUERY_ALL_PACKAGES
权限的用例的一些示例:
- 启动器应用
- 无障碍应用
- 浏览器
- 点对点 (P2P) 共享应用
- 设备管理应用
- 安全应用
不过,在绝大多数情况下,可以通过声明 <queries>
元素实现应用的用例。为了尊重用户隐私,您的应用应请求应用正常工作所需的最小软件包可见性。
在即将发布的政策更新中,Google Play 会为需要 QUERY_ALL_PACKAGES
权限的应用提供相关指南,敬请期待。
软件包过滤的日志消息
如需详细了解软件包可见性的变更对您的应用有何影响,您可以启用软件包过滤的日志消息。如果您是在 Android Studio 中开发测试应用或可调试应用,系统会为您启用该功能。或者,您也可以在终端窗口中运行以下命令,手动启用该功能:
adb shell pm log-visibility --enable PACKAGE_NAME
然后,每当从 PackageManager
对象的返回值中滤除软件包时,您都会在 Logcat 中看到类似于以下内容的消息:
I/AppsFilter: interaction: PackageSetting{7654321 \
com.example.myapp/12345} -> PackageSetting{...} BLOCKED
注意:启用此标记后,应用的性能会受到影响。建议您停止用日志记录与软件包可见性相关的消息,除非您要测试软件包可见性行为。
测试变更
如需测试此行为变更是否已在您的应用中生效,请完成以下步骤:
- 安装 Android Studio 3.6.1 或更高版本。
- 安装 Android Studio 支持的最新版 Gradle。
- 将应用的
targetSdkVersion
设为30
。 - 不要在应用的清单文件中添加
<queries>
元素。 - 调用
getInstalledApplications()
或getInstalledPackages()
。这两种方法都应返回过滤后的列表。 - 查看应用的哪些功能无法正常使用。
- 引入适当的
<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。
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。此标记不会显示消除歧义对话框,而是允许应用选择如何向用户显示内容,从而省去一些麻烦。
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);
}