AOSP日志系统实战:从logcat到dmesg的完整调试指南

发布时间:2026/9/9 12:54:52
AOSP日志系统实战:从logcat到dmesg的完整调试指南 先跟第一次接触AOSP底层调试的朋友澄清一件事标题里的log跟高中数学那个对数函数曲线没有任何关系。我见过太多人搜“log曲线”搜到数学题去了结果越看越迷糊。Android世界里的log来自英文“logbook”的本意是航行记录、运行记录的意思。整个AOSP系统从内核驱动到Java应用层每一层都在往里写运行状态这套日志体系就是你在Android开发、逆向、定制ROM、驱动调试、Framework二次开发时最重要的情报来源。我自己的体会特别深。早年间调一块开发板的触摸屏I2C设备死活不工作我对照原理图量电压、猜寄存器折腾了大半天。后来一个老同事过来敲了一条命令日志里清清楚楚写着i2c 0-0040: probe failed直接锁定了驱动probe阶段的问题。从那一刻起我意识到在AOSP这套系统里不会看日志就等于闭着眼睛修车。这篇文章不打算讲那些东拼西凑的logcat速查表而是把我在实际调试中反复用到、反复踩坑的AOSP日志系统知识整理成一套能落地的体系。内容包括日志的四层分流、logcat命令里容易被忽略的细节、缓冲区为什么会“丢”日志、内核日志怎么抓、崩溃和ANR现场怎么分析以及几个我一直保留的日志调试习惯。适合做AOSP源码开发、ROM定制、驱动调试、系统级App、逆向分析的朋友参考也适合刚入门想搞懂“日志到底是怎么转的”的新人。1. 先理清楚AOSP里的log到底存在哪、由谁记录很多人以为日志就是logcat窗口里那几行字其实logcat只是“展示层”在它背后AOSP把日志按来源和用途分成了好几条完全不同的流水线。如果你不理解这层结构遇到“logcat里怎么什么都看不到”的情况就会彻底卡住。1.1 log不只有一种Java、Native、Kernel、Events四条主流AOSP的日志体系从底层往上可以粗略分成四类理解这四类比背一百条logcat参数都重要。第一类是Java框架层日志。你在App或者系统服务里写的android.util.Log.d(TAG, ...)最终会通过Log.println进入liblog然后通过socket写入logd守护进程。打开/dev/socket/logdw这个节点能看到日志数据流进logd的痕迹。Framework里大量系统服务比如ActivityManager、PackageManager、WindowManager它们的Java代码日志都在这一层。第二类是Native/C/C层日志。Framework并不全是Java写的底层性能敏感的部分比如SurfaceFlinger、AudioFlinger、Vold、Netd都是用C实现的。它们调用__android_log_print对应头文件android/log.h代码里常见的ALOGD、ALOGE这些宏最终也都汇入logd。很多疑难杂症比如显示卡顿、音频延迟、存储挂载失败关键信息全在native日志里只看Java层logcat根本查不到。第三类是内核日志。底层驱动、Binder驱动、网络协议栈、电源管理这些模块用的是内核的printk写到内核自己的环形缓冲区里。平时查看要用dmesg或者logcat -b kernel。这一层和用户态的logd是两个独立体系虽然logd会想办法把内核日志也拷贝一份到kernel buffer但数据量、时序都和真正的内核日志有差异后面我会专门讲。第四类是Events事件日志这个最容易被人忽略。它走的是EventLog接口数据是二进制的结构化格式不面向普通阅读而是面向统计和指标分析。比如am_proc_start记录进程启动am_anr记录ANR发生am_crash记录崩溃。这些事件在普通logcat里是看不到的必须logcat -b events才能读出来。我做稳定性分析时第一步永远是翻events日志因为它把所有关键事件按时间线排好了能快速圈定问题范围。用一个表格把这四类日志的核心信息列出来方便对照日志类型写入接口最终去向常见查看方式典型用途Java层android.util.Logliblog - logdlogcatApp、Framework业务日志Native层__android_log_print / ALOGD等liblog - logdlogcatSurfaceFlinger、Audio等底层服务内核日志printk内核环形缓冲区dmesg、logcat -b kernel驱动、电源、网络、I2C/SPI调试Events事件EventLoglogd events bufferlogcat -b events生命周期、ANR、崩溃、性能指标1.2 logd所有用户态日志的大总管搞明白logd就搞明白了logcat一半的机制。logd是AOSP里一个常驻的守护进程用户态所有日志——不管Java还是Native——都通过socket写给它。它负责把日志按tag、pid、uid、优先级做索引按buffer分类存放再响应logcat等客户端的读取请求。logd到底维护了哪几个缓冲区常见的有main、system、crash、events、kernel、radio、bluetooth、security。每个buffer是独立的内存环形缓冲互不干扰。这样设计有一个非常实际的好处main buffer里刷屏了不会把crash buffer里的崩溃栈顶掉radio buffer里的通话日志也不会被系统服务的日志淹没。你拉日志的时候完全可以选择只拉自己关心的那条流水线而不用在几十万行垃圾日志里大海捞针。这里有一个新手最容易绕晕的点为什么同一个tag的日志有时候用logcat能看到有时候必须加-b system因为logcat默认只读main、system、crash三个buffer的组合不同Android版本略有差异而不是“所有buffer”。如果你想保证不漏可以直接logcat -b all但要承受日志量大、输出杂乱的代价。我的习惯是先logcat -b events圈时间点再针对性拉对应buffer。2. logcat 命令行里那些你容易忽略的高价值细节logcat是Android调试中使用频率最高的命令但我见过太多人只会在Android Studio里点那个Logcat面板或者执行裸的adb logcat然后被几十万行日志淹没。实际上logcat的过滤和输出控制设计得非常精细用好了效率翻倍。2.1 一条日志行的完整构成比你想的多几个字段先看最常见的两种格式。默认brief格式长这样I/ActivityManager( 1234): Start proc 5678:com.example.app/u0a123 for activity {com.example/.MainActivity}I是优先级ActivityManager是tag1234是进程号冒号后面是消息体。当你加上-v threadtime输出变成这样08-22 10:00:00.123 1234 5678 I ActivityManager: Start proc ...每一列分别是日期时间毫秒级、pid、tid、优先级、tag、消息。这个格式是我调试时的首选因为它带出了线程号。为什么线程号重要系统卡顿或者ANR分析时要判断是不是某个子线程死循环把主线程挤住了就必须知道log是在哪个线程打出来的只有threadtime格式能直接回答这个问题。优先级从低到高是V、D、I、W、E、F外加一个S代表静默。V是verbose最啰嗦平时开着会被刷死D是debug开发期用I是info例行信息W是warningE是error严重错误F是fatal致命错误。系统源码里很多关键点都打在了I级别比如进程启动、service绑定、页面切换别看到I就以为是无关紧要的在系统调试里I级别的信息往往才是主线索。2.2 高频又容易写错的logcat过滤命令很多人的logcat用法停留在adb logcat -s Tag但实际项目里日志量大的时候这种写法完全不够用。下面这几个是我几乎每天都在用的过滤姿势。按tag和级别过滤最标准的写法是# 只看ActivityManager的warning以上日志 adb logcat -s ActivityManager:W # 或者保留多个tag adb logcat -v threadtime ActivityManager:I *:S注意-s参数等价于*:S意思是“除了我指定的tag其他全部静默”。所以-s ActivityManager:W的含义是只有ActivityManager这个tag、且级别W的日志才会显示。这个符号写反的人特别多我就见过有人写ActivityManager:S结果啥都不显示还以为是设备出了问题。按进程过滤是高负载排查的利器# 只看某个pid的全部日志 adb logcat --pid1234 -v threadtime # 再看某个uid典型是system uid1000的日志 adb logcat --uid1000 -v threadtime按时间/行数尾巴抓取是“事后取证”的关键# 只显示最后200行并退出不会一直阻塞 adb logcat -t 200 -v threadtime # 显示某个时间点之后的日志 adb logcat -t 08-22 10:00:00.000 -v threadtime-t这个参数我觉得是新手最该先掌握的一个。设备已经复现完问题你才想起抓日志这时如果直接logcat看到的全是复现之后的新日志问题现场早就被冲掉了。用-t 500直接把最近500行调出来往往崩溃栈就在里面。还有清空缓冲区的-c写入文件的-f这两个要配合用。-c会清掉所有buffer执行之前一定要确认现场已经保存。-f指定文件输出适合长时间挂机采集但注意写入的路径要有权限我一般写到/sdcard/或者/data/local/tmp/然后adb pull出来。Windows下还有一个特别容易翻车的坑PowerShell里logcat的过滤表达式里那些*和:会被shell吃掉。adb logcat *:S经常直接报错或者窗口卡死。稳妥的做法是给整个过滤表达式加引号adb logcat -s ActivityManager:I在CMD、PowerShell、Git Bash里的行为还不完全一样。为了省心我强烈建议你在Windows上做日志分析时统一用Git Bash或者WSL少跟PowerShell的转义规则较劲。2.3 选错buffer日志就会“凭空消失”很多问题排查半天没有头绪最后发现是buffer看错了。我讲几个真实高频场景。第一个场景App闪退但怀疑是崩溃打开logcat却只看到自己App的普通日志没有崩溃栈。原因是Java UncaughtException和Native crash的堆栈默认打进crash buffer而不是main buffer。如果跑的是裸logcat某些设备/版本上确实能看到一部分但最完整的崩溃现场必须用-b crash来看adb logcat -b crash -v threadtime第二个场景开机起不来想看系统启动到哪一步挂了。这种场景普通logcat往往只能看到启动中期的日志非常早期的内核启动、init、zygote拉起阶段的日志分布在kernel buffer和system buffer里adb logcat -b kernel -b system -v threadtime第三个场景手机信号、通话、数据网络问题。这类日志在radio buffer默认logcat是不读的adb logcat -b radio -v threadtime我把常见诉求对应到buffer的经验整理成了下面这张表贴出来给各位参考排查目标优先查看的bufferApp自身业务逻辑main系统服务AMS/WMS/PMS等systemJava崩溃、Native崩溃、强制关闭crashANR、进程生命周期、启动耗时events驱动、内核、外设、开机kernel电话、基带、数据网络radio蓝牙协议栈bluetoothSELinux、权限相关security3. log缓冲区的真相为什么日志会“丢”“日志怎么又没了”是调试群里出现频率最高的问题。很多时候不是设备不给你面子而是你还没理解AOSP日志缓冲区的物理本质。搞懂这一节你再也不会乱怪工具。3.1 环形缓冲区日志写满后会发生什么logd拿内存维护的每一个缓冲区都是环形缓冲区ring buffer。环形缓冲区的特点就一个写满了新日志会覆盖最旧日志。所以如果你长时间挂着logcat却不保存等到问题复现时最早的现场可能已经被几万条新日志覆盖掉了。想确认当前缓冲区有多深用这个命令adb logcat -g输出大致是main: ring buffer is 256Kb (210Kb consumed), max entry is 5120B, max payload is 4096B system: ring buffer is 256Kb (0Kb consumed), max entry is 5120B, max payload is 4096B crash: ring buffer is 256Kb (48Kb consumed), max entry is 5120B, max payload is 4096B events: ring buffer is 256Kb (0Kb consumed), max entry is 5120B, max payload is 4096B不同厂商、不同Android版本这个默认大小不完全一样常见的是256KB或1MB。对深度调试来说256KB太小稍微刷一下就没了。AOSP提供了系统属性来调整最常见的两个# 全局调整 adb shell setprop persist.logd.size 1M # 单独调整某个buffer adb shell setprop persist.logd.size.main 512K adb shell setprop persist.logd.size.crash 1M调整后需要重启logd进程才生效最简单的方式是重启设备或者adb shell stop adb shell start。如果你手头的是userdebug/eng版本改起来很方便如果是user版本大概率没权限写这些属性那就只能靠外挂logcat重定向来弥补。这里必须多说一句logcat -g里显示的“consumed”只是当前已经用掉的环形空间不代表日志文件的大小。很多人把它当成“日志文件占了多少存储”这是误读。3.2 logd如何同时服务多个读取者logcat并不是直接从内核读日志而是作为客户端连上logd。你用两个终端同时跑logcat一个过滤tag A一个过滤tag B两者看到的日志互不干扰——因为logd为每个logdr连接各自维护了独立的读取位置。这个机制平时没问题但在极端情况下会有一个隐藏坑如果其中一个客户端长时间以非常高的频率消费日志它的读取位置会持续追着尾部跑而另一个慢速客户端可能因为追不上被整体跳过一些日志表现为“漏日志”。实际调试中这种场景不多但如果你在跑自动抓取脚本的同时又手动开了一个logcat窗口恰好碰上日志洪峰是有可能漏掉中间一段的。解决方式很简单重要现场用文件保存不要在终端里肉眼看实时流。3.3 重启后日志去哪了内存缓冲区的宿命这是每一个从App开发转到底层调试的人都会问的问题“我明明在崩溃前抓到了日志重启一下怎么就全没了”答案很残酷logcat默认的所有缓冲区都存在内存里掉电即失。App崩溃也好内核panic也好只要设备重启logd缓冲区立刻清零。所以做稳定性测试的正确姿势不是“崩溃后再去看”而是提前把日志想办法落盘。AOSP有一套叫logpersist的机制可以把日志持续写入/data/misc/logd/目录。启动它需要在init rc里有对应配置普通设备上不一定开启。更通用的方案是直接挂一个后台logcatadb shell nohup logcat -b all -v threadtime -f /data/local/tmp/log_all.txt 注意要确认/data/local/tmp有写权限免得跑了一晚上一个字节都没写进去。我自己就吃过这个亏脚本跑完一看文件大小是0白忙一场。检查是否在写入可以adb shell ls -l /data/local/tmp/对比文件大小变化。内核日志的持久化则要依赖pstore这个放到下一节专门讲。4. 内核日志崩溃现场与驱动调试的第一现场很多人做Android调试很少碰内核日志觉得那是搞Linux驱动的人才需要看的。实际上I2C外设失灵、电源管理异常、网络链路不稳、开机卡在某一阶段、莫名其妙重启这些问题的主线索全都在内核日志里。AOSP的log基础不碰dmesg等于只学了一半。4.1 dmesg、printk与内核环形缓冲区内核日志由printk产生存放在内核自己的环形缓冲区。用户态查看方式主要有三种# 最常用完整导出当前内核日志 adb shell dmesg # 实时跟踪新增内核日志 adb shell dmesg -w # 通过logcat的kernel buffer查看部分设备支持 adb logcat -b kernel这三种方式有个重要区别dmesg读的是内核ring buffer的“实时状态”logcat -b kernel读的则是logd从内核拷贝过来的一份副本。拷贝这件事有时间延迟而且logd的kernel buffer也有自己的容量限制所以高并发打印时logcat -b kernel可能会比dmesg少内容。做内核级分析时我永远优先用dmesglogcat kernel只作为补充。/proc/kmsg也可以读内核日志但它的读取是排他性的——一个进程打开后其他进程再读就会阻塞或者读到空。并且Android上用普通shell用户访问/proc/kmsg大概率被SELinux拦下来需要root。所以不建议新手碰它。printk有个日志级别概念从0到7数字越小越紧急。0是KERN_EMERG7是KERN_DEBUG。内核默认会过滤掉级别过低的日志所以有时你打开dmesg看不到驱动里那些调试打印不是没打印而是被过滤了。查看当前过滤阈值cat /proc/sys/kernel/printk输出四个数字例如7 4 1 3第一个是控制台日志级别表示级别高于7的才会打到控制台几乎全放行第二个是默认日志级别第三个是最低控制台级别第四个是默认控制台级别。调试驱动时我常用root权限把这个阈值调到最开放echo 8 4 1 7 /proc/sys/kernel/printk但注意Android设备上SELinux通常禁止普通进程甚至shell直接写这个节点userdebug版本也要先adb root才有机会。如果连root都写不了只能回顾内核编译参数这个就不展开说了。4.2 重启后dmesg为什么是空的pstore与ramoops这是稳定性调试里最痛的一个问题设备panic或者被watchdog拉起来重启之后你赶紧跑dmesg结果发现一片空白。原因就是内核ring buffer在内存里重启清零。要抓到重启前的“遗言”必须靠pstore——它把一段日志写到一次重启不会清空的保留内存区域里。AOSP设备上常见的位置是# 重启后第一时间执行 adb root adb shell ls -l /sys/fs/pstore/如果运气好你会看到类似console-ramoops-0、dmesg-ramoops-0、pmsg-ramoops-0这样的文件。console-ramoops保存的是控制台输出dmesg-ramoops保存的是完整内核日志pmsg保存的是用户态通过pstore接口写入的日志。抓取动作要快因为pstore在部分内核配置下读取过一次后就会被清除留给下一次崩溃用。我吃过一次亏设备崩溃重启后我慢悠悠地先开了一堆工具等五分钟后再去ls /sys/fs/pstore文件已经没了崩溃现场彻底丢失。现在我养成的习惯是重启完成后第一步永远是adb root adb pull /sys/fs/pstore/先落袋为安。有些厂商会把pstore内容在启动时拷贝到/data/vendor/log/或者/persist/目录下系统工程师拉日志的时候可以顺手找找这两个目录。如果你拿到的设备连pstore节点都没有说明内核没开ramoops支持这种情况只能从硬件上看门狗日志入手已经不是纯软件范畴了。4.3 一个I2C驱动调试实例内核日志怎么用拿热词里提到的“i2c-tools 在 Android 上使用”来说事。假设你在一台开发板上调试一个I2C触摸屏设备不工作我的标准动作是三个终端并行第一个终端实时看内核日志adb shell dmesg -w | grep -i i2c第二个终端用i2c-tools扫描总线adb shell i2cdetect -y -r 0 adb shell i2cget -y 0 0x40 0x00第三个终端用logcat看驱动框架日志adb logcat -b kernel | grep -iE i2c|touch如果内核日志里出现sendbytes: NAK bailout说明总线上设备没应答往硬件连接、设备地址方向排查如果出现probe failed说明设备响应了但probe阶段某个步骤失败那就往复位、中断、供电方向查。这些信息在app层和framework层是永远看不到的不打开dmesg根本无从下手。5. 实战从一堆日志到真正定位根因看日志的最终目的是定位问题。这一节我拿崩溃、ANR、网络连接三类最常见的现场讲讲我是怎么从原始日志里一步步梳理出根因的。这不是标准教程里的流程而是我实际操作中验证过无数遍的套路。5.1 崩溃日志与tombstone先看crash buffer再看墓碑Java崩溃比较好认logcat里会有一大段AndroidRuntime打头的堆栈通常出现在crash buffer。直接执行adb logcat -b crash -d -v threadtime-d参数是dump完后退出不会一直挂着适合一次性取回现场。Native崩溃更隐蔽。debuggerd会拦截先在logcat里打出一行被* * * * * * * * * * * * * * * * * * * * * * *包围的分隔线堆栈随之打出。除了logcat里的即时输出debuggerd还会把详细信息写进tombstone文件adb shell ls /data/tombstones/ adb pull /data/tombstones/tombstone_00分析tombstone时我通常先看三个关键信息signal字段signal 11 (SIGSEGV) code 1 (SEGV_MAPERR)告诉你访问了非法地址fault addrfault addr 0x0基本就是空指针解引用backtrace从下往上看找到崩溃点在哪个so、哪个函数。很多人拿到tombstone就懵其实看多了会发现绝大多数Native崩溃的根因就两类空指针和生命周期错乱。空指针在backtrace里通常表现为偏移量很小的地址访问比如0x0、0x10生命周期错乱则常伴随着use-after-free此时logcat里往往还能找到“已释放对象又被回调”的蛛丝马迹。5.2 ANR日志的正确打开方式ANR是系统稳定性里的大头。AOSP在ANR发生时会往events buffer里写一条am_anr事件。这是分析ANR的第一入口adb logcat -b events -d | grep -i am_anr典型输出类似am_anr: [0,12345,com.example.app,321,Input dispatching timed out (Waiting because the touched window is not responding...)]拿到进程号和reason之后下一步去/data/anr/目录取traces文件adb shell ls /data/anr/ adb pull /data/anr/traces.txt分析ANR的常规思路是四步走找到主线程的栈看它阻塞在哪个函数。如果主线程在执行binder调用继续往下看是等哪个binder服务。看CPU负载。/proc/loadavg和traces文件顶部的CPU汇总信息能判断是主线程自己慢还是整个系统负载过高被拖垮。看锁等待。traces里出现waiting to lock 0x... held by thread 12说明主线程在等一个锁而持锁线程可能卡死在某个循环里。看是否存在内存压力。低内存设备上频繁GC、LMK杀进程都会导致ANR频发此时traces里的CPU使用率往往不高而是kswapd占满CPU再加上logcat里的lowmemorykiller日志基本就能锁定方向。这里有个新手常犯的错误一上来就翻traces里自己App的线程栈却不看系统整体状态。很多ANR根本不是App主线程卡住而是整个系统被某个内核线程或者系统服务拖住这时你App的栈只是“受害者”真正的问题在持锁者或者CPU占用榜靠前的线程上。正确顺序是先看系统级汇总再往下钻。5.3 一个跨层案例网络连接异常怎么用四类日志串联拿一个常见场景做示范设备网络连接频繁断开终端日志里出现interface output discard exceeded the log threshold。这行日志本身说的是某个网络接口的发送队列丢包数超过了阈值触发了打印。如果你只盯着App代码很难有进展正确做法是分层找证据。第一步看events buffer里ConnectivityService、WifiManager的关键事件adb logcat -b events -d | grep -iE wifi|connectivity|net第二步看系统日志里的网络栈决策adb logcat -b system -d | grep -iE ConnectivityService|EthernetTracker第三步看内核日志里接口状态adb shell dmesg | grep -iE wlan|eth|rmnet|link down第四步查看接口统计信息确认丢包adb shell ifconfig wlan0 adb shell cat /proc/net/dev这样做的好处是每一步都在验证“问题到底出在哪一层”。如果events里没有异常system日志里ConnectivityService也没上报链路断开但内核有link down说明是驱动/硬件层面的链路波动如果内核一切正常但ConnectivityService在不停切换网络那更可能是上层网络评分策略的问题。日志不会直接告诉你答案但会告诉你该往哪个方向找答案。5.4 通用抓日志脚本拿现场就要一次拿全稳定性问题最怕缺日志。我现在养成的习惯是所有复现测试开始前先跑一个组合脚本把四类日志全部落盘宁可多抓不可漏抓。下面这个脚本各位可以直接改路径使用# 先清一轮旧日志保证现场干净 adb logcat -c adb shell echo 3 /proc/sys/vm/drop_caches 2/dev/null || true # 主日志所有buffer threadtime格式 adb logcat -b all -v threadtime -f /sdcard/log_all.txt LOGCAT_PID$! # 单独抓crash防止被大量普通日志淹没 adb logcat -b crash -v threadtime crash_standalone.txt 21 # 内核日志 adb shell dmesg dmesg_before.txt # 复现操作做完后再取一次内核日志 adb shell dmesg dmesg_after.txt # 收尾时结束logcat进程 kill $LOGCAT_PID老设备上-b all不一定被支持会直接报错。安卓7及以下的设备稳妥起见分开写adb logcat -b main -b system -b crash -v threadtime -f /sdcard/log_all.txt大日志文件拿到之后不要急着从头看到尾。先做关键词初筛。我常用的过滤模式# 找崩溃 grep -nE FATAL|AndroidRuntime|DEBUG|signal log_all.txt # 找ANR grep -nE ANR|am_anr log_all.txt # 找系统服务异常 grep -nE Watchdog|am_crash|am_kill log_all.txt之前有朋友问“log文件翻译”是什么意思其实在分析阶段最需要的不是“翻译”而是“归纳”。几十万行日志先筛出关键字再沿着时间线把事件串起来远比逐行读要有用。6. 几个我自己踩过坑之后养成的日志习惯最后分享几条纯个人经验不一定写在官方文档里但每一条都是真金白银换来的。第一条TAG命名要克制而且要规范。常用格式是“类名_功能点”比如MainActivity_Init、NetMgr_Login。不要用笼统的MainActivity更不要用DEBUG这种谁都在用的tag。否则logcat过滤时会混入一堆不相关内容分析效率直线下降。第二条日志级别要“按需开”。调试期可以放开V/D但发布出去的版本特别是线上灰度包尽量控制I级别以下日志的输出。AOSP里提供了Build.TYPE和Log.isLoggable两套开关我一般会做一个统一的日志开关类通过系统属性控制线上包日志开关这样既保留排查手段又不至于打爆logd缓冲区。第三条别把敏感信息写进日志。用户Token、密码、验证码之类的东西一旦打进日志就等于把这些数据送进了所有能读日志的人手里。AOSP系统的日志权限并没有想象中那么严很多App都能通过READ_LOGS权限读到别的进程日志。我在做系统级开发时对日志脱敏的要求很高宁可多写几行脱敏代码也不要给自己埋雷。第四条先看events再找logcat。Events日志的时间线是整个系统事件的“目录”很多问题可以先在events里精准定位到发生时间然后回到对应buffer的对应时间段看细节。这比漫无目的地扫logcat高效得多。第五条遇到需要保存现场的情况第一时间用adb bugreport。这个命令会把系统属性、进程列表、内核日志、logcat各buffer、ANR traces、系统配置等一大堆信息打包成一个zip。虽然文件大、启动慢但对复杂稳定性问题来说它是最全面的“现场快照”。我接到一个陌生设备的问题时第一件事往往就是让现场的人先搞一个bugreport出来。AOSP的日志体系远不止logcat一条命令它是从内核printk到Java Log从环形缓冲区到pstore从logcat终端到tombstone文件的一整套设施。把这套设施的每个环节弄明白你面对任何奇怪的系统问题时至少知道第一步该去哪里看、第二步该信哪条日志。希望这篇文章能帮你把这套基础打牢。