23、诊断与日志:车载诊断系统(UDS)、Logcat 定制、Bugreport 分析

车载开发里,诊断和日志是两件绕不开的事。诊断是车跟诊断仪之间的对话,日志是我们跟系统之间的对话。这两件事做好了,车出了问题你能快速定位;做不好,车在路上一亮故障灯,你连从哪查起都不知道。

我个人习惯把诊断和日志比作车的「病历本」和「体检报告」。UDS 就是医生问诊的协议,Logcat 是日常体温记录,Bugreport 则是全身 CT 扫描。今天咱们就把这三块掰开揉碎讲清楚。

23.1 车载诊断系统(UDS)

UDS,全称 Unified Diagnostic Services,统一诊断服务。说白了,就是一套标准化的「车跟诊断仪聊天」的协议。你拿一个诊断仪插到 OBD 接口上,能读到故障码、能看实时数据、能刷写固件,背后都是 UDS 在干活。

UDS 基于 ISO 14229 标准,运行在 CAN 总线或 DoIP(基于以太网)上。在 Android Automotive OS 里,我们通常通过 CarDiagnosticManager 来跟底层 UDS 交互。

23.1.1 UDS 的核心服务

UDS 定义了很多服务,每个服务有一个 SID(Service Identifier)。常用的有这些:

SID 服务名称 用途
0x10 DiagnosticSessionControl 切换诊断会话模式(默认/编程/扩展)
0x19 ReadDTCInformation 读取故障码(DTC)
0x22 ReadDataByIdentifier 按 ID 读取数据(如车速、水温)
0x2E WriteDataByIdentifier 写入数据(如配置参数)
0x31 RoutineControl 执行例程(如自检、校准)
0x34/0x36 RequestDownload/TransferData 固件刷写

嗯,这里要注意:不是所有服务在任意会话模式下都能用。比如刷写固件必须在「编程会话」(0x10 03)下才能执行。我见过有新手直接在默认会话下发刷写请求,结果 ECU 直接拒绝,还以为是硬件坏了。

23.1.2 Android 侧的 UDS 实现

在 Android Automotive OS 里,我们通过 CarDiagnosticManager 来发起诊断请求。大致流程是这样的:

// 获取诊断管理器
CarDiagnosticManager diagnosticManager = 
    (CarDiagnosticManager) car.getCarManager(Car.DIAGNOSTIC_SERVICE);

// 创建诊断请求
DiagnosticRequest request = new DiagnosticRequest.Builder()
    .setServiceId(0x22)  // ReadDataByIdentifier
    .addDataId(0xF190)   // 车速数据 ID
    .build();

// 发送请求并监听结果
diagnosticManager.sendDiagnosticRequest(request, 
    new CarDiagnosticManager.OnDiagnosticResultListener() {
        @Override
        public void onDiagnosticResult(DiagnosticResult result) {
            if (result.isSuccess()) {
                byte[] data = result.getData();
                // 解析数据...
            } else {
                Log.e("UDS", "诊断请求失败: " + result.getErrorCode());
            }
        }
    });

我曾经在一个项目里遇到过一个坑:诊断请求发出去后,回调一直不触发。查了半天发现是诊断会话超时了。UDS 默认会话超时是 5 秒,如果你在 5 秒内没有发任何请求,ECU 会自动退出诊断会话。解决方案是在发送请求前先发一个 TesterPresent(0x3E)服务来保持会话活跃。

⚠️ 避坑指南: 我曾经在量产前测试时发现,某些 ECU 对诊断请求的响应时间特别慢,超过 500ms 才返回。而我们的 App 层超时设的是 300ms,导致大量「假失败」。后来我把超时时间放宽到 2 秒,并加了重试机制,问题才解决。记住:不同 ECU 的响应能力不一样,别一刀切。

23.2 Logcat 定制

Logcat 是 Android 开发者的老朋友了。但在车载场景下,默认的 Logcat 输出远远不够。车机上有几十个 ECU,每个 ECU 都有自己的日志,而且日志量巨大——你想想看,一辆车跑一天,光 CAN 总线上的日志就能上 GB。

所以我们需要对 Logcat 做定制,让它更适合车载环境。

23.2.1 日志分级与标签规范

我个人习惯在车载项目里强制使用统一的日志标签格式:[模块名]_[子模块]。比如:

// 诊断模块
Log.d("DIAG_UDS", "发送诊断请求: SID=0x22, DID=0xF190");
Log.e("DIAG_UDS", "诊断响应超时");

// 导航模块
Log.i("NAV_ROUTE", "路线规划完成,距离: 12.3km");
Log.w("NAV_GPS", "GPS 信号弱,使用惯性导航");

// 多媒体模块
Log.v("MEDIA_PLAYER", "播放状态: PLAYING, 曲目: 第5首");

为什么要这么严格?因为车载系统的日志量太大了。没有统一的标签规范,你 grep 的时候根本找不到想要的信息。我见过一个项目,日志标签五花八门,有人用 myLog,有人用 test,最后出问题查日志查了三天。

23.2.2 日志缓冲区定制

Android 默认的日志缓冲区大小是 256KB,对于车载场景来说太小了。我们需要调大缓冲区,并且把日志持久化到文件。

init.rcbuild.prop 里可以配置:

# 调大日志缓冲区到 4MB
logcat.log_buffer_size=4M

# 开启日志持久化
logcat.persist.enable=true
logcat.persist.path=/data/logs/
logcat.persist.count=10
logcat.persist.size=20M

这样配置后,系统会自动把日志轮转写入 /data/logs/ 目录下,保留最近 10 个文件,每个文件最大 20MB。车跑一天,日志基本都能存下来。

💡 小技巧: 我建议在日志文件名里加上时间戳和启动序号,比如 logcat_20250101_120000_001.log。这样即使车机重启了,你也能根据文件名判断日志是哪个启动周期产生的。

23.2.3 过滤与实时监控

有时候我们需要在车机上实时监控某个模块的日志。可以用 logcat -s 加标签过滤:

# 只显示诊断模块的日志
logcat -s DIAG_UDS

# 显示诊断和导航模块的日志
logcat -s DIAG_UDS:V NAV_ROUTE:I

# 输出到文件并实时查看
logcat -f /data/logs/diag.log -s DIAG_UDS:V &
tail -f /data/logs/diag.log

嗯,这里要注意:-s 参数是「静默模式」,只显示你指定的标签。如果不加 -s,其他模块的日志也会刷屏,你根本看不清。

23.3 Bugreport 分析

Bugreport 是 Android 提供的「全家桶」日志工具。执行 bugreport 命令后,系统会把当前状态的所有信息打包成一个 zip 文件,包含:

  • Logcat 日志(所有缓冲区)
  • 系统属性(getprop
  • 进程列表(ps
  • 内存信息(dumpsys meminfo
  • CPU 使用率(top
  • 网络状态(netstat
  • ANR 和崩溃信息
  • 等等等等

在车载场景下,Bugreport 是排查疑难杂症的利器。我遇到过一个 case:车机在行驶过程中偶尔黑屏重启,Logcat 里看不出明显异常。后来抓了 Bugreport,发现 dumpsys cpuinfo 里显示一个系统服务在重启前 CPU 占用率飙到 98%,再结合 dumpsys meminfo 发现内存泄漏了。问题定位到是一个后台线程不断创建 Bitmap 没有回收。

23.3.1 如何抓取 Bugreport

在车机上抓 Bugreport 有两种方式:

  1. ADB 命令adb bugreport /path/to/save
  2. 系统按键:某些车机支持长按特定按键组合触发 Bugreport

我个人推荐用 ADB 方式,因为可以指定保存路径。而且你可以写一个脚本,在特定条件触发时自动抓取 Bugreport:

#!/bin/bash
# 当诊断模块检测到严重故障时自动抓取 Bugreport

if [ "$1" == "critical_fault" ]; then
    TIMESTAMP=$(date +%Y%m%d_%H%M%S)
    adb bugreport /data/bugreports/bugreport_${TIMESTAMP}.zip
    echo "Bugreport saved: bugreport_${TIMESTAMP}.zip"
fi

23.3.2 Bugreport 分析要点

拿到 Bugreport 后,别急着解压乱翻。我一般按这个顺序看:

步骤 查看内容 目的
1 dumpstate_board.txt 查看系统整体状态,是否有 panic 或 crash
2 events.log 查看系统事件,如 ANR、低内存、服务重启
3 main.log + system.log 查看应用和系统日志,重点关注 ERROR 和 WARN
4 dumpsys_meminfo.txt 查看内存使用情况,是否有泄漏
5 dumpsys_cpuinfo.txt 查看 CPU 占用,是否有进程跑满
6 dumpsys_batteryinfo.txt 查看电池和功耗,是否有异常耗电

为什么要先看 dumpstate_board.txt?因为这个文件是 Bugreport 的「目录」,里面汇总了所有关键信息。如果系统有严重问题,这里会直接显示出来,不用你一个个文件去翻。

🔍 实战经验: 我曾经分析过一个 Bugreport,发现 events.log 里频繁出现 am_proc_died 事件,一个系统服务每隔几分钟就重启一次。顺着这个线索,我在 dumpsys_meminfo.txt 里看到该服务的 PSS 值持续增长,确认是内存泄漏。修复后,车机再也没重启过。

23.4 本章知识体系

下面这张图总结了诊断与日志的核心知识结构,你可以把它当作一个快速索引:

诊断与日志知识体系 UDS 诊断系统 Logcat 定制 Bugreport 分析 核心服务(SID 0x10~0x36) Android CarDiagnosticManager 会话管理 & TesterPresent 日志标签规范 缓冲区大小 & 持久化 实时过滤与监控 抓取方式(ADB/按键) 分析顺序(6步法) 关键文件解读 三者结合:诊断发现问题 → 日志定位根因 → Bugreport 全面分析

诊断和日志,说白了就是「发现问题」和「定位问题」的两把刷子。UDS 让你能主动跟 ECU 对话,Logcat 让你能看系统运行轨迹,Bugreport 则是在出大事时给你一份完整的「案发现场报告」。这三样东西用好了,车载开发里 90% 的问题你都能搞定。

我个人建议,在项目初期就把诊断和日志的规范定下来。别等到车都上路了,才发现日志格式不统一、诊断超时设置不合理。到那时候再改,成本就高了。