要搞懂这个问题,我们得先拆两个关键: "这两个 adb 命令到底在问 PMS 要什么" 和 "隐藏的安装包到底'藏'在了哪里" 。下面用 "小白能懂的源码逻辑" 一步步讲透。
先铺垫:什么是 "隐藏的安装包"?
不是说应用藏在手机某个文件夹里找不到,而是指:PMS(PackageManagerService,Android 管理应用的 "大管家")里有这个包的 "身份记录",但这个包没有 "可用的 APK 文件" 。
比如常见场景:
- 你手动删了
/data/app/com.myapp
下的 APK,但没执行adb uninstall
(PMS 没来得及删记录); - 应用安装到一半失败了,APK 没了,但 PMS 已经记了 "有个叫 com.myapp 的包";
- 系统隐藏应用(比如通过工具把应用设为 "未启用",APK 被移走但记录留着)。
第一步:搞懂两个命令的 "本质区别"
adb shell pm path
和 adb shell dumpsys package
看似都是 "查应用",但它们向 PMS "要的东西完全不一样"------ 这是结果不同的核心。
我们先把两个命令的 "底层调用链路" 简化(跳过复杂的跨进程通信,只看关键逻辑):
命令 | 对应 PMS 的核心操作 | 核心目的 |
---|---|---|
pm path com.myapp |
调用 PackageManager.getPackageInfo() → 取 ApplicationInfo.sourceDir |
找这个应用的 APK 文件路径(必须是 "能正常读取的 APK 文件") |
dumpsys package com.myapp |
调用 PackageManagerService.dump() → 遍历 PMS 的 "包记录列表" |
把 PMS 里所有关于这个包的 "历史 / 当前记录" 全吐出来(不管 APK 在不在) |
第二步:深入源码逻辑,看 PMS 怎么 "回应" 这两个命令
PMS 是 Android 系统的核心服务,它内部维护着一个 "账本"------ 记录所有和应用相关的信息(包名、APK 路径、权限、数据目录等),这个 "账本" 主要存在两个地方:
- 内存里的
mPackages
集合(所有解析过的包的实时记录); - 磁盘上的
packages.xml
文件(开机时会读这个文件,把历史安装记录加载到mPackages
)。
场景 1:adb shell pm path com.myapp
为什么找不到?
pm path
的目的是 "要 APK 的路径",所以 PMS 会做一个严格的 "有效性检查"------没有可用的 APK,就说 "找不到" 。
我们看它的源码逻辑(简化后,对应 frameworks/base/cmds/pm/src/com/android/commands/pm/Pm.java
):
java
// pm path 命令的核心代码
private void runPath() {
String packageName = mArgs[0];
// 1. 向PMS要"这个包的详细信息(PackageInfo)"
PackageInfo pkgInfo = mPackageManager.getPackageInfo(packageName, 0);
if (pkgInfo == null) {
// 没拿到信息 → 输出"找不到"
System.err.println("package: not found");
return;
}
// 2. 从PackageInfo里拿APK的路径(sourceDir就是APK所在位置)
String apkPath = pkgInfo.applicationInfo.sourceDir;
// 3. 关键检查:APK文件是否真的存在?
if (new File(apkPath).exists()) {
System.out.println("package:" + apkPath);
} else {
// 文件不存在 → 还是输出"找不到"
System.err.println("package: not found");
}
}
翻译 :
pm path
就像你问 PMS:"我要找 com.myapp 的安装包文件,你告诉我它在哪?"
PMS 会先查自己的 "账本"(mPackages
):
-
如果账本里根本没有 com.myapp → 说 "没听过这个包";
-
如果账本里有,但查了一下 "记录的 APK 路径"(比如
/data/app/com.myapp/base.apk
),发现文件没了 → 说 "找不到"; -
只有 "账本有记录,且 APK 文件真的在",才会告诉你路径。
而 "隐藏的安装包" 正好是 "账本有记录,但 APK 没了",所以 pm path
必然找不到。
场景 2:adb shell dumpsys package com.myapp
为什么能找到?
dumpsys package
的目的是 "调试用的信息_dump"------PMS 会把 "所有和这个包相关的记录,不管有用没用,全给你列出来"。它的逻辑完全不关心 "APK 在不在",只关心 "我有没有这个包的记录"。
看它的源码逻辑(简化后,对应 frameworks/base/services/core/java/com/android/server/pm/PackageManagerService.java
):
java
// dumpsys package 命令的核心dump方法
@Override
public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
// 1. 解析参数,找到要查的包名com.myapp
String targetPackage = getTargetPackage(args);
// 2. 从PMS的"账本"(mPackages)里捞这个包的记录
PackageParser.Package pkg = mPackages.get(targetPackage);
if (pkg == null) {
pw.println("No package info for " + targetPackage);
return;
}
// 3. 不管APK在不在,把所有记录全打印出来:
pw.println("Package name: " + pkg.packageName);
pw.println("Previous APK path: " + pkg.applicationInfo.sourceDir); // 哪怕文件没了,也打印路径
pw.println("Data directory: " + pkg.applicationInfo.dataDir); // 数据目录还在就打印
pw.println("Is enabled: " + pkg.applicationInfo.enabled); // 哪怕禁用了也打印
// ... 还有权限、版本号等几十项信息
}
翻译 :
dumpsys package
就像你问 PMS:"把你知道的关于 com.myapp 的所有事,哪怕是陈芝麻烂谷子的记录,都告诉我"。
PMS 只要在 "账本"(mPackages
)里查到这个包名,不管:
-
APK 文件是不是被删了(会打印 "以前的 APK 路径");
-
应用是不是被禁用了(会打印 "Is enabled: false");
-
安装是不是失败了(会打印 "安装状态:未完成");
都会把记录全列出来 ------ 所以 "隐藏的安装包"(有记录没 APK)自然能被找到。
第三步:总结底层原理(一句话说透)
两个命令的 **"查询目的不同"→ 调用 PMS 的接口不同 → 检查的条件不同 **:
-
pm path
是 "要能用的 APK 路径",必须满足 "PMS 有记录 + APK 文件存在",缺一不可; -
dumpsys package
是 "要所有记录",只要 "PMS 有这个包的记录"(不管 APK 在不在),就会显示。
而 "隐藏的安装包" 正好是 "PMS 有记录,但 APK 没了"------ 所以前者找不到,后者能找到。
再补个小实验,帮你彻底理解
-
正常安装 com.myapp:
adb install myapp.apk
; -
执行
adb shell pm path com.myapp
→ 能看到/data/app/com.myapp-xxx/base.apk
(APK 存在); -
手动删 APK:
adb shell rm /data/app/com.myapp-xxx/base.apk
(此时 PMS 还没删记录); -
再执行
adb shell pm path com.myapp
→ 输出 "not found"(APK 没了); -
执行
adb shell dumpsys package com.myapp
→ 能看到包名、以前的 APK 路径(虽然文件没了)、数据目录等信息(PMS 记录还在)。
这就是最直观的验证~