刚接手传感器测试的时候我最怕的就是在Android 12设备上跑sensor_fusion脚本手机已经连上adb了adb devices也能看到序列号但脚本推过去一执行要么报Permission denied要么logcat里刷出一堆sensor HAL打开失败要么跑完拉回来的数据全是0。折腾几天才发现Android 12对传感器权限、SELinux策略和高频数据采集的约束比老版本严格得多很多在Android 9/10/11上跑得好好的命令链路到Android 12直接失效。这篇文章我会把在Android 12下通过adb调试sensor_fusion测试脚本的完整过程拆开讲测试脚本的结构和角色划分、从push到执行的完整命令链路、数据异常时的排查思路以及我亲测有效的常见报错解决方案。无论你是刚开始接触传感器算法验证的测试新人还是正在做系统稳定性评估的底层开发都可以把这里的方法直接拿去参考。1. Android 12下跑sensor_fusion测试先弄清楚脚本在干什么再动手1.1 sensor_fusion测试脚本的真实工作流sensor_fusion测试脚本的名字听起来高大上但拆开看就两部分host端控制脚本和device端可执行程序。host端通常是shell或python脚本负责通过adb把device端程序推到手机里、设置执行权限、启动运行、最后把结果文件拉回电脑device端才真正做传感器融合计算把加速度计、陀螺仪、磁力计的原始数据读进来计算姿态角roll/pitch/yaw再把结果写到文件里。我在实际项目里见过的sensor_fusion程序基本会做这么几件事遍历系统里的传感器列表按名字或type定位目标传感器。设定采样率陀螺仪和加速度计一般要求200Hz以上磁力计频率可以低一些。用一个循环持续读取若干秒同时对三轴数据做低通滤波、互补滤波或卡尔曼滤波。把原始数据和融合后的姿态数据按时间戳写入CSV或txt文件。搞清楚这个流程后调试方向就明确了凡是跑不起来优先查文件系统权限、架构和SELinux凡是跑起来但数据不对优先查sensor HAL是否正常上报、采样率是否被限制、后台调度是否被杀。1.2 adb版本与授权检查最容易翻车的第一个环节很多人一上来就敲adb devices看到设备号显示unauthorized或offline第一反应是换线、换口其实近八成问题出在授权或adb版本上。Android 12要求adb client版本不能太低否则设备管理和授权流程会出现兼容问题。我的建议是直接用Android Studio自带的platform-tools路径类似/Users/你的用户名/Library/Android/sdk/platform-tools下的adb版本至少是1.0.41。太老的adb连Android 12设备时授权弹窗可能根本不出现。如果你用的是macOS且没装过homebrew还可能遇到系统自带adb太旧的问题。别用/usr/bin/adb那多半是老掉牙的版本去platform-tools目录执行业务时请用绝对路径。先跑一遍adb kill-server adb start-server重置adb服务然后拔插一次USB线手机通知栏里选择“允许USB调试”。如果还是unauthorized就到开发者选项里点“撤销USB调试授权”然后重新插线手机会再次弹出指纹确认窗口。我碰到过Android 12工程机因为刷过特殊userdebug固件指纹确认弹窗不出现的情况那种时候可以直接改~/.android/adbkey.pub但日常调试用不上这招只有当设备完全不受控才考虑。1.3 Android 12特有的传感器权限与SELinux约束Android 12最让人头疼的不是API变化而是对高频传感器的访问限制。系统会校验调用方是否处于前台且持有的权限满足要求比如高频采样率传感器需要HIGH_SAMPLING_RATE_SENSORS权限。很多sensor_fusion测试程序直接在shell下运行shell进程默认不具备这个权限于是表现为“程序起来但数据量极少”或“sensor open成功但事件一直不来”。另一个坑是SELinux。Debug版本的手机可以执行adb shell setenforce 0临时关闭SELinux我自己定位问题时也切过但这里说句实在话别在日常测试中关SELinux。因为同一台设备上可能还跑着其他系统服务关掉后sensor HAL的行为会脱离真实约束测出来的融合数据参考价值会打折扣。正确姿势是先保持enforcing看logcat和dmesg里的avc denied日志定位到具体策略再去放开那一条。注意Android 12上如果计划长时间跑高频传感器采集建议把测试程序放进前台进程或者把host端命令设计成adb shell start某个壳组件的方式让sensor事件能持续投递。纯粹在shell里跑后台任务的方案在Android 12上会明显感觉到数据中断。2. 从push到执行的完整命令链路放置路径、权限、运行与数据落盘2.1 为什么测试程序必须放在/data/local/tmp把sensor_fusion测试程序推到手机上时放置路径不是随便选的。虽然sdcard/Download或/data/local/tmp都能放文件但对于shell域执行环境来说首选永远是/data/local/tmp。原因有两个第一/data/local/tmp是shell用户默认有读写执行权限的目录不需要额外处理就能运行普通二进制第二/sdcard路径走的是FUSE存储挂载SELinux安全上下文会变成fuse域很多测试程序从fuse域执行动态库时会被拒绝报错形式千奇百怪。所以别嫌麻烦统一用adb push /data/local/tmp/。还有一点如果测试程序依赖动态库比如libc_shared.so或libutils.so要把这些so文件一块push到同一个目录否则执行时会出现cannot locate symbol或library not found。Android 12的NDK环境编译出的二进制常见依赖是libc_shared.so没带上它加载阶段就挂了。2.2 标准执行序列push、chmod、确认架构、运行我在日常调试中sensor_fusion测试程序的启动序列是这样的# 1. 连接设备确认能看到序列号 adb devices # 2. 推送测试程序和依赖so adb push sensor_fusion_test /data/local/tmp/ adb push libc_shared.so /data/local/tmp/ # 3. 加执行权限 adb shell chmod 755 /data/local/tmp/sensor_fusion_test # 4. 确认架构是arm64 adb shell file /data/local/tmp/sensor_fusion_test # 预期输出: ELF 64-bit LSB executable, ARM aarch64 # 5. 后台执行日志输出到文件 adb shell nohup /data/local/tmp/sensor_fusion_test -t 30 -o /data/local/tmp/result.csv /data/local/tmp/run.log 21 # 6. 确认进程在跑 adb shell ps -A | grep sensor_fusion_test第5步里加了nohup和这是Android 12上的关键经验。如果直接adb shell /data/local/tmp/sensor_fusion_test一旦adb的连接因为USB断开或超时中断子进程会被挂断信号杀掉。用nohup转后台运行后进程就能脱离adb shell的会话生命周期长时采集才不会被意外打断。第4步的file命令我建议每次换编译版本后都跑一遍因为Android 12设备几乎都是64位系统如果一个x86或armeabi-v7a的测试程序被推到arm64设备上执行时不会得到友好提示而是直接报/app_data/local/tmp/sensor_fusion_test: not executable: magic 7F 45 4C 46或者cannot execute binary file: Exec format error。这种错最容易让人怀疑人生其实只是架构不匹配。2.3 怎么判断程序真的在采数据而不是假死程序启动后不要只看进程存在就以为完事。传感器测试经常出现“进程活着但数据没写”的假死状态。我的三板斧看输出文件大小是否增长每过几秒执行adb shell ls -l /data/local/tmp/result.csv如果文件size在增长说明有数据落盘。看logcat里的SensorService活动记录执行adb logcat -d -v threadtime | grep -i sensor能看到sensor状态的改变记录比如某个sensor enable、采样率设定等。看CPU占用率adb shell top -n 1 | grep sensor_fusion如果进程CPU占用一直在0%大概率卡在等待数据或I/O上。这三招能帮你快速判断问题是出在前半段环境/执行还是后半段采样/融合。3. 数据异常时的排查链路从sensorservice到logcat到dmesg逐层定位3.1 先用dumpsys sensorservice确认系统侧情况当测试程序能运行但数据不对时我第一步一定先执行adb shell dumpsys sensorservice这个命令会输出系统里所有传感器列表、每个sensor的属性type、maxDelay、minDelay、fifoMaxEventCount、当前哪些进程在监听、实际的采样率设置。重点看三项目标传感器是否在列表里有的Android 12定制ROM会裁掉磁力计或者把陀螺仪驱动挂载失败列表里根本没有对应条目。是否有sensor处于active状态输出里能看到类似Active sensors:的栏目如果程序已经打开了传感器但这里没有记录说明程序打开sensor失败。实际事件上报率dumpsys会显示selectedDelay之类的参数如果设置的是5000微秒200Hz但实际输出显示最大事件间隔明显偏大说明底层驱动或采样队列有问题。dumpsys sensorservice还能看到一些HAL层的错误信息。如果HAL初始化失败通常会有SensorDevice::connect或batch相关的报错文本接下来再去logcat确认。3.2 logcat抓取与关键字过滤策略调试sensor程序时我最常用的logcat组合是# 清空旧日志执行测试然后抓取相关日志 adb logcat -c adb shell nohup /data/local/tmp/sensor_fusion_test -t 10 -o /data/local/tmp/result.csv /data/local/tmp/run.log 21 sleep 5 adb logcat -d -v threadtime | grep -iE sensor|Sensors|HAL|fusion|avc|denied-v threadtime会输出每个日志的线程名和时间这对区分HAL层日志和framework层日志很有帮助。常见的关键字组合Fatal signal程序崩溃多半是空指针或数组越界。avc: deniedSELinux拦截后面会跟目标上下文和源上下文。SensorDevice::connect failedHAL层连接失败需要看是权限问题还是驱动问题。write blocked或buffer full采集缓冲区溢出说明采样率太高或处理跟不上。在Android 12上还特别留意HIGH_SAMPLING_RATE相关的日志系统可能直接拒绝高频采样请求并打出一条权限拒绝记录。这种问题在Android 9上根本不会出现属于版本演进带来的新坑。3.3 用dmesg的avc denied日志精确锁定SELinux拦截logcat里如果看到了avc: denied接下来要拿细节adb shell dmesg | grep -i avc | tail -50输出里会包含类似这样的信息avc: denied { read } for pid1234 commsensor_fusion_test namesensor_xxx devsysfs scontextu:r:shell:s0 tcontextu:r:system_sensor:s0 tclassfile permissive0这里的comm是你的程序名scontext是源上下文tcontext是目标上下文。android 12的userdebug版本policy里有不少测试用allow规则通常不会拦截shell域读取sensor节点但如果你在定制ROM上遇到拦截且确认不是恶意行为就可以告诉系统固件那边的人让他们在sepolicy里补一条对应allow规则。这比直接setenforce 0要专业得多也不影响整机测试环境的一致性。4. 高频报错根因与完整排雷记录4.1 adb devices显示unauthorized或offline这个问题出现的频率非常高在Android 12的工程机和部分出厂机上尤其明显。终端里执行adb devices序列号后面跟着unauthorized说明adb服务已经发现设备但设备的调试授权还没通过。解决办法按顺序试手机开发者选项里关闭再开启“USB调试”。点“撤销USB调试授权”重新插拔USB线并在手机弹窗上勾选“始终允许”。如果还是不弹窗adb kill-server adb start-server重启本机adb服务。更换USB线或直连电脑主板接口避免前置集线器供电或信号干扰。实在没办法可以检查电脑上~/.android/adbkey.pub是否存在有时误删或权限异常会导致adb的RSA密钥匹配失败删除~/.android目录后重连这个操作会清空所有设备的调试记录谨慎执行。offline的情况则多半是adb版本与设备端adbd不匹配或者是无线调试模式下信号不稳。如果坚持用无线调试Android 12的配对码机制要求先adb pair一次之后再adb connect IP:端口。跑sensor测试我还是建议有线稳定性和传输速度都更好。4.2 执行报Permission denied的三层原因adb shell chmod 755 /data/local/tmp/sensor_fusion_test之后执行仍然报Permission denied从三个层面排查执行位没给到位确认ls -l输出里r-x存在Android默认的umask可能不正常直接chmod 755即可。文件系统只读挂载如果之前做过adb remount或系统分区被锁/data也可能出现挂载异常。执行adb shell mount | grep /data看ro/rw。SELinux策略禁止Android 12的userdebug固件一般允许shell域执行/data/local/tmp下的普通程序但定制固件或某些安全加固ROM会拒绝。这时dmesg里通常有avc denied记录按照上面提到的方式确认。如果程序本身没问题但启动时访问某个配置文件失败也可能是配置文件的安全上下文不对。把配置文件也放到/data/local/tmp并保持同样域通常能绕开这个问题。4.3 动态库找不到或Segmentation fault编译sensor_fusion测试程序时最常见的两个运行期错误CANNOT LINK EXECUTABLE: could not load library libc_shared.so或Fatal signal 11 (SIGSEGV) at 0x0000000000000000第一个错误很明确依赖的动态库没推上去或者推了但不在同一个目录。要注意Android 12的dynamic linker默认只搜索以下路径/vendor/lib64、/system/lib64、/apex/com.android.runtime/lib64等不会自动去/data/local/tmp找。所以要么把so放到系统库里要么在编译时用-Wl,-rpath,/data/local/tmp指定运行时搜索路径要么在执行时设置LD_LIBRARY_PATH。我测试时最常用的办法是adb shell LD_LIBRARY_PATH/data/local/tmp /data/local/tmp/sensor_fusion_test -t 10第二个Segmentation fault多半是sensor返回的数据格式和程序预期不一致。比如Android 12某些sensor的report mode被设置成特殊模式或者多路传感器同时打开时线程同步没做好。这种情况建议先用一个固定的、已知良好的sensor数据文件做离线回放确认程序逻辑没问题再跑到真实设备上联调能省很多时间。4.4 sensor输出全为0或数据量远低于预期程序跑完result.csv里每个时间戳的数据都是0.000000这是我在Android 12上遇到的第二大类问题。原因主要有这几个传感器没真正打开程序可能open失败但没检查返回值照样往下跑输出自然全0。在代码里对每个sensor的open结果做严格判断失败就打印error code。采样率设置被系统拒绝Android 12对高频传感器的权限校验更严格如果以shell身份运行且未经过前台权限申请200Hz以上的采样率请求可能被降级或拒绝。可以先把采样率调低到100Hz试试如果数据恢复正常就能确定是权限导致。传感器事件类型不匹配有的程序假定sensor事件里value[0]是timestamp实际上Android 12的传感器事件结构里时间戳在另一个字段。这种问题引起的全0很隐蔽需要在程序里把每个事件的所有字段都打印出来对照一次。数据落盘路径没写对程序把结果写到了运行目录但host端从另一个路径pull自然拿不到。建议统一把输出路径写死到/data/local/tmp/下。4.5 高频报错速查对照表下面这个表是我在实际调试中遇到频率最高的几类报错基本可以当快速索引用。报错现象优先排查项常见根因解决路径adb devices显示unauthorized授权弹窗、adb版本未授权或密钥失配撤销授权、重启adb、~/.android清理后重连执行报Permission denied文件权限、SELinux执行位不足或avc拦截chmod 755、检查mount ro/rw、确认sepolicy策略cannot link library动态库缺失、路径不对依赖so未推送或linker搜不到推送so、设置LD_LIBRARY_PATH、指定rpathExec format error二进制架构编译目标与设备不匹配重编arm64版本adb shell file确认架构sensor输出全0sensor打开状态、采样率open失败未检查、权限拒绝采样率严格检查返回值、调低采样率、补充前台权限Segfault空指针、结构体错位代码或数据格式不匹配离线回放定位、多打印调试字段5. 日常调sensor_fusion时好用的几组命令和验证技巧5.1 我给自己的adb命令组合调试时间长了我攒了一套每次都要用到的命令直接做成脚本不用每次敲。#!/bin/bash # 设置设备并检查连接 ADBadb $ADB root $ADB wait-for-device # 关闭selinux仅定位时用 $ADB shell setenforce 0 # 清理旧数据 $ADB shell rm -f /data/local/tmp/result.csv $ADB shell rm -f /data/local/tmp/run.log # push并执行 $ADB push sensor_fusion_test /data/local/tmp/ $ADB push libc_shared.so /data/local/tmp/ $ADB shell chmod 755 /data/local/tmp/sensor_fusion_test $ADB shell nohup /data/local/tmp/sensor_fusion_test -t 30 -o /data/local/tmp/result.csv /data/local/tmp/run.log 21 # 等10秒后抓日志 sleep 10 $ADB logcat -d -v threadtime | grep -iE sensor|fusion|avc|denied | tail -80 $ADB shell ps -A | grep sensor_fusion_test || echo 进程已退出无线调试是另一种加分项。Android 12的无线调试入口在开发者选项里打开后会看到“使用配对码配对设备”的选项手机端会给出IP和6位配对码电脑端先执行adb pair ip地址:端口输入配对码然后adb connect ip地址:端口。无线调试对跑长时sensor采集特别友好不用一直占着USB口速度也能满足大部分测试需求。不过要注意无线调试连接后有些低端手机可能会因为省电策略在熄屏状态暂停sensor事件传递测试时建议开启“不锁定屏幕”或充电状态下运行。5.2 验证融合结果真实性的小技巧数据拉回来后除了看CSV里有没有非0数字还要确认数据合理性。我最常用一个土办法把手机平放在桌上运行20秒拉回数据看roll和pitch的方向。设备静止时加速度计三轴输出应该近似为(0, 0, 9.8)融合后的roll/pitch应该稳定在某个小角度附近陀螺仪的角速度积分不能无限漂移。如果看到融合角速度在手机静止时还持续增大那说明融合算法或传感器零偏校准有问题。另一个技巧是用adb shell uiautomator dump辅助判断前台场景。如果你把sensor_fusion测试程序封装成了一个带UI的应用先用这个命令导出当前界面的UI层级确认应用真的处于前台。Android 12对后台传感器访问的限制很强如果应用退到后台传感器事件可能被系统整体暂停采集到的数据量会明显减少。这时候不是测试程序的问题而是Android的电源管理机制在起作用。5.3 Android 12版本特定问题和新版本系统变化提示Android 12是分界点。在Android 9/10/11时代shell进程调用sensor HAL通常不需要额外权限后台进程也能持续拿到高频数据很多测试程序为此设计成了headless二进制。到了Android 12权限校验前移到framework层运行环境和老系统差别很大。如果项目里同时维护多个Android版本建议在测试程序里加一个运行权限检测启动时读取Build.VERSION.SDK_INT如果是31及以上自动采用前台服务模式或申请权限否则走传统模式。Android 13和Android 14进一步收紧了传感器权限尤其是针对低频传感器的后台访问限制做了更多细分。在Android 12下调通的方案升到Android 13后可能又会冒出新的permission denied或数据断流问题。遇到版本升级导致的老脚本失效优先去查对应版本的SensorService源码和权限策略变更记录比在论坛里找答案快得多。我在实际项目里的体会是sensor_fusion测试脚本调试本身不算难难的是每次系统升级都要重新确认一遍权限边界。把上面这些排查步骤固化成自己的调试流程遇到报错先按链路走一遍基本能覆盖绝大部分Android 12下的问题。最后再分享一个小习惯调试过程中所有命令和对应的系统输出都单独存一份日志文件归档到项目目录里。后续复现问题或写测试报告时这些原始记录比任何口头描述都有说服力。
