ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

adb shell pm命令进阶:包管理与动态权限的自动化测试实战

adb shell pm命令进阶:包管理与动态权限的自动化测试实战 这个系列写到第五期前面把 adb shell 的一些基础命令过了一遍今儿继续聊 pm 命令的第二期。很多人看到“pm 命令第二期”第一反应是pm 不是第一期已经讲完了吗其实不是pm 命令整个体系特别庞大平时大家用得最多的 pm install、pm uninstall、pm list packages 只是入门真正在测试开发里好用的是后面这些pm path、pm dump、pm clear、pm grant/revoke、pm enable/disable每一个都能直接写进自动化脚本。这次的内容适合正在做 App 测试、搞自动化测试脚本的人尤其是需要在命令行里快速控制应用状态、验证应用行为的同学。看完之后你不仅会把 pm 的基础命令用熟还能在权限测试、应用状态隔离、多用户场景这些进阶方向上少走弯路。建议打开一台测试机对着下面的命令一个个敲写脚本的时候直接抄作业。1. 包信息查询的花式玩法list/path/dump 组合拳1.1 pm list packages小参数里藏着大讲究先从一个最常见的场景说起你在写自动化脚本的前置步骤需要判断目标 App 到底装没装。用 list packages 大家都会但很多人只会adb shell pm list packages | grep xxx。这里有个细节直接 grep 没问题但如果你要匹配的是完整包名最好写成pm list packages | grep -w 包名否则包名是另一个包名的子串匹配出来一堆干扰项脚本里的断言直接废掉。除了 greplist packages 自带的几个参数值得搞明白。-f会把 APK 在设备上的完整路径一起打出来格式形如package:/data/app/~~xxx/base.apkcom.xxx。-3只看三方应用-s只看系统应用这两个是互斥的过滤条件别想当然地同时加。-e列出已启用的应用-d列出已禁用的应用这对后面讲 enable/disable 会很有用。还有个-u参数会把卸载但保留了数据的应用也列出来翻旧账的时候特别好用。参数作用典型使用场景-f显示 APK 在设备上的完整路径拿到路径后再 pull 出来分析-3只看三方应用筛选目标测试 App-s只看系统应用预装应用兼容性测试-d / -e列出已禁用 / 已启用应用恢复误禁应用、核对状态-u包含卸载但仍保留数据的包检查残留数据--user N指定用户编号多用户隔离、访客模式多用户场景也得多提一嘴。--user 0指定用户 0主用户--user 10就是工作资料或访客用户。如果你在测双开、多用户隔离的 App不同用户下包是否安装、应用状态是否一致靠pm list packages --user 10一眼就能看清。很多测试同学在 PC 上执行 adb 命令时习惯性忽略当前用户结果在用户 0 上装了包切到访客用户就找不到了排查半天最后发现是多用户隔离问题这类血泪案例我见过不少。顺便再说一句电脑上如果同时跑着多个 adb 服务pm 命令会时不时报 “device offline” 或 “多个版本 adb” 这种让人摸不着头脑的错误。这跟 pm 本身没关系是 adb 服务冲突但会直接影响所有 adb shell 命令。遇到这种问题先adb kill-server再adb start-server把服务统一重启一遍很多玄学问题就消失了。1.2 pm path顺着路径把 APK 掏出来pm path 的用法非常直接pm path 包名。它会返回设备上这个包对应的 APK 路径比如/data/app/~~xxx/com.example.app-xxx/base.apk。在测试开发里这命令最大的价值是配合 adb pull 用adb pull /data/app/....../base.apk ./target.apk把已经安装到设备上的包掏出来做静态分析、比对版本差异、看是否加固或加密。如果你的自动化平台有“抓包”能力可以直接在脚本里先 pm path 拿路径再 pull APK整个流程几秒钟搞定比从应用市场重新下载快得多而且拿到的一定是真机当前实际跑的版本不会出现“本地包和服务端包版本不一致”这种问题。不过 pull 之前要确认权限。普通 shell 用户通常能读路径但未必能 pull 出来部分定制 ROM 会限制访问 /data/app 下的文件。遇到 permission denied可以退而求其次用adb shell cat 路径 本地文件重定向但效率低APK 一大会非常慢不如直接用带 root 的测试机或者预先配置好 pull 权限。经验之谈测试开发手里最好常备一台能 root 的老设备很多 pm、pull 相关的怪问题在这台机器上都能迎刃而解。1.3 pm dump一个命令看清应用的“家底”pm dump 是 pm 家族里信息量最大的命令没有之一。pm dump 包名会把包的版本信息、安装时间、权限声明、权限授予情况、组件信息、进程状态、甚至部分内存统计全部吐出来。信息太多一般不会直接看全量输出而是在后面加管道过滤。比如我想快速拿当前版本号用pm dump 包名 | grep versionName想看某个运行时权限到底有没有授予用pm dump 包名 | grep -A 2 runtime permissions。在权限测试里pm dump 有一段非常关键的输出叫grantedtrue/false它直接反映动态权限的授予状态。跑权限用例的时候不要说点完授权弹窗就算通过用 pm dump 拿到 granted 字段做断言才是实打实的验证。另外 flags 段落里的 PRIVILEGED、SYSTEM 标志位也能帮你判断应用是不是系统特权应用这在排查“为什么这个应用能拿到别的应用拿不到的权限”时特别管用。2. 应用状态管理与数据重置enable/disable/clear2.1 enable/disable/disable-user控制应用的可运行状态测试“弹窗关闭”“应用设置里禁用”这类场景经常要在命令行里模拟用户把应用禁用掉。pm disable 就是干这个的。adb shell pm disable 包名执行后这个包会进入禁用状态系统启动 Activity 都进不去。对应的恢复命令是pm enable 包名。但 pm disable 有个坑它是“彻底禁用”相当于把应用冻住了。可现实是很多测试场景只希望模拟“用户在设置里关闭了应用”。Android 上还有个参数disable-user只针对当前用户禁用系统级配置不动恢复成本更低行为也更接近真实用户操作。如果你的测试设备是主力机千万别随手 pm disable 某个系统关键应用有的定制 ROM 会把禁用后的应用直接列为“已停止”再想恢复还得去设置里翻非常折腾。这个命令配合前面的pm list packages -d是绝配。禁用一批应用后用-d列出被禁用的包核对哪些被误伤再配合-e看哪些还是启用状态排查漏网之鱼。我自己写“一键清理预装应用”类脚本时就会先pm list packages -d备份禁用清单再写 restore 脚本统一pm enable这样即使误禁也能一键还原不用一台台设备手动去设置里捞。2.2 pm clear比卸载重装更优雅的“重置”pm clear 大概是测试开发里使用频率最高的 pm 命令之一adb shell pm clear 包名。它的效果等同于在系统设置里点“清除数据”会删掉应用的/data/data/包名下的私有数据应用回到刚安装完的初始状态。自动化测试用例之间想隔离状态最简单的做法就是 clear 而不是卸载重装。因为卸载再安装耗时长还要处理安装权限、渠道包冲突、授权状态丢失等一堆问题clear 一条命令几十毫秒就搞定效果还干净。几百次用下来我总结了几条 pm clear 容易踩的坑。第一clear 只能清应用私有目录外部存储里应用自己建的文件、媒体库里缓存的索引不一定清得干净。如果用例对缓存路径敏感得先搞清楚应用数据到底存在哪。第二clear 之后应用会丢掉运行时权限比如相机权限、定位权限接下来用例如果涉及权限弹窗要先想好是重新授权还是在脚本里提前 pm grant。第三部分系统应用不支持 clear执行后返回 Failure比如一些厂商预装的系统服务。遇到这种别硬来该查日志查日志该找厂商咨询找咨询。还有个细节pm clear 返回的字符串不一定都是 Success。在自动化脚本里做断言时大家习惯用clear result contains Success去判断但有些 ROM 返回的是英文有些返回码带额外信息最稳的做法是检查退出码 ExitCode 而不是文本内容。你也不要觉得这个细节矫情我见过不止一次团队因为错误地把 Failure 判断成 Success导致后续用例全部白跑最后才定位到是断言写太宽松了。3. 权限动态管理grant/revoke 与测试自动化3.1 grant 与 revoke绕开权限弹窗的正确姿势Android 6.0 之后运行时权限成了重灾区自动化测试里最讨厌的就是系统弹权限框一弹就挡住后续操作。pm grant 可以把权限直接在命令行里授掉pm grant 包名 android.permission.CAMERA。授予后应用再启动就不会弹窗口了用例流程能顺畅往下走。同理pm revoke 包名 android.permission.CAMERA把权限收回用来模拟用户拒绝授权的场景。使用时有三个前提一是目标权限必须是应用声明了的二是不是所有权限都能 grant/revoke只有运行时权限组里的才行三是有部分特殊权限比如通知、悬浮窗光靠 grant/revoke 是不行的得用 appops。很多同学上来就跑pm grant com.xxx android.permission.SYSTEM_ALERT_WINDOW结果报Operation not allowed不是命令写错是这条路根本就走不通。还有个实用参数是--user。pm grant --user 10 包名 权限可以在指定用户下授权。多用户或多开环境里只授予当前用户权限另一个用户不动避免无关用户也被污染。我之前测支付类 App 的权限继承逻辑就是靠 grant/revoke 加不同 user 切换来构造权限状态矩阵的比在 UI 上一层层点设置快太多了。3.2 权限状态验证与“隐藏”权限工具授予完权限验证也是关键。两种方式一种是pm dump 包名 | grep granted查 runtime permissions 的授权状态另一种是cmd appops get 包名来查权限操作级别。这两个容易混分开说清楚pm grant 管的是运行时权限的“是否授予”appops 管的是某个权限操作的“运行策略”比如 allow、deny、ignore前者适合做权限弹窗测试后者适合做隐私合规里的细粒度控制测试。实际测试中 appops 的价值非常大。比如要做“应用在后台被禁止定位”“麦克风权限被悄悄收回”这类用例pm grant/revoke 控制不了必须靠appops set 包名 android:fine_location deny这类操作。appops 和 pm grant 之间也存在联动有些权限被 appops 设成 deny 后pm dump 里 granted 字段可能仍然是 true但实际拿不到数据。写断言时别只看 granted要结合 appops get 的当前 ops 值一起判断不然很容易把“已授权但被隔离”的用例判成通过。4. 测试场景里的组合套路从单条命令到脚本4.1 场景一重置登录状态并验证版本最常用的组合是pm clear pm path pm dump。比如做登录态相关用例前置步骤里先pm clear 包名再用pm dump 包名 | grep versionName拿到当前版本号写入测试报告。一条 clear 命令把应用恢复到未登录状态一条 dump 命令确认版本比在 UI 上反复点“退出登录”再进设置看版本号高效得多。脚本里要注意的是给 pm 命令预留足够的超时时间不要默认 5 秒设备 IO 紧张时 pm dump 有概率跑到十几秒。我一般把 adb 执行器的 timeout 设成 30 秒这样既不影响总体效率又不会因为个别慢命令导致整个用例误报超时。4.2 场景二禁用预装应用并做恢复备份厂商预装应用测试或者做应用兼容性回归经常要禁一批系统 App。脚本逻辑可以这样写先pm list packages -3 -e拿到启用中的三方应用列表筛出要禁的应用逐个pm disable-user然后pm list packages -d生成禁用清单测试结束后再遍历清单pm enable恢复。这套流程既能照顾好主用户的体验又能自动还原。唯一要注意的是别把桌面、输入法这些基础组件给禁掉否则后续所有自动化操作都白搭。我自己会在禁用名单里加一个白名单校验目标包名出现在系统关键组件列表里就直接跳过宁可用例漏测也不能把主力测试机搞成砖。4.3 场景三权限矩阵构造与用例闭环构造权限用例矩阵时可以用 grant/revoke 结合pm dump | grep granted做“授予-验证-撤销-验证”的闭环。比如定位权限用例 1 前置 grant 定位权限断言 grantedtrue用例 2 前置 revoke断言 grantedfalse用例 3 前置 appops deny再断言业务层拿不到位置信息。这套矩阵能覆盖权限弹窗、权限拒绝、权限隔离三种核心场景。写脚本时建议把这些 pm 命令封装成函数库输入包名和权限名返回当前权限状态方便多套用例复用。封装的时候注意命令执行失败的情况也要返回结构化错误比如 SecurityException、包不存在的报错这些在跑大批量用例时非常常见不要让一条异常的 pm 命令把整个测试任务打挂。4.4 场景四查询入口 Activity 与测试工具还有两个容易被忽略的 pm 子命令。pm resolve-activity --brief 包名可以拿到应用入口 Activity 的完整组件名在 UI 自动化里定位 launcher 场景时非常有用省去了去 AndroidManifest 里翻找的功夫。pm list instrumentation则列出设备上已安装的测试插桩比如 UIAutomator 的 runner。跑插桩测试前先确认 instrumentation 有没有能少踩“设备上工具没装导致测试直接失败”的坑。另外pm query-activities系列在处理“某个 intent 能拉起哪些 Activity”的时候也很有用尤其是做深链跳转测试想快速验证一个 URL scheme 对应的 Activity 是否在当前版本还存在一条 query 命令比打开 App 去点十几次页面快得多。5. 实测踩坑记录pm 命令使用中的常见问题排查5.1 shell 权限不足各种 Operation not allowedpm 命令不是所有子命令都能在普通 shell 权限下执行的。最典型的是pm disable面对系统应用以及某些厂商 ROM 对pm grant做了限制控制台直接抛java.lang.SecurityException: Permission Denial。这种问题的判断思路是先看清楚是对哪个包、哪个子命令报的权限错误然后换真 root 设备先adb root再执行试试如果 root 也不行大概率是 ROM 策略或 SELinux 限制得走系统层面解决别在权限不足的机器上死磕。还有一部分是“可调试应用限制”。比如用run-as 包名 ls查看应用私有目录时经常报Package com.xxx is not debuggable这不一定是你包拿错了而是应用本身没有开调试开关。这种场景下你需要的是 debuggable 版本的包或者在 root 设备上绕过 run-as 直接看目录。5.2 clear 后应用没反应“假清空”pm clear 返回 Success 但应用登录态还没掉这种“假清空”多半是数据没存在 /data/data 下。很多应用会把 token 存到外部存储、SharedPreferences 之外的目录甚至服务器端。遇到这种直接在 clear 前用pm path验证包路径clear 后再用run-as 包名 ls检查残留数据目录只适用于 debuggable 应用逐步定位数据的真实存放位置。有些 App 还会做双进程保活clear 后后台进程又自动拉起恢复了一部分缓存这种要想彻底隔离得连应用进程一起杀掉正确的顺序是am force-stop 包名先停进程再 clear。顺序不能反先 clear 再 force-stop 的话进程被杀前可能又把部分状态写回去了。5.3 命令执行慢、超时、结果解析不稳定自动化平台跑 pm 命令时最烦的不是命令报错而是超时。pm dump、pm list packages 这类命令在包很多或设备 IO 卡顿时返回可能要十几秒。写脚本时要注意三点一是超时时间别设太短至少 30 秒二是对比较长的输出用| head -n或精确 grep 截取关键行减少管道传输量三是解析返回值时优先看 ExitCode 而不是 stdout 的文本 Success因为不同系统、不同版本返回的文本有差异。还有一个冷门坑某些设备上pm list packages的输出编码不是 UTF-8加管道处理时容易乱码稳妥起见可以先adb shell pm list packages 本地文件再在 PC 端解析减少字符集问题。5.4 厂商 ROM 魔改导致行为不一致最后说一下厂商 ROM 对 pm 命令的魔改问题。不同厂商对 pm 的底层实现都有改动尤其是 disable、clear、grant 这几个子命令。同一套脚本在 A 厂商设备上通过B 厂商设备上可能就报错或者行为不同。我的建议是所有涉及 pm 的脚本都做“能力探测”先跑一条pm help或pm list packages -d干跑一遍确认当前设备在该命令上行为符合预期再进入正式流程。用一套脚本跑全量市场设备之前至少先把主流 ROM 都过一遍别让“脚本能在真机上跑”变成“脚本只能在某台真机上跑”。我正在维护的测试框架里就专门维护了一个设备能力表哪台设备支持什么、不支持什么全量记录下来pm 命令执行前先查表能省掉一大半的兼容性排查时间。这个系列到这儿pm 命令第二期基本就聊完了。我做安卓测试开发这几年pm 命令算是渗透进日常脚本最多的 adb 子命令之一从最开始的查包名、装卸载到后来搞 clear、grant、appops每深入一层自动化脚本的稳定性和效率都会明显上一个台阶。特别是动态权限和状态隔离这两块用命令行的思路去设计用例真的比在 UI 上一步步点要省心太多。下一篇我打算接着把 adb shell 里另一些高频命令比如 wm、input、screencap 这些再串一遍等更完之后这块知识体系差不多就完整了。
返回列表