☰
Android车机与PAD性能优化:八维指标、adb命令与回归表
2026/11/2 21:55:07 网站建设 项目流程

简介:面向Android系统与应用开发者的性能优化专题文档,聚焦PAD与车机两类项目的实际调优场景,围绕开机速度、响应速度、内存管理、系统稳定性、数据传输、数据流量、存储容量与模块功耗八个方向展开,并补充adb常用命令在调试中的用法。压缩包仅含1个docx文件,大小约10.32MB,便于集中查阅与整理成个人优化清单。文档结合14类集成应用APK响应时间测试、冷热启动差异、内存泄露与ANR/Crash排查、Monkey与CTS兼容性测试等具体内容,给出可落地的分析思路和检查项,适合需要系统梳理Android性能优化框架、对照项目查漏补缺的开发者参考。已有687人学习下载。

1. 车机冷启动 30 秒的痛,靠八块拆解慢慢治

车机项目刚接手那会儿,最怕的不是功能做不出来,而是每天早上冷车点火,屏幕黑着等三十多秒。用户不会关心你 kernel 里链了多少驱动,他只会说这台机器卡。Android 性能优化落到 PAD 和车机这两个形态上,跟手机完全不是一套打法:车机烧油不靠电池,功耗可以让位给启动速度和稳定性;PAD 的 BSP 是内嵌打包的 bin,模块功耗基本无解,只能在应用层做对比兜底。这份笔记把优化拆成开机速度、响应速度、内存管理、系统稳定性、数据传输、数据流量、存储容量、模块功耗八个方向,每一块都配了判定指标和能直接跑的 adb 命令。它适合手上有一台真机、被 ANR 和开机时间追着跑、需要拿数据说话的 Android 系统工程师,不适合想找现成框架直接套的人。

2. 开机速度:把一次冷启动切成四段,逐段砍掉日志、驱动和预加载

2.1 冷启动链路上谁在吃时间

开机时间不是一个数字,是一条链。从按下电源键到 Launcher 第一帧可见,中间依次经过 bootloader、kernel 初始化、init 解析 rc 文件、zygote 孵化、SystemServer 起服务、PackageManager 扫描 APK、开机动画退出、Launcher 冷启动。任何一段出问题,整体就慢,而大多数人只盯着最后那段动画,这就是典型的抓错对象。

我一般先把这条链切成四段来记:kernel 段(从内核入口到 init 运行)、init 段(解析 rc、起 native service)、SystemServer 段(起 Java 服务、扫描包)、App 段(Launcher 首帧)。车机还会多出 MCU 唤醒、倒车影像优先级抢占这些平台特性,PAD 则常常败在预装 APK 太多、首次启动 dex2oat 编译上。分段的意义在于,你后面做的每一次删减,都能对应到具体的段里去,而不是靠感觉说"好像快了点"。

分段之后要定的就是判定标准。我的习惯是:kernel 段不超过 3 秒,init 段不超过 2 秒,SystemServer 到开机动画退出不超过 12 秒,Launcher 首帧在动画退出后 1.5 秒内出现。超过任何一条,就进对应段里查。这套阈值不是标准答案,但它让"开机慢"变成可追责的具体项。

2.2 用 dmesg 时间戳和 logcat 把耗时落到具体阶段

拿到机器第一件事不是改代码,是抓基线。三条命令就能把大部分信息抓下来,注意 dmesg 的时间戳是相对内核启动的秒数,logcat 是墙钟时间,两边要对齐得靠开机那一刻的BOOT_LOGO或者init打印。

# 抓内核启动时间戳,重点看 initcall 里谁最慢 adb shell dmesg | grep -E "initcall|Freeing unused kernel memory|Run /init" | tail -n 60 # 抓整条开机链的 logcat,main 之外一定要带 events 和 system adb logcat -b main,events,system -v threadtime > boot_full.txt # 看当前内核命令行,确认是否开了 initcall_debug adb shell cat /proc/cmdline

第一条命令看的是 kernel 段。Freeing unused kernel memory那一行的时间戳,近似就是内核初始化完成、准备跑 init 的时刻;initcall那些行如果开了initcall_debug,会逐个打印驱动初始化耗时,谁在后面拖几百毫秒一目了然。第二条命令的关键在-b参数:默认只抓 main 缓冲区,会漏掉 events 里的boot_progress_*事件,而这些事件恰恰是 Android 官方埋的开机打点,从boot_progress_start到boot_progress_enable_screen一条不少。第三条命令确认initcall_debug有没有开,这个参数只在调试版内核上开,量产版开了会拖慢启动,属于典型的"为了测而测"。

logcat 抓下来之后,按 tag 过滤出几个关键点:BootAnim的启动和退出、ActivityManager的Displayed、PackageManager的扫描起止、SystemServer的总耗时。把这几条时间戳列成一张表,哪一段超阈值就非常清楚了。

# 从抓到的日志里抠关键打点,按时间排序 grep -E "boot_progress|Displayed|BootAnim|PackageManager.*scan" boot_full.txt | sort -k1,2

这里要注意sort的字段,threadtime格式下前两列是日期和时间,-k1,2才能按时间正序排。如果你的日志跨了零点,得先把日期补全,否则排序会乱——这个坑我在车机项目上踩过一次,因为日志里时间戳没带年份,跨年那次直接排反了。

2.3 删日志、删驱动、删服务:三处改动的边界

第一刀砍日志,收益立竿见影但要注意边界。kernel 侧的printk等级、Android 侧 logd 的缓冲区大小、init.rc 里的loglevel,三处都能调。常见做法是把 kernel 的默认日志等级从 7 降到 4,只保留 warning 以上,然后关掉CONFIG_DEBUG_*那一批调试配置。这一刀下去 kernel 段通常能省 200 到 500 毫秒,具体看平台。

# 看 init.rc 和各个 .rc 里配的日志等级与服务状态 adb shell getprop | grep -E "init.svc|log.level|persist.log" # 运行时改日志等级,验证收益(重启后失效) adb shell setprop log.level 4

init.svc.<服务名>这个属性族非常有用,它直接告诉你某个 native service 当前是 running、stopped 还是 restarting。删服务之前先用它确认服务到底有没有被拉起,别删了个从来没启动过的,白高兴一场。persist.log.tag系列属性控制的是 Java 侧 log 开关,逐个查一遍能发现不少遗留的调试开关。

第二刀砍驱动,风险最高。做法是在 defconfig 里把不用的CONFIG_*关掉,比如没有 NFC 的 PAD 就关掉 NFC 子系统,没有 FM 的关掉 FM。但驱动之间有依赖,关一个可能连带关掉别的东西,所以每次改完必须重新编译并对比vmlinux大小和 dmesg 里的 initcall 列表,确认没有引入新的报错。

# 先看当前 defconfig 里开了哪些外设相关配置 grep -E "^CONFIG_(NFC|FM|GPS|SENSOR|TOUCHSCREEN|CAMERA)" arch/arm64/configs/target_defconfig # 编译后对比内核体积,体积降了但启动没快,说明这个驱动本来就没耗时 ls -l vmlinux

第三刀砍服务,主要动 init.rc 里的 service 和 SystemServer 里的startService。判断标准很简单:这个服务在整机功能清单里有没有对应入口?没有就停。改完用getprop | grep init.svc复查,确认服务确实没起来,而不是起来又挂了。

2.4 预加载类和预装 APK 的减法,以及首次启动为什么要单算

preloaded-classes这个文件是开机时间的隐形大户。它决定了 zygote 预加载哪些类,文件越大,zygote 启动越慢,但应用启动会快一些,是个典型的权衡。很多团队的做法是手工删几行,这是错的——这个文件是工具扫描生成的,手删会破坏类之间的依赖顺序,轻则预加载失败,重则开机直接起不来。正确做法是用WritePreloadedClassFile重新跑一遍生成流程,基于当前系统实际用到的类重新产出。

预装 APK 的扫描耗时在 PackageManagerService 里,每个 APK 都要解析 manifest、校验签名、建索引。PAD 项目上预装三四十个 APK 是常态,扫描段轻松吃掉两三秒。减法思路是:能放到/data下按需下载的,就别塞进/system/app;必须预装的,合并成少数几个包,减少 APK 数量本身比减小 APK 体积更有效。

# 数一数开机到底扫了多少包,和预装清单对一下 adb shell dumpsys package | grep -c "Package \[" # 看 dexopt 状态,首次启动的编译开销在这里 adb shell dumpsys package dexopt | head -n 40

首次启动要单独算,是因为它和二次启动完全是两码事。刷机后第一次起来,/data要初始化、所有预装应用要跑 dex2oat 生成 oat 文件、数据库要建表。这一段慢是正常的,但慢到不可接受就得优化:常见做法是预置编译好的 oat,或者把编译过滤器调成quicken而不是speed,用一点运行时性能换开机时间。验证方法是连续重启五次,第一次和后面四次的耗时分开记录,差距超过一倍就说明首次启动没优化到位。

3. 响应速度与内存:14 个集成 APK 的冷热启动怎么量、内存怎么定位

3.1 用高速摄像机和代码埋点测响应时间

响应时间的测量有两套方法,一套靠外部设备,一套靠系统埋点,两者要交叉验证。高速摄像机的作用是拿到用户真实感知的时间:从手指按下屏幕到画面出现第一帧变化,这中间包括了触摸上报、输入分发、应用处理、渲染合成、屏幕刷新,任何一个环节都能被拍进去。我一般用 240fps 拍,一帧约 4.2 毫秒,精度足够。代码埋点拿到的则是应用内部的处理时间,两者一减,就知道慢在应用里还是慢在系统链路。

需要重点测的是那批集成 APK:桌面、电话、联系人、短信、相机、相册、云邮箱、音乐、视频、应用中心、安全精灵、主题中心、设置、文件浏览器。这十四个应用有个共同特征——都要做大数据的导入导出和频繁的数据交互,联系人要读几千条记录、相册要扫缩略图、文件浏览器要遍历目录,它们的响应时间最能反映系统的真实水平。闹钟、计算器、录音机、便签、日历、浏览器、输入法这些轻量应用不用单独测,它们的开销已经包含在上面那批里了。

# 用 am start -W 拿三个时间,注意必须先 force-stop 制造冷启动 adb shell am force-stop com.demo.settings adb shell am start -W -n com.demo.settings/.Settings

am start -W会输出三个字段:ThisTime是最后一个 Activity 的启动耗时,TotalTime是整条启动链的总耗时,WaitTime是 AMS 从发起到收到完成回调的总时间。判断标准看TotalTime,WaitTime明显大于TotalTime说明系统调度层有阻塞。这个命令的局限在于它只统计到 Activity 的onResume返回,不含首帧渲染,所以要和摄像机数据配合看。

3.2 冷启动和热启动的差别,以及锁屏解锁为什么要单独算

冷启动和热启动的差距可以是三到五倍。冷启动时进程不存在,要 fork zygote、加载类和资源、初始化 Application;热启动时进程还在后台,直接走onRestart到onResume。测量时必须严格控制变量:冷启动用am force-stop杀进程,热启动按 Home 键回桌面等三秒再拉起,两次之间要留足间隔,否则系统还没回收完资源。

# 热启动测量:先回桌面,等进程稳定后再拉 adb shell input keyevent KEYCODE_HOME sleep 3 adb shell am start -W -n com.demo.settings/.Settings

锁屏和解锁要单独算,是因为这里有一堆容易被忽略的动作:播放锁屏和解锁音效、加载和解除功耗锁、窗口转场动画、指纹或人脸识别的等待。测法是按下电源键到锁屏画面完全出现算一次,点亮屏幕到解锁完成算一次,各测二十次取中位数。如果发现解锁慢,先用dumpsys power看 wakelock 的持有和释放时机,很多时候是某个应用在解锁瞬间抢了功耗锁。

# 看当前 wakelock 和窗口状态 adb shell dumpsys power | grep -E "Wake Locks|mWakefulness" adb shell dumpsys window | grep -E "mCurrentFocus|mFocusedApp"

3.3 meminfo、procrank、showmap 三组数据怎么看

内存这块最容易犯的错是只看一个数字。Android 的内存有四个口径:VSS 是虚拟地址空间大小,包含共享库;RSS 是实际驻留物理内存,也包含共享库;PSS 是按比例分摊共享库之后的物理内存;USS 是进程独占的物理内存。看内存泄漏要看 PSS 和 USS 的趋势,看整体压力看 RSS,看地址空间碎片才看 VSS。

# 三组命令各有侧重,别只跑一个 adb shell dumpsys meminfo com.demo.settings adb shell procrank -u adb shell showmap -w <pid> adb shell cat /proc/<pid>/smaps

dumpsys meminfo给的是分项明细,Dalvik Heap、Native Heap、Stack、Code、Private Dirty 都在里面,适合定位"是哪一类内存涨了"。procrank -u按 USS 降序排列所有进程,适合快速找出大户。showmap -w <pid>和/proc/<pid>/smaps给的是虚拟内存区域的逐段明细,适合查大块映射和内存碎片。三组数据要落在同一个时间窗口里采,否则交叉对比没有意义。

我一般会写个小循环,每五秒采一次dumpsys meminfo的 PSS 值,连续跑十分钟,画一条曲线。曲线单调上升就是泄漏,波动但收敛就是正常缓存。这个方法笨,但比任何工具都可靠,因为它测的是真实的运行时行为。

3.4 内存泄漏、空指针、ANR、Crash 的定位顺序

这四类问题的定位顺序不能乱,乱序会浪费大量时间。我的顺序是:先 Crash 再 ANR,先空指针再内存泄漏。因为 Crash 有明确的堆栈,最快能定位;ANR 有 traces 文件,次之;空指针往往是 Crash 的子集,单独看是为了找出那些被 try-catch 吞掉的;内存泄漏最难,需要长时间采样,放最后。

# Crash 和 ANR 的现场都在 /data 下 adb shell ls -l /data/anr/ adb shell cat /data/anr/traces.txt adb logcat -b crash -v time adb shell ls -l /data/tombstones/ adb shell dumpsys dropbox --print system_app_anr

-b crash这个缓冲区很多人不知道,Android 会把FATAL EXCEPTION单独丢进去,比在 main 里大海捞针高效得多。/data/anr/traces.txt在新版本上会拆成多个anr_*文件,按时间戳取最新的那个。dumpsys dropbox里存的是系统级异常的历史记录,需要 root 权限才能读全。

内存泄漏的排查要结合代码和运行时数据。运行时看 PSS 曲线,代码侧看静态引用:Context 被静态变量持有、Handler 非静态内部类持有 Activity、Bitmap 用完没 recycle、Cursor 没 close,这几类占了系统应用泄漏的绝大多数。空指针则集中在异步回调里——回调回来时 Activity 已经销毁,getContext()返回 null,这类问题在dumpsys meminfo里看不出来,只能靠 logcat 里的NullPointerException堆栈定位。

跑分工具(BenchmarkPi、Linpack、softweg benchmark)可以作为回归参考,但不能当目标。它们的测试场景和真实使用差距很大,分值涨了不代表用户觉得快。我的用法是:每次版本迭代跑一遍,分值掉超过 5% 就回头查最近的改动,仅此而已。

4. 稳定性与数据传输:Monkey、CTS 达标线,以及 WiFi、蓝牙、SD 卡测速

4.1 Monkey 与 CTS 的达标线怎么定

系统稳定性的第一道关是 Monkey。它不是随便跑跑就行,参数、seed、包名、事件间隔都得固定下来,否则两次结果没法比。-p指定被测包,--throttle控制事件间隔,-s固定随机种子保证可复现,事件数按项目要求定,车机项目我一般跑两万次起步。

# 固定 seed 跑两万次,事件间隔 300 毫秒 adb shell monkey -p com.demo.settings --throttle 300 -s 20240101 -v 20000 # 多包场景:把 -p 重复写,或者用 --pkg-whitelist-file adb shell monkey --pkg-whitelist-file /data/local/tmp/pkgs.txt -s 20240101 -v 20000

判定标准要提前写死:出现// NOT RESPONDING、// CRASH、** Monkey aborted due to error,任何一条都算不通过。跑完之后从 logcat 里把ANR in和FATAL EXCEPTION抠出来,逐条归因。这里有个细节,-p只对主包生效,多进程应用的其他进程不会被覆盖,所以要么用白名单文件,要么在跑之前确认清楚进程模型。

CTS 是另一条线,它测的是兼容性而不是稳定性。跑法是用cts-tradefed,重点关注arm64-v8a架构下的失败项和CtsOsTestCases这类和系统行为强相关的模块。CTS 失败不一定是你的 bug,但每一条失败都要有结论:是平台配置问题、是缺少某个 feature、还是真的改坏了。

系统的兼容性还包括第三方应用。这块没法穷举,我的做法是维护一个二十个左右的高频第三方应用清单,每个版本手动过一遍核心路径,重点看拉起速度、页面切换、后台驻留这三项。清单不用大,但要稳定,每次都测同样的一批,才能看出趋势。

4.2 WiFi、蓝牙、SD 卡读写的测速方法

数据传输的测速要区分"连接速度"和"传输速度",这是两件事。WiFi 的连接速度看的是关联和认证耗时,传输速度看的是吞吐。AP 切换重连速度是车机上特别重要的指标,因为车机经常在停车场里跨 AP 漫游。

# WiFi 状态和吞吐,iperf3 需要在主机端先跑 -s adb shell dumpsys wifi | grep -E "mNetworkInfo|SSID|Link speed" adb shell iperf3 -c 192.168.1.100 -t 30 -P 4 # 蓝牙配对和传输状态 adb shell dumpsys bluetooth_manager | grep -E "state|bonded"

iperf3的-t是测试时长,-P是并发流数,单流测不出真实带宽,四流比较接近实际场景。WiFi 的Link speed是协商速率,不是实际吞吐,两者差三到五成很正常,别拿协商速率当结论。测 AP 切换要写个脚本,让设备在两个 AP 之间来回移动,记录每次断开到重连的时间。

SD 卡测速直接上dd,注意块大小要选对,太小测不出带宽,太大容易受缓存影响。写测试用 1MB 块写 256MB,读测试从同一个文件读回/dev/null。

# 外置卡写入,注意会占空间,测完删掉 adb shell dd if=/dev/zero of=/sdcard/dd_test bs=1M count=256 # 读回,去掉缓存影响可以换一个文件读 adb shell dd if=/sdcard/dd_test of=/dev/null bs=1M

dd的输出里看的是最后那行bytes transferred in X secs,直接算 MB/s。内置卡和外置卡的差距通常在两到三倍,如果测出来差不多,八成是路径没走对,或者外置卡根本没挂载成功。

云同步速度这块比较虚,因为它依赖网络和服务端。我的做法是只测本地侧的耗时:从触发同步到数据落盘,不含网络往返。这样至少能把客户端的问题和服务端的问题分开。

4.3 流量与存储:防偷跑和冗余数据清理

流量控制的核心是搞清楚谁在联网。Android 的流量统计有好几个来源,/proc/net/dev是网卡级别的总量,dumpsys netstats是按 UID 分的明细,后者才是定位应用的关键。防偷跑流量不是把网全断了,而是建立基线:正常使用一天消耗多少,超过基线就查。

# 按 UID 看流量明细,和 package 对起来 adb shell dumpsys netstats detail | grep -E "uid=|rxBytes|txBytes" adb shell cat /proc/net/dev # 看某个应用的网络访问记录 adb shell dumpsys package com.demo.settings | grep -i permission

应用访问网络的控制和释放,重点看两处:一是后台唤醒后有没有及时断开连接,二是长连接有没有心跳保活导致的持续小流量。这两类问题在netstats里表现为"流量不大但频次很高",比大流量更难发现。PAD 项目很多不支持蜂窝,只涉及 WiFi 流量,但这不代表可以放松,WiFi 后台偷跑一样耗电。

存储容量的优化分两头:内置存储靠定时清理冗余数据,外置卡靠安全工具及时释放。清理策略要定规则而不是拍脑袋,比如日志文件保留七天、缩略图缓存超过 500MB 清一次、崩溃日志上传后本地删除。规则写进代码里,别指望用户手动清。

# 看存储分布,先定位是谁占的 adb shell df -h adb shell dumpsys diskstats adb shell du -sh /data/log/* 2>/dev/null | sort -h | tail -n 20

du那条命令在设备上跑会比较慢,建议限定目录。dumpsys diskstats给的是分类统计,能快速看出是应用数据、缓存还是系统占用。

4.4 模块功耗在 PAD 上的可做与不可做

功耗这块要先划边界。车机烧油,功耗优化的优先级排在启动速度和稳定性之后;PAD 的 BSP 是内嵌打包的 bin,模块级功耗改不动,只能做版本间的对比验证。这个现实要认,不然会浪费大量时间在改不了的地方。

能做的部分集中在应用层:音视频编解码的参数选择(硬解优先于软解、分辨率匹配屏幕)、背光曲线的调整、LCD 和触摸屏的刷新频率、灭屏后的后台限制。音视频这块的收益最明显,一个 4K 软解能把整机功耗拉高两三瓦,换成硬解加降分辨率能省一半。

# 看功耗相关的系统状态 adb shell dumpsys battery adb shell dumpsys power | grep -E "mWakefulness|Display Power" adb shell dumpsys alarm | grep -c "RTC_WAKEUP"

dumpsys alarm里数一下RTC_WAKEUP类型的闹钟数量,太多说明有应用在频繁唤醒系统,这是待机功耗的常见元凶。dumpsys battery在 PAD 上能看到放电电流,配合固定场景(比如播放视频一小时)能做出可对比的数据。车机没有电池,这个命令的输出意义不大,得靠外部功率计。

BSP 版本对比的做法是:同一场景、同一亮度、同一后台,刷两个版本的 BSP,各测一小时,看电流曲线。差异超过 5% 就值得深挖,但能不能改得动是另一回事,很多时候只能记录在案,等 BSP 供应商下一版解决。

5. 避坑排查:adb 与日志分析里最容易翻车的五个地方

5.1 现象:procrank 每次跑出来的排序都不一样

现象很直观,同一台机器连着跑三次procrank,占用最高的进程每次不同,数字也飘忽。第一反应会怀疑是不是有进程在偷偷涨内存,然后开始到处加日志。

原因是 Android 的内存本身就是动态的,页缓存、共享库的引用计数、后台进程的回收都在实时变化,而且procrank采样的是瞬时值,没有固定窗口。你在 A 时刻采到的 PSS,和 B 时刻的 PSS 本来就不该一样,拿两个不同时刻的数字做对比是无效的。

解决办法是固定采样条件:先am force-stop掉无关应用,等三十秒让系统稳定,然后连续采十次取中位数,而不是取单次值。对比两个版本时,采样脚本、采样次数、间隔、后台应用清单必须完全一致。我后来干脆写了个脚本,把这一整套固化下来,人只负责看结果。

5.2 现象:设置里显示的可用内存和 /proc/meminfo 对不上

cat /proc/meminfo看到 MemFree 加 Cached 有 1.2GB,进设置的应用管理页面一看只剩 900MB,差了好几百兆。新手会以为哪里漏算了。

原因是设置里的可用内存不是简单的 MemFree 加 Cached,它还减掉了一项预留内存(SECONDARY_SERVER_MEM),这部分是留给二级服务应急用的,不参与常规统计。这个计算逻辑在设置应用的源码里,getFreeSize() + getCachedSize() - SECONDARY_SERVER_MEM就是全部。

解决办法是明确口径:做性能分析时统一用/proc/meminfo的原始值,报告里写清楚是哪个口径,别把设置页面的数字和 meminfo 混着用。对外汇报用设置页面的数字(用户能对上),对内分析用 meminfo(能看到细节),两套数字各自成立,但不能交叉引用。

5.3 现象:tombstone 里全是十六进制地址,看不出是哪个函数

打开/data/tombstones/下的崩溃文件,调用栈长这样:#00 pc 00012345 /system/lib/libxxx.so。地址有了,so 也有了,但完全不知道对应哪行代码。

原因是 native 崩溃的栈里只有相对地址,符号信息在编译时生成的符号表里,设备上跑的 so 是 strip 过的。要把地址翻译成函数名,必须拿带符号的 so 做离线解析,而且 so 的版本必须和崩溃时设备上的完全一致,差一个编译选项地址就对不上。

解决分三步:先用addr2line把地址翻成文件和行号,再用nm导出符号表做交叉验证,最后用objdump反汇编看具体指令。

# 三个工具都在 prebuilts/gcc/linux-x86/arm/arm-eabi-4.6/bin 下 arm-eabi-addr2line -e libxxx.so 0x00012345 # 导出完整符号表,-l 带行号 -C 做 C++ 反修饰 -n 按地址排序 -S 显示大小 arm-eabi-nm -l -C -n -S libdvm.so > dvm.symbols # 反汇编,-C 反修饰 -d 只反汇编代码段 arm-eabi-objdump -C -d libc.so > libc.dis

addr2line的-e指定 so 路径,后面跟地址,输出就是文件名和行号。这里有个前提:so 必须是带调试信息的版本,strip 过的不行,所以每次发版都要把符号文件归档,否则线上崩溃只能干瞪眼。nm的-C参数对 C++ 特别重要,不加的话看到的全是修饰过的名字。

5.4 现象:抓 /proc/kmsg 抓出一个空文件,或者命令卡住不动

写了个批处理脚本,里面有一行adb shell cat /proc/kmsg > kmsg.txt,跑完发现文件是空的,有时候命令直接卡死在那儿。

原因有两个层面。第一,重定向的位置错了,>是在主机端生效的,但adb shell后面的整条命令是在设备端执行的,这个写法实际上是把设备端的输出重定向到了设备端的某个路径,主机这边只收到一个空流。第二,/proc/kmsg是阻塞读,读完当前缓冲区之后不会退出,会一直等新日志,脚本就卡住了。

解决办法是改用dmesg:adb shell dmesg > kmsg.txt,dmesg读完就退出,不会阻塞。如果确实需要实时看内核日志,用adb shell cat /proc/kmsg不加任何重定向,让输出直接打到终端,再在主机侧用tee保存。抓上一次重启前的内核日志则读/proc/last_kmsg,这个文件是只读快照,不会阻塞。

5.5 现象:adb root 成功但 remount 失败,改不了系统分区

敲adb root返回restarting adbd as root,看着成功了,接着adb remount报remount failed: Operation not permitted。换成adb shell进去mount -o rw,remount /system也一样失败。

原因是 user 版本的系统默认关闭了 adbd 的 root 能力,adb root只是重启了 adbd 进程,并没有真的提权。另外 Android 高版本引入了动态分区和 verity 校验,/system是只读挂载的,不关掉校验根本改不了。

解决办法分情况:调试版直接adb root && adb disable-verity && adb reboot,重启后再adb remount;user 版本得先刷 eng 或 userdebug 镜像;如果只是临时改个文件,用adb push到/data/local/tmp再adb shell su -c "cp ...",绕开系统分区的只读限制。这一条的血泪经验是:别在量产机上折腾 remount,浪费时间还容易把机器搞成砖。

6. 把八块指标收成一张回归表:一次抓齐日志并做版本对比

做到最后会发现,单点优化不难,难的是不让已经优化过的东西退回去。我的做法是把八个方向压成一张回归表,每个版本刷完机跑一遍,指标掉出阈值就拦住。表的结构很简单:行是维度,列是本次值、上版本值、阈值、结论。

维度指标采集命令阈值
开机速度Launcher 首帧耗时logcat -b events | grep boot_progress小于 18s
响应速度设置冷启动 TotalTimeam start -W小于 800ms
内存管理桌面 PSS 十分钟增幅dumpsys meminfo循环采样小于 15MB
系统稳定性Monkey 两万次无 CRASH/ANRmonkey -s 固定seed零异常
数据传输WiFi 四流吞吐iperf3 -P 4大于 80Mbps
数据流量待机八小时后台流量dumpsys netstats detail小于 5MB
存储容量/data 剩余空间df -h /data大于 2GB
模块功耗视频播放一小时电流dumpsys battery或功率计小于基线 5%

采集侧我写了一个批处理,插上设备一条命令把现场全抓下来,省得每次都手敲。

@echo off REM 一次抓齐开机后的关键现场,输出到以时间戳命名的目录 set STAMP=%date:~0,4%%date:~5,2%%date:~8,2%_%time:~0,2%%time:~3,2% set OUT=perf_%STAMP:\=% mkdir %OUT% adb wait-for-device adb shell dmesg > %OUT%\kernel.log adb logcat -b main,events,system -d -v threadtime > %OUT%\logcat.txt adb shell dumpsys meminfo > %OUT%\meminfo.txt adb shell dumpsys cpuinfo > %OUT%\cpuinfo.txt adb shell procrank -u > %OUT%\procrank.txt adb shell df -h > %OUT%\disk.txt adb shell dumpsys netstats detail > %OUT%\netstats.txt adb shell ps > %OUT%\ps.txt adb pull /data/anr/ %OUT%\anr\ 2>nul adb pull /data/tombstones/ %OUT%\tombstones\ 2>nul adb shell screencap /sdcard/shot.png adb pull /sdcard/shot.png %OUT%\shot.png pause

这段脚本的关键在-d参数,它让 logcat 打完当前缓冲区就退出,不会挂住;2>nul是为了在目录不存在时不报错打断流程。wait-for-device放在最前面,保证设备还没枚举完时脚本会等,而不是直接抓空。时间戳那一行在中文 Windows 上会带空格,所以我做了:\=的替换,避免路径非法。

真正让这张表发挥作用的是对比方式。对比必须满足三个固定:固定采样窗口(都是开机后静置五分钟再采)、固定环境(同一亮度、同一温度、同一后台清单)、固定脚本版本。少一个条件,数据就没有可比性。我吃过一次亏,两个版本的测试间隔了一周,中间换了测试机,结果所有指标都"变好"了,查了半天才发现是硬件差异。

从那以后我每次发版前都强制走一遍这张表,脚本从版本库拉,不用本地改过的版本,采集环境写进报告里。慢是慢了点,但至少不会再出现"上个版本明明优化过,这个版本又退回去"这种没法交代的情况。希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询