去掉安卓的“读取已安装应用列表”,隐私合规

vivo:https://dev.vivo.com.cn/documentCenter/doc/747

XML 复制代码
<meta-data
     android:name="oem_installed_apps_runtime_permission_enable"
     android:value="true"/>

小米:https://dev.mi.com/xiaomihyperos/documentation/detail?pId=1619

XML 复制代码
<meta-data
     android:name="do_not_need_get_installed_apps"
     android:value="true"/>

通过上述方案适配,小米和vivo设备基本就不会弹出了。不过oppo和华为(mate 30 pro) 还是会弹出

最终方案:

在工程AndroidManifest.xml里加上以下代码:

XML 复制代码
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" 

tools:node="remove" />

<uses-permission android:name="android.permission.QUERY_ALL_PACKAGES" 

tools:ignore="QueryAllPackagesPermission" 

tools:node="remove" />

在混淆文件proguard-rules.pro里加上以下代码:

XML 复制代码
-assumenosideeffects class android.content.pm.PackageManager {
    public java.util.List getInstalledPackages(int) return null;
    public java.util.List getInstalledApplications(int) return null;
}

即可解决oppo和华为设备的 "读取已安装应用列表" 弹窗

最最终方案:

与隐私合规检测三方进行沟通,机审存在误判

XML 复制代码
截图内给到的日志1是:

```
2026-03-30 15:24:08.416  2524-3019  TelephonyPermissions  com.android.phone
E Exception caught obtaining package info for package null 
android.content.pm.PackageManager$NameNotFoundException
    at android.app.ApplicationPackageManager.getApplicationInfoAsUser(ApplicationPackageManager.java:452)
    at android.content.pm.PackageManager.getApplicationInfoAsUser(PackageManager.java:4759)
    at com.android.internal.telephony.TelephonyPermissions.reportAccessDeniedToReadIdentifiers(TelephonyPermissions.java:
    at com.android.internal.telephony.TelephonyPermissions.checkPrivilegedReadPermissionOrCarrierPrivilegePermission(Tele
    at com.android.internal.telephony.TelephonyPermissions.checkCallingOrSelfReadDeviceIdentifiers(TelephonyPermissions.j
    at com.android.internal.telephony.PhoneSubInfoController.callPhoneMethodForPhoneIdWithReadDeviceIdentifiersCheck(Phon
    at com.android.internal.telephony.PhoneSubInfoController.getDeviceIdForPhone(PhoneSubInfoController.java:74)
    at com.android.internal.telephony.PhoneSubInfoController.getDeviceIdWithFeature(PhoneSubInfoController.java:68)
    at com.android.internal.telephony.PhoneSubInfoController.getDeviceId(PhoneSubInfoController.java:64)
    at com.android.internal.telephony.IPhoneSubInfo$Stub.onTransact(IPhoneSubInfo.java:457)
    at android.os.Binder.execTransactInternal(Binder.java:1184)
    at android.os.Binder.execTransact(Binder.java:1143)
```

然后 上面的 d2f34955c580bff6d5c54bf3fb821cda.xlsx 给到的日志2是 :

```
	at android.app.ApplicationPackageManager.getInstalledPackagesAsUser()
	at android.app.ApplicationPackageManager.getInstalledPackages(ApplicationPackageManager.java:735)
```

上述反馈日志 1 是具体可以本地复线的场景,从日志 1 内也能看出缺少关键的 getInstalledPackages 方法,我们已经仔细分析 sdk 内的逻辑,在设置 Main.setConfig("pkglist", "1"); 方法关闭应用列表采集之后,模块内就不会触发 getInstalledPackages 的调用。

还有获取设备已安装应用列表的系统标准 API 是 getInstalledPackages 或 getInstalledApplications。而本次堆栈中触发的是 getApplicationInfoAsUser ,这两个接口有着本质区别:后者仅用于查询单一指定包名的应用信息,完全不具备"获取列表"的功能。

从源码内可以看到触发日志的方法是
```
callingPackageInfo = context.getPackageManager().getApplicationInfoAsUser(callingPackage, 0, UserHandle.getUserHandleForUid(uid));
```
系统传入的参数是 callingPackage(即当前正在运行的 App 自身的包名)。系统调用此接口的唯一目的,是为了在第 442 行核对当前 App 的 targetSdkVersion 是否小于 Android 10 (Q):
```
if (callingPackageInfo != null && (callingPackageInfo.targetSdkVersion < Build.VERSION_CODES.Q))
```


这是 Android 系统底层为了兼容老版本 App 的权限逻辑,系统自身发起的一次"查询当前 App 自身的 targetSdkVersion"的操作,完全不涉及对外部其他应用的扫描,更未触碰任何"应用列表"隐私数据。建议将此源码逻辑反馈给审核机构,以解除自动化工具的误判。


日志对应的源码:

https://cs.android.com/android/platform/superproject/main/+/main:frameworks/base/telephony/common/com/android/internal/telephony/TelephonyPermissions.java;l=417?q=reportAccessDeniedToReadIdentifiers

```
/**
     * Reports a failure when the app with the given pid/uid cannot access the requested identifier.
     *
     * @returns false if the caller is targeting pre-Q and does have the READ_PHONE_STATE
     * permission or carrier privileges.
     * @throws SecurityException if the caller does not meet any of the requirements for the
     *                           requested identifier and is targeting Q or is targeting pre-Q
     *                           and does not have the READ_PHONE_STATE permission or carrier
     *                           privileges.
     */
    private static boolean reportAccessDeniedToReadIdentifiers(Context context, int subId, int pid,
            int uid, String callingPackage, String message) {
        ApplicationInfo callingPackageInfo = null;
        try {
            callingPackageInfo = context.getPackageManager().getApplicationInfoAsUser(
                    callingPackage, 0, UserHandle.getUserHandleForUid(uid));
        } catch (PackageManager.NameNotFoundException e) {
            // If the application info for the calling package could not be found then assume the
            // calling app is a non-preinstalled app to detect any issues with the check
            Log.e(LOG_TAG, "Exception caught obtaining package info for package " + callingPackage,
                    e);
        }
        // The current package should only be reported in StatsLog if it has not previously been
        // reported for the currently invoked device identifier method.
        boolean packageReported = sReportedDeviceIDPackages.containsKey(callingPackage);
        if (!packageReported || !sReportedDeviceIDPackages.get(callingPackage).contains(
                message)) {
            Set invokedMethods;
            if (!packageReported) {
                invokedMethods = new HashSet<String>();
                sReportedDeviceIDPackages.put(callingPackage, invokedMethods);
            } else {
                invokedMethods = sReportedDeviceIDPackages.get(callingPackage);
            }
            invokedMethods.add(message);
            TelephonyCommonStatsLog.write(TelephonyCommonStatsLog.DEVICE_IDENTIFIER_ACCESS_DENIED,
                    callingPackage, message, /* isPreinstalled= */ false, false);
        }
        Log.w(LOG_TAG, "reportAccessDeniedToReadIdentifiers:" + callingPackage + ":" + message + ":"
                + subId);
        // if the target SDK is pre-Q then check if the calling package would have previously
        // had access to device identifiers.
        if (callingPackageInfo != null && (
                callingPackageInfo.targetSdkVersion < Build.VERSION_CODES.Q)) {
            if (context.checkPermission(
                    android.Manifest.permission.READ_PHONE_STATE,
                    pid,
                    uid) == PackageManager.PERMISSION_GRANTED) {
                return false;
            }
            if (checkCarrierPrivilegeForSubId(context, subId)) {
                return false;
            }
        }
        throwSecurityExceptionAsUidDoesNotHaveAccess(message, uid);
        return true;
    }
```
相关推荐
cch89182 小时前
DCATAdmin后台框架极速上手
android
Ehtan_Zheng3 小时前
ActivityMetricsLogger 深度剖析:系统如何追踪启动耗时
android
用户69371750013844 小时前
Android 开发,别只钻技术一亩三分地,也该学点“广度”了
android·前端·后端
唔664 小时前
原生 Android(Kotlin)仅串口「继承架构」完整案例二
android·开发语言·kotlin
一直都在5724 小时前
MySQL索引优化
android·数据库·mysql
代码s贝多芬的音符5 小时前
android mlkit 实现仰卧起坐和俯卧撑识别
android
jwn9996 小时前
Laravel9.x核心特性全解析
android
今天又在写代码7 小时前
数据智能分析平台部署服务器
android·服务器·adb
梦里花开知多少7 小时前
深入谈谈Launcher的启动流程
android·架构