一:问题
以下代码为判断是否已安装某指定app,在工程升级到Android11后无法正确运行,都是未安装。
private static boolean isInstallApp(Context context, String packageName) {
PackageInfo packageInfo;
try {
packageInfo = context.getPackageManager().getPackageInfo(packageName, 0);
} catch (Exception e) {
packageInfo = null;
e.printStackTrace();
Log.e("判断是否安装导航软件时出错,",e.getMessage());
}
return packageInfo != null;
}
二:原因
Android 目标 SDK 版本(targetSdkVersion)升级到 30 后,无法检测应用安装问题,主要是由于 Android 11(API 级别 30)引入了包可见性(Package Visibility)限制来提高用户隐私和安全性。
在 Android 11 及更高版本中,你的应用默认只能看到有限的包集合。getPackageInfo(packageName, 0)
方法在查询不在这个“可见集合”内的应用时,即使应用已安装,也可能抛出 NameNotFoundException
或返回 null,导致判断失效。
默认情况下,你的应用只能看到以下应用:
-
你自己的应用
-
实现了 Android 核心功能的某些系统包(如媒体提供程序)
-
安装了你应用的应用(通常应用商店或安装器)
-
使用
startActivityForResult()
在你应用中启动了某个 activity 的任何应用 -
启动或绑定到你应用中某项服务的任何应用
-
访问你应用中 Content Provider 的任何应用
-
具有 Content Provider 且你应用已被授予 URI 权限来访问的任何应用
-
作为输入法向你应用提供输入的任何应用
三:解决方法
1、声明权限需要查询所有包
在 AndroidManifest.xml
中添加权限声明:(本文用该方法解决)
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.your.packagename">
<!-- 添加查询所有包的权限 -->
<uses-permission android:name="android.permission.QUERY_ALL_PACKAGES" />
<application ...>
...
</application>
</manifest>
2、声明特定的包名
如果你的应用只需要查询少数几个特定的应用(例如高德地图、百度地图),这是最推荐的做法,因为它遵循了最小权限原则。
在你的 AndroidManifest.xml
文件中的 <manifest>
标签内,添加 <queries>
元素:
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.your.packagename">
<!-- 添加 queries 声明 -->
<queries>
<!-- 指定你需要查询的单个包名 -->
<package android:name="com.autonavi.minimap" /> <!-- 高德地图 -->
<package android:name="com.baidu.BaiduMap" /> <!-- 百度地图 -->
<!-- 添加其他你需要检测的导航软件包名 -->
</queries>
<application ...>
...
</application>
</manifest>
3、通过intent filter查询
如果你不知道应用的包名,只是想查询具有某种功能的应用,那么可以使用以下的方式
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="com.blowing.demo"
<queries>
<intent>
<action android:name="android.intent.action.SEND"/>
<data android:mimeType="image/png"/>
</intent>
</queries>
</manifest>
如果我们使用android.intent.action.MAIN 作为action元素,那么不添加权限,也是可以绕过去的,因为几乎所有应用都会有这个action。
那么,完整的就是:
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.your.packagename">
<uses-permission android:name="android.permission.QUERY_ALL_PACKAGES" />
<queries>
<!-- 这是一个非常广泛的查询,声明你的应用需要与具有 MAIN Action 的应用交互 -->
<intent>
<action android:name="android.intent.action.MAIN" />
</intent>
</queries>
<application ...>
...
</application>
</manifest>