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

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;
    }
```
相关推荐
HLC++19 小时前
Linux的进程间通信
android·linux·服务器
爱笑鱼1 天前
Binder(二):AIDL 生成的 Proxy、Stub 和 Parcel 到底在做什么?
android
爱笑鱼1 天前
Binder(一):一次方法调用,究竟怎样跨进程执行?
android
壮哥_icon1 天前
【Android 系统开发】使用 BAT 脚本高效自动化管理 /system/priv-app/ 系统应用(安装与卸载)
android·运维·自动化
用户69371750013841 天前
Claude Code终端日志Token占用实测:一个过滤器砍掉60%-90%
android·前端·后端
蝉蜕日记1 天前
AccumuPDF高级版 v2.57 | 视图文转换PDF,合并分割压缩
android·智能手机·pdf·生活·软件需求
吐了啊取名字太难1 天前
美颜系统AI修图本地跑并支持Mac、win、安卓、iOS不卡顿
android·人工智能·windows·数码相机·mac·ai编程
阿pin1 天前
Android随笔-Retrofit
android·retrofit
想取一个与众不同的名字好难1 天前
安卓自定义颜色选择器
android
我命由我123451 天前
Android 开发问题:java.lang.NoSuchMethodError:No virtual method readAllBytes()
android·java·java-ee·kotlin·android studio·android-studio·android runtime