十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

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

Android自动化测试中Launcher桌面空白问题排查与自愈方案

Android自动化测试中Launcher桌面空白问题排查与自愈方案 说实话自动化测试跑到凌晨最怕的不是用例失败而是失败得莫名其妙。有天早晨我例行检查回归报告发现某台测试机的截图非常诡异Android主屏上一个应用图标都没有页面干干净净像是刚恢复出厂设置但底部栏还好好待在原地电话、浏览器、应用抽屉按钮都在。后面几条用例继续正常启动其它应用也没有任何报错。也就是说这个环境从系统层面看完全健康唯独桌面工作区空了。如果你也维护过自动化测试设备池大概率见过类似场面。这个现象在长时间回归、反复reboot、内存高占用、多用例交叉执行的场景下并不罕见。麻烦在于它太隐蔽不崩、不黑屏、不ANR但它会让所有涉及桌面的截图失去参考价值也会让依赖桌面图标坐标的用例集体翻车。这一篇我就把这类问题的完整排查链路、根因分析和自动化框架里的自愈方案全部摊开讲。1. 现场定位三条命令锁定异常边界遇到主屏空白但系统正常的现场第一条原则是先别急着操作设备把证据链捞全。因为在无人值守的自动化环境里设备一重启、一解锁很多现场就没了。以下三条命令是我每次诊断的标准动作正好对应三个层面的信息。1.1 先确认Launcher进程到底死没死想知道问题出在哪个层面第一件事是确认桌面进程是否存活adb shell pidof com.android.launcher3输出为空说明进程已经被回收输出一串数字说明进程还活着。这一步看似简单但它决定了接下来的排查方向进程死了问题大概率是重建不完整或 lmkd杀进程后的恢复竞态进程活着问题就被压缩到Launcher进程内部的渲染/绑定状态里范围小得多。注意不同厂商的桌面包名不一样直接对照查表即可系统/ROM桌面包名AOSP / 原生com.android.launcher3Pixelcom.google.android.apps.nexuslauncherMIUI / HyperOScom.miui.homeEMUI / HarmonyOScom.huawei.android.launcherColorOScom.oplus.launcherOriginOScom.bbk.launcher2三星 One UIcom.sec.android.app.launcher这里有个常见的坑有些ROM里桌面进程虽然活着UI线程却可能已经卡死Handler消息堆积触摸事件根本不响应。所以进程存活只是第一道信号不能只靠它下结论。我习惯紧接着抓窗口状态和Activity栈两相结合判断。1.2 抓窗口状态和Activity栈用两条命令分别看Activity调度和窗口焦点adb shell dumpsys activity activities | grep -iE launcher|mResumedActivity|topResumedActivity adb shell dumpsys window windows | grep -iE launcher|mCurrentFocus|mFocusedApp需要确认两个关键点当前Resumed Activity是不是桌面自身的桌面Activity当前Window焦点是否落在桌面上。如果Activity栈显示桌面在前台但Window焦点不在桌面上说明可能存在一个透明的系统级窗口把它盖住了。这种情况下看起来也是主屏没有应用实际是桌面被遮挡了。后续用例如果直接按坐标点击会点到那个透明窗口的拦截区域出现明明界面正常但怎么点都没反应的诡异现象。再看Launcher侧日志adb logcat -d | grep -iE launcher|workspace|LauncherModel|hotseat重点看问题前后一两分钟的片段。如果日志里能看到bindCellLayout、bindAppsAdded这类回调被调用了、但没有后续的绑定完成记录或者只看到Hotseat相关绑定、没看到Workspace绑定那十有八九是LauncherModel的LoaderTask在中途被打断了。1.3 手动触发一个最小操作观察变化证据捞完可以做一个成本极低的最小干预实验adb shell input keyevent KEYCODE_HOME按一下HOME等两三秒再看桌面。这个操作的原理是让Launcher从后台被拉回前台触发onResume/onStart流程它内部的绑定检查有机会自我修复。很多前台恢复不完整的情况在这一步就能好。如果按HOME仍是空白再触发一次配置变更强制Launcher重建adb shell settings put system user_rotation 1 sleep 2 adb shell settings put system user_rotation 0横竖屏切换会驱动Activity走onDestroy到onCreate的完整重建流程等于让Launcher重新执行一次全量加载。这一步如果恢复了说明问题就在重建链条上如果连重建都不恢复那基本要怀疑是桌面数据库launcher.db里的数据不一致或者更底层的存储访问异常了。把这三个步骤串起来就能快速把异常边界锁定到进程层、渲染层、数据层中的某一层。这个分类法在自动化环境里特别实用因为你不一定有时间当场debug但至少能根据分类决定是直接重启设备还是清桌面数据。2. 主屏空白但底栏正常问题出在Launcher的哪一环快速定位能应急但搞清楚为什么只空主屏、底栏却正常才能让自动化框架做到精准自愈而不是一遇到问题就重启设备或者清数据。2.1 工作区与热键区的加载机制差异Android原生的Launcher是一个典型的MVP结构。LauncherModel负责从LauncherProvider读取桌面数据库launcher.db同时监听应用安装、卸载、更新以及包状态变化的广播维护应用列表和桌面布局数据。UI侧的核心视图就是Workspace——桌面中间那片网格区域所有应用快捷方式和小部件都绑在它的CellLayout里。底部栏的Hotseat本质上也是Workspace里的一个特殊CellLayout数据同样存在launcher.db中只是container字段和普通页面区分开。冷启动或进程重建时Launcher会通过LoaderTask异步执行加载流程。这个任务的顺序大致是先加载并绑定工作区数据其中Hotseat因为绑定顺序靠前会先于主体页面完成填充随后才去绑定所有应用列表、页面指示器等。正常情况下整个流程近乎原子化——要么全部绑上要么触发一次重新加载。问题就藏在近乎两个字里。一旦LoaderTask的执行被外部事件打断比如Activity重建、配置变更、广播风暴、内存被杀底栏已经绑完、工作区主体还没绑完的窗口期被冻结住画面上就会定格成底栏正常、中间空白。2.2 启动其它应用正常帮我们排除了什么排查问题我有一个习惯先圈定不可能再聚焦可能。标题里这个启动其它应用正常是非常关键的线索因为它一口气排除了好几类系统级故障ActivityManager无法调度Activity排除否则其它应用根本起不来WindowManager无法添加窗口排除否则后续用例都会挂在启动阶段SurfaceFlinger和显示合成链路异常排除否则其它应用界面也会花屏、黑屏或者撕裂系统全局卡死、全局ANR排除否则你连adb命令都跑不利索。换句话说整个系统底层都健康问题被压缩到Launcher进程内部的Workspace绑定链路这么一个小范围内。有了这个边界再回头审视自动化测试的操作序列就能直接锁死嫌疑人名单而不是靠玄学去猜。2.3 从源码走向看最容易断掉的那根线缩窄范围之后再看LauncherModel的任务队列机制高危场景其实就那么几类配置变更打断LoaderTask。横竖屏切换、density变化、size变化都会触发Launcher重建。如果旧任务的结果在Activity已经销毁之后才返回绑定回调就可能落在一个失效实例上出现数据到了、视图没补上的中间态。应用广播打断增量更新。Launcher会监听PACKAGE_ADDED、PACKAGE_REMOVED、PACKAGE_CHANGED。批量卸载、批量禁用、静默安装时增量更新任务会不断被插进队列。增量更新和全量加载两个任务在同一个工作线程里排队时一旦发生竞态可能出现某页CellLayout被清空后没有重新bind的后果。数据库数据不一致。桌面的favorites表里如果存在指向已卸载应用的残留记录或者某条记录缺少关键字段Launcher在bind过程中可能会吞掉异常表现为这一页空白、其它页正常。自动化测试经常大量安装卸载应用这个场景比普通用户高发得多。这三种可能都不需要系统级崩溃作为前提。理解了这一点你就能明白为什么手动操作手机上很少遇到、自动化测试设备上却隔三差五出现。3. 自动化测试最常踩中的几处雷前面分析了机制层的可能性这一节把它落到具体的自动化测试操作上按触发概率从高到低排列。3.1 force-stop桌面进程后马上执行下一步很多用例为了净化环境会在启动时顺手杀一下桌面adb shell am force-stop com.android.launcher3问题不在杀桌面本身而在于杀完之后没有任何等待和校验。如果脚本紧接着就按HOME键回桌面找图标或者直接执行下一步UI查找撞上的往往是Launcher还没恢复完的窗口期。尤其是定制ROM桌面冷启动要做的事远比你想象的多读取数据库、加载图标缓存、同步主题、恢复壁纸、甚至拉取云端布局。慢的时候十几秒都有可能。原生ROM倒是快一些但也不保证立即ready。脚本设计和实施时把杀桌面和等桌面恢复中间加一个等待校验是成本最低的修复方式。更稳的做法是改用am kill而不是force-stop前者只杀后台进程、不会清掉进程状态后者是彻底冷启动恢复路径更长。3.2 滑动和手势误触桌面编辑模式移动端自动化里很多操作是直接按坐标执行的。input swipe的起点如果落在桌面空白区域或者测试脚本里模拟双指捏合手势被投到了桌面上就可能触发Launcher的桌面编辑模式。这个模式下工作区中间会变成页面缩略图或者应用移除界面看起来就像是应用图标全部消失了但底部栏通常仍然显示因为退出编辑模式的入口和部分系统功能还保留在那里。这个坑的隐蔽性在于Activity栈并没有变化dumpsys看到的resumed activity还是Launcher但UI树里根本没有应用图标的bounds。如果你用page_source去找某个包名自然找不到于是误报主屏无应用。检测思路是去页面里找编辑模式的特征控件比如完成、设置壁纸、小部件标签这类按钮。判断到之后发送BACK键或者点击完成退出即可恢复。3.3 批量卸载、禁用应用与桌面刷新撞车自动化测试经常要做环境清理pm uninstall、pm disable-user、pm hide都是高频动作。这些动作对系统来说是包状态变更事件Launcher收到广播后要把对应图标摘除并刷新数据库。麻烦在于如果清理动作是一次性批量执行的Launcher会在短时间内收到大量广播增量刷新任务被疯狂插队。加上同一时刻还有其它测试在操作桌面、切换应用Launcher的刷新线程很容易被卡住最终数据库已经更新了但视图没有刷新。此时现象往往是某一行图标消失某一页空白叠加起来最终整体看起来像大片区域没有应用。应对方式很简单批量清理时在两条pm命令之间加间隔最好合并成一条命令执行完再等几秒给Launcher留出消化广播的时间。3.4 显示参数和方向切换带来的重建竞态自动化测试需要适配多分辨率所以wm size、wm density、wm overscan这些命令使用频率很高。这类命令对Launcher来说是致命级配置变更——它必须重新计算网格列数、图标大小、Hotseat宽度整体重建Workspace。如果命令下得太密集比如先改size、马上又改density、紧接着又切横屏Launcher可能连一次重建都没完成就被下一次变更打断。重建不完整时工作区CellLayout的数量、span、id和数据库里的记录对不上图标自然绑不上去。这个场景下Hotseat因为布局结构简单往往能正常重建于是又出现中间空、底栏在的经典画面。在自动化框架里凡是涉及显示参数变更的用例前后都应该有等待配置变更完成的同步点最简单的方式是变完之后轮询dumpsys display或者直接等待固定几秒再继续。3.5 内存压力下的半加载状态这是我在高负载测试机上见过最多次的场景。一台设备上同时挂着被测App、Appium服务、UiAutomator服务、截图器、日志抓取器内存长期紧绷。Android的lmkd低内存杀手会优先杀后台进程Launcher虽然被系统标记为较重要进程但在高压力下同样可能被杀或进入cache状态。接下来的恶性循环很有代表性Launcher被杀脚本按HOME键Launcher重建重建需要读数据库、加载应用列表、构建图标缓存但内存依然不够加载到一半lmkd又杀再按HOME再重建……几次三番折腾下来出现加载到一半被打断的概率极高最终正好就是那个主屏空白、底栏正常的经典状态。这种场景下logcat里能看到连续的、间隔几秒到十几秒的进程死亡记录特征非常明显。根治方法是降低测试机的负载水位而不是反复重启Launcher。4. 给你的自动化框架加保险桌面异常的自动探测与自愈方案排查清楚原因后真正要解决的是怎么让自动化测试不再被这类问题反复打断。我的做法是分三块检测、自愈、追因。4.1 在Teardown里加一个轻量的桌面状态检查建议在每条用例的teardown阶段执行一次桌面健康检查而不是等用例失败再去补救。实现不复杂import subprocess import time LAUNCHER_PACKAGES [ com.android.launcher3, com.miui.home, com.huawei.android.launcher, com.oplus.launcher, com.bbk.launcher2, com.sec.android.app.launcher, ] def adb_shell(cmd): r subprocess.run([adb, shell, cmd], capture_outputTrue, textTrue) return r.stdout def get_top_activity(): out adb_shell(dumpsys activity activities | grep -E mResumedActivity|topResumedActivity) return out def on_launcher(): top get_top_activity() return any(pkg in top for pkg in LAUNCHER_PACKAGES) def workspace_has_icons(): xml adb_shell(uiautomator dump /sdcard/ws.xml cat /sdcard/ws.xml) if not xml or hierarchy not in xml: return False return pkgName in xml调用逻辑是先判断当前顶栈是不是桌面只有确认在桌面时再检查UI树里有没有应用图标节点。两个条件都满足才算健康。不在桌面时不检查避免和正常用例流程打架。重点提醒这个检查一定要轻。不要在teardown里全量解析page_source、不要坐等超时。设备池规模上来之后每台设备多花三秒都是真金白银的执行时长。上面的实现里uiautomator dump有明确的路径参数比直接抓内存里的XML更可控。4.2 分级自愈策略先重启Launcher再清数据检测到异常之后按影响从小到大的次序恢复而不是一上来就清数据恢复级别操作适用场景副作用1input keyevent KEYCODE_HOME等待3秒前台恢复不完整几乎没有2am force-stop launcher_pkg等待3秒再按HOME绑定链路卡死、进程状态异常Launcher冷启动耗时十几秒3pm clear launcher_pkg等待5秒再按HOME桌面数据库损坏、数据不一致桌面布局、壁纸、小部件全部重置谨慎代码骨架大概是这样的def heal_home(launcher_pkg): # Level 1: 按HOME触发前台恢复 adb_shell(input keyevent KEYCODE_HOME) time.sleep(3) if workspace_has_icons(): return 1 # Level 2: 强杀桌面进程冷启动一次 adb_shell(fam force-stop {launcher_pkg}) time.sleep(3) adb_shell(input keyevent KEYCODE_HOME) time.sleep(5) if workspace_has_icons(): return 2 # Level 3: 清桌面数据最后手段 adb_shell(fpm clear {launcher_pkg}) time.sleep(5) adb_shell(input keyevent KEYCODE_HOME) time.sleep(5) if workspace_has_icons(): return 3 return -1要注意的是Level 2里强杀之后不能立刻按HOME。系统从收到force-stop到重新拉起Launcher是异步的按键事件如果在进程还没起来时发出会直接丢失。等待3秒是经验值没有硬性标准但别急着做下一步。Level 3的pm clear会丢失所有桌面布局数据对于正在回归桌面设置类功能的用例绝对不能自动执行最好是只记录告警、通知人工处理。4.3 设备端看护脚本无人值守时的第二道防线如果维护的设备池规模大我建议把桌面健康看护做成设备端常驻守护脚本而不是挂在每条用例的teardown里。这样就算某个用例执行过程中意外退出、崩溃或者有人临时手动调试跳过了框架设备也能靠自己恢复现场。具体做法是通过adb push放一个Shell脚本到设备上后台循环检测当前resumed activity。脚本逻辑不复杂#!/system/bin/sh while true; do top$(dumpsys activity activities | grep -E topResumedActivity | head -1) if echo $top | grep -Eq com.android.launcher3|com.miui.home; then xml$(uiautomator dump /sdcard/ws.xml /dev/null 21 cat /sdcard/ws.xml) if ! echo $xml | grep -q pkgName; then echo $(date) workspace empty, try heal /sdcard/home_heal.log input keyevent KEYCODE_HOME sleep 3 fi fi sleep 10 done这个脚本实测在7x24小时的无人值守执行里价值很大尤其是机房里摆着一排测试机的时候能少跑很多趟。当然它也有代价uiautomator dump每隔几秒跑一次会消耗一点CPU和IO所以脚本里的轮询间隔别太短10秒是平衡之后的选择。4.4 在CI层把偶发现象沉淀成定位线索自愈只是降低损失追根因仍然要靠数据。我会让CI在自愈触发时自动归档三类现场信息问题前60秒的logcatgrep范围覆盖Launcher、ActivityManager、WindowManagerdumpsys activity activities和dumpsys window windows的完整输出自愈前后各一张截图、一份uiautomator dump。打包成以时间戳命名的目录挂到测试报告页面。后续如果同一台设备或同一类用例反复触发就能从归档里发现规律。我遇到过最典型的情况多台设备日志里同时出现某个第三方App反复crash的记录它的崩溃导致内存突增、lmkd开始收割、Launcher背锅真正的元凶其实藏在另一个App里。没有归档数据这种跨应用的因果链根本追不出来。5. 环境漂移治理自动化测试中灵异现象的长期对策每次做一次性修复还不够因为这类问题的本质是环境漂移。自动化测试跑得越久设备环境离干净基线就越远各种灵异现象越是层出不穷。这一节聊聊长期治理思路。5.1 用例隔离每个用例结束都回到标准桌面这是治本的基础没有条件可讲。所有会改变系统状态的用例结束时必须恢复原状。我整理过一个公共的恢复清单放在teardown里统一执行改过wm size、wm density的要改回来改过横竖屏的要回竖屏装过的App要卸载或者至少记录下来禁用的组件要恢复enable状态切换过多用户、工作档案的要回到默认用户。恢复动作统一封装成公共的restore_device_defaults()比每个用例自己手写三段恢复代码可靠得多。恢复完成后顺带执行一次前面说的桌面健康检查作为用例安全退出的前置条件。这样即使某个用例中途崩了也不会把脏环境留给下一个用例。5.2 设备基线管理建议在测试平台里为每台设备记录一份基线状态包括桌面包名、分辨率、密度、默认横竖屏、已安装应用集合。设备漂移不好感知但基线有了之后每次执行任务前做一次增量比对很快能定位是哪台设备、被谁、把哪个状态改了。实操建议是设备进入任务队列前先重启一次重启后等待sys.boot_completed返回1再等Launcher完成首次加载然后才放行用例。这个等待动作看着费时间但能直接砍掉一大批reboot竞态类问题。5.3 从修问题到降概率的转变说实话自动化测试里的环境问题很难做到100%不再出现。更现实的目标是把它的频率从天天打断你降到一个月看不到一次。做法是在监控、自愈、归档的闭环里不断调整策略记录每次自愈的触发频率、时机、设备和用例找出高发场景反过来优化用例执行顺序比如把修改显示参数的用例从重负载场景里挪走把高负载、频繁出问题的设备从主回归流水线里摘掉换到只跑碎片用例减少它对整体结果的影响。我自己的体会是自动化测试最怕的不是死机而是这种看起来一切正常、实际上桌面已经空了的静默问题。它不报错、不影响后续用例的通过率但所有截图都失真时间久了整个测试结论都会失去可信度。所以这类问题的核心价值不在你会不会重启桌面而在于你有没有一套检测-自愈-追溯的闭环。再往深了说我踩过几次之后总结出的原则是面对这类偶发问题先保证自动化框架能看见它再谈修复。你说它表象再简单没有截图、日志和状态归档它就是个玄学有了这些数据它就是一条可以复现、可以定位、最终可以被预防的环境漂移问题。所以先别急着改代码把检测和自愈的链路搭起来让问题自己暴露出来再顺着日志一步步找根因——这条路径比搜到什么就复制粘贴十条江湖命令要靠谱得多。
返回列表