安卓日志这块,我刚入行那会儿是真吃过亏。一个偶现的崩溃,测试同学只丢过来一句“打开某个页面就闪退”,没有设备、没有时间点、没有任何上下文,我和另一个开发对着代码猜了一下午,最后发现是推送 SDK 在某款定制 ROM 上初始化顺序出了问题。从那以后我就养成习惯:只要涉及问题定位,第一件事不是看代码,而是先把日志拿下来。标题里说的“安卓设备 App 查看抓取日志的方式以及测试时常用的工具”,本质就是一套完整的“取证”能力——你能不能在问题发生的那一刻,把系统、应用、网络、性能这几路证据完整截留下来。
这套东西的受众其实比想象中广。开发要它来定位崩溃和 ANR,测试要它来出具缺陷报告,运维和客诉支持要它来还原用户现场,连产品同学做竞品分析时也常需要看别人 App 的运行轨迹。工具门槛不高,真正拉开差距的是细节:logcat 的缓冲区怎么调、高版本安卓为什么读不到别人的日志、ANR trace 去哪找、Monkey 跑出来的日志怎么筛。下面我按自己的实际工作流,把这几块从头到尾捋一遍,能直接抄的就抄,踩过的坑我也一并标出来。
1. 先搞清楚安卓日志到底分几条道
1.1 logcat 只是主路,不是全部
很多人一说抓日志,脑子里只有adb logcat,其实安卓的日志体系是分层的,不同问题得去不同的管道里找。我习惯把这套东西分成五类。
第一类是logcat,也就是应用层和框架层打出来的那堆文本,Java 代码里的Log.d()、系统 ActivityManager 的启动记录、输入法状态变化,全在这儿。它是日常用得最多的,但它有个天然缺陷:环形缓冲区,写满了就覆盖旧的。
第二类是dmesg / kernel log,内核态的输出。像驱动加载失败、内存分配异常、部分 native 崩溃的上下文,会出现在这里。以前adb shell dmesg很常用,新版本安卓因为权限收紧,普通用户读不全,一般靠adb shell dmesg加 root 或者从 bugreport 里扒。
第三类是ANR traces,应用主线程卡死超过阈值时系统 dump 出来的线程堆栈,路径在/data/anr/。这是定位卡顿和死锁的核心证据,后面会单独讲。
第四类是tombstone,native 层崩溃(比如 so 库挂掉)时生成的记录,在/data/tombstones/,里面有完整的寄存器状态和调用栈。做 JNI 或者接第三方 so 的同学,这东西必须会看。
第五类是dropbox,系统的一个“事件档案馆”。ANR、崩溃、系统重启、电池异常这些事件,会被系统摘要性地存进去,用dumpsys dropbox能列出来。它的价值在于——logcat 已经被冲掉的时候,dropbox 里可能还留着一条摘要。
提示:排查“偶现且无法复现”的问题时,优先去 dropbox 和 ANR 目录翻,那里留存的东西比实时 logcat 靠谱得多。
1.2 日志级别和缓冲区,是两个必须先懂的概念
日志级别从小到大是V(Verbose)、D(Debug)、I(Info)、W(Warn)、E(Error)、F(Fatal),最后还有一个S(Silent),它不是级别而是“静默”的意思,专门用来做过滤器。理解这点很关键,因为 logcat 的过滤语法是“标签:级别”,比如MyTag:D表示只看 MyTag 这个标签的 Debug 及以上。而*:S是万能静默,意思是“其他所有标签全部闭嘴”。
缓冲区是另一个新手常踩的坑。logcat 不是单独一块内存,而是分成几个命名缓冲区:main(应用日志)、system(系统框架)、crash(崩溃)、events(结构化事件)、radio(通信相关)。默认每个缓冲区大概 256KB 左右,应用日志一多,几秒钟就滚没了。所以排查问题时我一般会先干两件事:清空旧日志adb logcat -c,然后临时把缓冲区调大adb logcat -G 16M。调大之后缓冲区是内存占用,重启就恢复默认,不用担心留后遗症。
1.3 什么场景抓什么日志,先建立一张对照表
我把常见问题和对口的日志源整理成了一张表,遇到问题先对号入座,能省掉大量无效尝试。
| 问题现象 | 优先看的日志源 | 关键命令 |
|---|---|---|
| 应用闪退(Java 层) | logcat crash 缓冲区 | adb logcat -b crash |
| 应用无响应、卡死 | ANR traces + logcat | adb shell ls /data/anr/ |
| native 崩溃、so 报错 | tombstone + logcat | adb pull /data/tombstones/ |
| 系统级异常、重启 | dropbox | adb shell dumpsys dropbox --print |
| 冷启动慢、页面卡顿 | Perfetto / Systrace | adb shell perfetto -c - --txt |
| 网络请求失败 | 抓包工具 + logcat | 见第 4 章 |
| 内存持续增长 | Profiler / LeakCanary | Android Studio Profiler |
| 兼容性问题(特定机型) | logcat 全量 + dmesg | adb logcat -b all |
这张表我一般会贴给团队新人,让他们先学会“问对问题”,而不是一上来就adb logcat全量输出,然后被几万行刷屏刷到怀疑人生。
2. adb logcat 抓日志的完整实操
2.1 环境准备与设备连接
前提是你得有一台装了 adb 的电脑。最省事的方式是装 Android Studio,它自带 platform-tools。不装 Studio 的话,单独下载 platform-tools 压缩包解压,把目录加到系统 PATH 里就行。验证一下:
adb version adb devicesadb devices能列出设备序列号和状态才算通。状态有几种:device是正常,unauthorized是设备上还没点“允许 USB 调试”,offline通常是连接不稳或者 adb 服务卡了。遇到后两种,先看手机屏幕上有没有授权弹窗,没有就拔插一次数据线,再不行执行:
adb kill-server adb start-server adb devicesWindows 上还有一个经典坑:设备管理器里驱动带感叹号。大部分国产机型装个通用 ADB 驱动能解决,或者去厂商开发者官网下对应驱动。这一步不通,后面所有操作都是空谈。
2.2 logcat 的核心参数逐个拆解
adb logcat的参数看着多,实际高频的就那么几个。我把最常用的整理如下。
-v控制输出格式,默认是 brief,信息太少。生产环境我几乎只用threadtime,它包含日期、时间、PID、TID、级别、标签,是排查问题的黄金格式。
adb logcat -v threadtime-b指定缓冲区,可以叠加,比如同时看主日志和崩溃日志:
adb logcat -b main -b crash -v threadtime想看全部就-b all。-c是清空,建议在复现问题前先清一次,避免旧日志干扰。-d是把当前缓冲区的日志 dump 出来然后退出,适合脚本化抓取,不会一直挂着。-t 500是只输出最近 500 行,用来快速看现场。-G设置缓冲区大小,前面说过了。
--pid是精准过滤单个进程的利器,需要 Android 7.0 以上:
adb shell pidof com.example.app adb logcat --pid=12345 -v threadtime如果pidof没输出(有些机型裁剪了),用adb shell ps -A | grep com.example.app替代。
2.3 过滤表达式:把噪音砍掉九成
不做过滤直接看 logcat,等于把耳朵贴在高速路口听人说话。过滤有两种思路。
一种是按标签过滤,用-s或者显式的过滤表达式。-s相当于“只显示这些标签”:
adb logcat -s MyApp:V OkHttp:V等价于MyApp:V OkHttp:V *:S。这里的*:S把其他所有标签静默掉,非常干净。
另一种是用 grep 二次过滤。这种方式更灵活,适合关键词、异常类名、包名搜索。Linux/macOS 下:
adb logcat -v threadtime | grep -E "Exception|FATAL|ANR"Windows 的 cmd 没有 grep,用 findstr:
adb logcat -v threadtime | findstr /I "Exception FATAL ANR"我的习惯是先粗后细:第一遍不加过滤,全量落盘到一个文件;第二遍在文件上做各种 grep,反复搜关键词。因为实时过滤的时候你永远不知道下一秒哪个标签会冒出来,漏掉关键行就白抓了。
2.4 把日志落盘:脚本化和轮转
排查偶现问题最大的痛点是“日志滚没了”。所以正式抓取时,我一定是落盘而不是盯着屏幕看。最基础的一条:
adb logcat -v threadtime > run.log但这样有个问题:文件会一直涨,长时间跑测试能到几个 G。我一般写个小脚本做按大小切分,或者用-t配合定时任务。下面这个 bash 片段是我常用的简化版,每 50MB 切一次:
#!/bin/bash DIR=./logs mkdir -p "$DIR" i=0 while true; do adb logcat -v threadtime > "$DIR/logcat_$i.log" & PID=$! # 监控文件大小,超过阈值就重启抓取 while kill -0 $PID 2>/dev/null; do SIZE=$(stat -c%s "$DIR/logcat_$i.log" 2>/dev/null || echo 0) if [ "$SIZE" -gt 52428800 ]; then kill $PID break fi sleep 5 done i=$((i+1)) doneWindows 下用 PowerShell 也能写类似的循环,思路一样。关键是分段保存 + 记录时间点,这样复现问题后能快速定位到对应时间段。
提示:抓取前先确认设备和电脑的时间同步,否则日志时间戳和你的操作记录对不上,排查时会非常痛苦。手机一般在设置里能手动校准,或者用 NTP 对齐。
2.5 无线调试与多设备场景
USB 连着的限制挺多,尤其是要拿着手机到处走动做真机测试时。Android 11 以上支持无线调试配对,进入开发者选项里的“无线调试”,选“使用配对码配对设备”:
adb pair 192.168.1.100:37000 # 输入配对码 adb connect 192.168.1.100:5555更老的方式是先 USB 连上,然后adb tcpip 5555再adb connect 设备IP:5555。注意这种方式重启设备就失效,要重新来一遍。
同时连了多台设备时,所有命令都要加-s指定序列号,否则 adb 会直接报错“more than one device”:
adb -s emulator-5554 logcat -v threadtime adb -s ABC123456 logcat -b crash3. 不接电脑,设备端也能抓日志
3.1 开发者选项里的错误报告
不是所有场景都能架着电脑连 adb。比如你在地铁上遇到一个闪退,或者在客户现场做演示。这时候最实用的是系统自带的**错误报告(Bug Report)**功能,开发者选项里能直接点,部分机型长按电源键组合也能触发。
它生成的是一个 zip 包,里面含 logcat 全量、dumpsys 各项快照、ANR trace、dropbox 记录,内容相当完整。缺点是体积大(几十到几百 MB)、生成慢(要跑一两分钟)。生成后可以分享导出,再拿到电脑上慢慢拆。
如果手边有 adb,等价的命令更直接:
adb bugreport ./bugreport.zip这个 zip 解压后重点看bugreport-xxx.txt,里面按章节分了SYSTEM LOG、EVENT LOG、ANR、DUMPSYS等,搜索类名或者关键字非常高效。
3.2 第三方日志 App 和高版本的权限墙
应用市场里有一批日志查看工具,早年比较出名的像 MatLog、Logcat Reader 这类,它们本质是调用系统的日志接口。但这里有个非常关键的分水岭:从 Android 10(API 29)开始,普通应用无法读取其他应用的日志了。也就是说,这些第三方 App 现在基本只能看到它自己和少量公开的系统日志,想抓你目标 App 的日志,除非设备 root。
这个限制很多人不知道,折腾半天以为是工具不好用,其实是系统从权限层面就堵死了。READ_LOGS这个权限现在只授给系统应用和签名为 signature 的应用。
所以结论很明确:设备端第三方 App 抓日志,只适合看自己开发的应用(同一签名),要抓别人或者做全面测试,还是老老实实上 adb。
3.3 root 设备的额外玩法与风险
root 之后确实能解锁很多能力,比如直接adb shell su -c "logcat"读全部日志、直接 pull/data/anr/和/data/tombstones/、用特殊工具持久化抓取。
但我得泼盆冷水:root 会带来三方面代价。一是安全性下降,权限完全开放后恶意应用更容易获得高权限;二是部分应用会主动拒绝在 root 环境运行(检测到 root 直接退出或限制功能),金融类、部分游戏尤其明显;三是系统 OTA 和安全补丁升级会变麻烦。
我的建议是:日常测试用一台专门的测试机,尽量用 adb + bugreport 这套非侵入式方案。只有当问题只在特定内核或 native 层复现、必须要看受保护目录时,才考虑 root 机器,并且把它隔离成专用的取证设备。
4. 测试时常用的工具清单,按用途分组
4.1 基础调试三件套:Logcat、Profiler、Layout Inspector
Android Studio自带的 Logcat 窗口是我最常用的。相比命令行,它有几个优势:按包名自动分组、支持正则过滤、可以直接跳转崩溃堆栈对应代码行、能把多设备日志合并显示。唯一的缺点是吃内存,长时间挂着会卡,所以我会定期清理窗口。
Profiler是性能排查的主力,CPU、内存、网络、电量四个维度一条时间轴。它的用法是“先看形状,再看细节”:比如 CPU 曲线在某个操作后一直不下来,说明有耗时任务没释放;内存曲线呈锯齿状上升,大概率是泄漏。定位到时间段后,再点进去看具体调用栈。
Layout Inspector用来查界面问题,能实时看到 View 树、层级、每个控件的属性。遇到“某按钮点了没反应”这种,先确认它有没有被上层透明 View 挡住,这个工具一眼就能看出来。
4.2 UI 自动化:Monkey 到 Maestro 的梯度
Monkey是系统自带的随机事件压测工具,零门槛,一条命令就能跑:
adb shell monkey -p com.example.app -v -v -v --throttle 300 \ --pct-touch 40 --pct-motion 25 --pct-nav 15 \ --ignore-crashes --ignore-timeouts -s 20240601 10000参数里-p指定包名,-v -v -v是最高详细级别,--throttle 300是事件间隔毫秒数,--pct-*控制各类事件占比,-s是随机种子(固定种子可复现),最后的数字是事件总数。关键点在于一定要重定向保存日志:
adb shell monkey -p com.example.app -v 10000 2>&1 | tee monkey.log不然崩溃信息一闪而过,根本来不及看。--ignore-crashes和--ignore-timeouts让 Monkey 遇到崩溃继续跑而不是停下,适合长时间稳定性测试。
UiAutomator / Espresso是写固定用例的框架,适合回归测试。UiAutomator 跨应用,Espresso 专注单应用内,速度快但隔离性强。
Appium走 WebDriver 协议,一套用例可以覆盖安卓和 iOS,适合团队已经有 Web 自动化积累的情况。装起来稍麻烦:
npm i -g appium appium driver install uiautomator2Maestro是这两年比较火的轻量方案,用 YAML 描述流程,上手极快,特别适合冒烟测试。一个典型流程长这样:
appId: com.example.app --- - launchApp - tapOn: "登录" - inputText: "13800000000" - tapOn: "获取验证码" - assertVisible: "请输入验证码"它底层调 adb,所以跑之前设备必须是 adb 可见状态。选择哪个没有绝对答案:探索性压测用 Monkey,固定回归用 UiAutomator/Espresso,跨平台用 Appium,快速冒烟用 Maestro。
4.3 性能与内存:Perfetto、Systrace、LeakCanary
Perfetto是现在安卓性能抓取的主流方案,取代了老的 systrace。它采集的信息非常全:CPU 调度、频率、进程线程、Binder 调用、帧渲染。抓取命令:
adb shell perfetto -c - --txt -o /data/misc/perfetto-traces/trace.pftrace <<EOF buffers: { size_kb: 65536 } data_sources: { config { name: "linux.ftrace" } } duration_ms: 10000 EOF adb pull /data/misc/perfetto-traces/trace.pftrace生成的 trace 文件拖到浏览器里的 Perfetto UI 打开,能看到完整的时间轴。看卡顿的核心是找主线程上过长的连续块,超过 16ms 就可能掉帧,超过几百毫秒用户就能感觉到明显卡顿。
LeakCanary是内存泄漏检测神器,集成到 debug 包里,它会自动监控 Activity、Fragment 的销毁,发现泄漏后 dump 堆内存并给出引用链。它的报告非常直观——直接告诉你“谁持有了这个本该被回收的对象”。
内存快照(hprof)是兜底手段。用 Profiler 抓一份堆转储,在 Android Studio 或者 MAT 里分析。这个适合 LeakCanary 覆盖不到的场景,比如大对象长期驻留。
4.4 网络抓包与高版本证书难题
看网络请求,工具选择挺多。电脑端主流是Charles和mitmproxy,也有Fiddler;设备端有HttpCanary这类。核心原理都是让流量经过工具监听,然后解密展示。
这里有个绕不开的坎:Android 7.0 之后,应用默认不再信任用户手动安装的 CA 证书。也就是说,你把抓包工具的证书装到手机里,系统层面信任了,但目标应用自己不一定买账,很多请求会直接失败或者显示为加密的乱码。
破解思路有这么几条,按侵入性排序:
- 应用自身配置了 network security config 并允许用户证书——这种最好办,直接装证书就行,但多见于开发调试包。
- 改包重签,在应用里注入信任用户证书的配置。这个只适合自家应用或者有授权的测试对象,改别人的包涉及法律和合规风险,别碰。
- 在系统层面把证书装到系统证书分区,需要 root,前面说过 root 的代价。
- 用设备端工具直接抓,部分工具能在不依赖系统信任链的情况下抓 HTTPS,但兼容性和可靠性因版本而异。
注意:抓包涉及数据隐私,务必只对自己拥有授权或自己开发的应用操作,抓到的数据要妥善保管、按规范脱敏和销毁。这条底线不能越。
4.5 线上崩溃收集:Crashlytics、Bugly、Sentry
本地抓日志解决的是测试阶段的问题,但真正的崩溃大头在线上。这块得靠崩溃收集平台。Google 的Firebase Crashlytics生态成熟,和 Android Studio 集成顺滑;国内的Bugly对国产机型和符号表处理友好;Sentry强在自建和多语言支持;友盟类方案胜在接入简单。
选型我一般看三点:符号表上传是否自动化、聚合去重是否准确、能否定位到具体版本和设备。其中符号表是重中之重,尤其是 native 崩溃,没有符号表解出来的堆栈全是地址,毫无意义。建议把符号表上传直接挂在 CI 流程里,每次构建自动传,别指望人工记得。
5. 系统级日志深挖:ANR 和 native 崩溃怎么读
5.1 ANR traces 的正确打开方式
ANR 也就是“应用无响应”,系统弹窗“等待 / 关闭”那个。触发它的原因通常有四类:主线程做耗时 IO、主线程等待锁、BroadcastReceiver 超时、Service 启动超时。
现场证据在/data/anr/目录。老版本只有一个traces.txt,Android 11 之后是一堆anr_日期时间命名的文件。非 root 读不全,但可以通过adb bugreport打包取出。
拿到之后怎么看?核心是找主线程(通常叫 main)的堆栈,看它卡在哪一行。常见的几种形态:
- 卡在
ScheduledFutureTask或者MessageQueue.nativePollOnce,这往往是正常的空闲等待,不代表问题。 - 卡在
Thread.sleep、Socket.read、文件读写,说明主线程在做阻塞操作。 - 卡在
synchronized或者Object.wait,说明在等锁,要去找是谁长期持有那把锁。
我一般会在 trace 里搜两遍:先搜"main"看主线程,再搜"held by"看有没有显式的锁竞争。ANR trace 的价值在于它是全进程所有线程的快照,能还原“谁在等谁”的完整关系。
5.2 native 崩溃与 tombstone
Java 崩溃你能看到清晰堆栈,native 崩溃(so 库崩溃)就没这么友好了。系统会在/data/tombstones/下生成 tombstone 文件,里面记录了信号类型、崩溃地址、寄存器状态和调用栈。
其中signal 11 (SIGSEGV)是最常见的,一般是空指针或者野指针;signal 6 (SIGABRT)多半是主动 abort,可能是断言失败或者 C++ 未捕获异常。
要看懂 tombstone 得有对应版本的符号表,用工具做地址还原,否则那一堆十六进制地址根本没法读。这也是为什么我一直强调符号表要归档——每出一个版本就存一份,出问题时才能对应上。
6. 常见问题与排查技巧实录
6.1 高频问题速查表
我把这些年被问得最多的问题整理成了表,遇到先查这里。
| 现象 | 可能原因 | 解决办法 |
|---|---|---|
adb devices显示 unauthorized | 设备未授权调试 | 手机上点“允许”,勾选“始终允许” |
| 显示 offline | adb 服务异常或线材问题 | adb kill-server后重启,换数据线 |
| 抓不到目标应用日志 | Android 10+ 权限限制 | 改用 adb,或确认读取方是系统应用 |
| 日志刷太快看不到关键行 | 缓冲区小、输出量大 | -G 16M扩大,同时落盘到文件 |
| 中文显示乱码 | 终端编码不匹配 | Windows 执行chcp 65001,或直接输出到文件 |
| 崩溃日志缺失 | 缓冲区被覆盖 | 用-b crash单独抓,或用 bugreport |
| Monkey 跑一半停了 | 未忽略崩溃超时 | 加--ignore-crashes --ignore-timeouts |
| 抓包 HTTPS 全是乱码 | 用户证书不被信任 | 见 4.4 节,优先用调试包或系统级证书 |
| 多设备命令报错 | 未指定设备 | 所有命令加-s 序列号 |
| 日志时间对不上操作 | 设备与电脑时间不同步 | 校准时间,或记录时间偏移量 |
6.2 几个只有踩过才知道的细节
第一,崩溃不一定出现在 crash 缓冲区。有时候 Java 崩溃因为进程被杀得太快,日志还没 flush 到 crash 里就没了。这种时候 dropbox 或者 bugreport 里的SYSTEM LOG反而可能有残留。所以抓崩溃我一般是-b all全量落盘,而不是只盯着一个缓冲区。
第二,logcat -c之后立刻抓,第一行可能是空的。因为它清空需要一点时间,脚本化抓取时最好清空后 sleep 一秒再开始,避免开头丢日志。
第三,过滤表达式里标签区分大小写。MyApp:D和myapp:D是两个不同的过滤器,写错了就一条都出不来,很容易误判成“应用没打日志”。
第四,高版本安卓的日志权限墙不止影响第三方 App,也影响一些自动化框架。某些工具在新系统上跑不出来日志,不一定是工具坏了,而是系统策略变了。
第五,别迷信“日志越全越好”。有一次同事给我传了个 800MB 的全量日志,我打开一是没有时间标记,二是不知道他做了什么操作。后来我要求团队提交缺陷时必须附上:问题时间段、复现步骤、当时的操作时间点。日志是证据,但证据得有上下文才有价值。
6.3 建立自己的抓取模板
折腾久了,我把常用抓取动作固化成了一个脚本,命名为grab.sh,参数是包名:
#!/bin/bash PKG=$1 TS=$(date +%Y%m%d_%H%M%S) DIR="./catch_$PKG_$TS" mkdir -p "$DIR" # 调整缓冲区并清空 adb shell logcat -G 16M adb shell logcat -c # 全量日志后台落盘 adb logcat -b all -v threadtime > "$DIR/logcat_all.log" & echo $! > "$DIR/logcat.pid" # 抓取进程信息 adb shell ps -A > "$DIR/ps.txt" adb shell dumpsys activity > "$DIR/dumpsys_activity.txt" echo "抓取已启动,目录:$DIR" echo "停止请执行:kill $(cat $DIR/logcat.pid)"这个脚本我几乎每天都在用,简单但极其实用。它的价值在于把“记得做哪些事”变成“自动做哪些事”,避免临时手忙脚乱漏掉关键信息。
7. 我个人的一点实操体会
摸索这套流程的过程中,我最大的感受是:抓日志的能力,本质上反映的是你对系统运行机制的理解深度。同样一个崩溃,新手只知道去 logcat 里搜“Exception”,老手会先想“这是 Java 层还是 native 层、是主线程还是子线程、是必现还是偶现”,然后决定去哪个缓冲区、哪个目录、用哪个工具。
另一个体会是要把抓取动作前置。等你发现问题的那个瞬间再开始抓,往往已经晚了——缓冲区被冲掉、现场被覆盖。所以我的习惯是:做任何测试之前,先跑一遍抓取脚本,让日志在后台静静躺着。真出问题了,直接翻文件,而不是手忙脚乱现学现抓。这种“先装好行车记录仪再上路”的思路,比任何技巧都管用。
还有个小技巧分享给常做长时间稳定性测试的朋友:给抓取脚本加一个定时打时间戳的日志,比如每五分钟往文件里写一行当前时间。这样回头定位“问题大概发生在哪一段”时,有个天然的锚点,不用再靠猜。这个小改动成本极低,收益却很高,值得一试。