ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Android Framework 默认开启 adb:user 版全链路配置

Android Framework 默认开启 adb:user 版全链路配置 刷完一台自编译的 user 版本机器插上 USB 线敲adb devices列表空空如也接着就得捏着鼻子进设置里翻到开发者选项把USB 调试手动拨开再回头敲一遍命令——这个动作如果你一天要重复二十次人是要疯的。Android Framework 常见解决方案里关于默认开启 adb的这一条本质就是为了干掉这个重复动作让系统在第一次开机、还没进过任何设置页的时候adbd 就已经跑起来、persist.sys.usb.config里已经带上了adb、Settings.Global.ADB_ENABLED已经是 1。这事听起来像是改一行配置就完事但我踩过的坑告诉我没那么简单。它横跨了构建变体build variant、属性系统property service、SettingsProvider 默认库、init 对 USB gadget 的配置、以及 adbd 自身的鉴权这几层任何一层没对齐你看到的现象都是adb 连不上这同一句话但根因完全不是一回事。下面我按为什么这么设计 → 每一层改哪里 → 怎么排查 → 版本差异的顺序把这套东西完整拆一遍代码和路径都是 AOSP 里能直接对上的。1. user 版凭什么默认把 adb 关掉三种构建变体的行为差异在动手改之前得先接受一个前提默认关掉 adb 不是 bug是刻意设计。AOSP 把adb当作一个调试通道而调试通道意味着可以拿到一个 shell、可以run-as进应用私有目录、可以读写/data/local/tmp。让这个通道在零售机器上一开机就敞开安全性上是说不过去的。所以官方给出的逻辑是开发态默认开量产态默认关。这个开发态/量产态的分界就是构建变体。1.1 eng、userdebug、user 三者的默认差异AOSP 的构建变体由TARGET_BUILD_VARIANT决定常见三个值eng、userdebug、user。它们对应的默认行为大致是这样的变体ro.debuggablero.secureadb 默认状态是否需 RSA 授权eng10默认开启不需要userdebug11默认开启需要弹窗确认user01默认关闭需要弹窗确认ro.debuggable这个属性非常关键。它是很多系统组件判断我现在是不是运行在可调试环境的依据adbd会看它决定要不要接受无认证连接、logd会看它决定日志级别、很多#ifdef之外的运行时判断也依赖它。ro.secure则决定adbd以什么身份运行——ro.secure0时adbd直接以 root 身份跑ro.secure1时降权到 shell。所以你会看到一种很常见的做法为了省事把user版本硬改成ro.debuggable1。我不太推荐这么干原因后面第 6 节会展开因为它会连带把一堆安全和性能相关的行为一起打开副作用面远超让 adb 能连上这个目标。1.2 adb 使能其实由两套并行的机制在管这是最容易混淆的地方。很多人以为adb 开没开是一个布尔值实际上在系统视角里有两条并行的链路属性链路persist.sys.usb.config里有没有adb这个 function。它直接决定 USB gadget 的 configfs 里挂不挂adb这个功能节点是硬件层面的开关。设置链路Settings.Global.ADB_ENABLED这个数据库字段是 0 还是 1。它是 UI 上那个USB 调试开关的状态来源同时UsbDeviceManager会监听它的变化再反过来去写属性链路。正常的人机交互是你在设置里拨开关 → 写ADB_ENABLED→UsbDeviceManager收到 ContentObserver 回调 → 改persist.sys.usb.config→ init 收到 property trigger → 重新配置 USB gadget。这是一条完整的因果链。问题来了如果你只在system.prop里塞了persist.sys.usb.configadb但ADB_ENABLED还是 0开机时UsbDeviceManager初始化会读到 0然后它会把属性又改回去把 adb 从 function 列表里摘掉。这就是我明明改了属性开机后getprop一看又变回 mtp 了这类诡异现象的来源。反过来如果你只改了ADB_ENABLED默认值某些平台上因为属性没提前写好、启动时序赶不上也一样连不上。结论很直白两条链路都要改而且要保证它们开机后不会互相打架。下一节先讲属性这一侧。2. 属性链路ro.adb.secure与persist.sys.usb.config到底谁在管谁属性这一侧要改的东西不多但每一个都有明确的职责改之前必须搞清楚它影响的是哪一环。2.1ro.adb.secure0与那条adb unauthorized弹窗从 Android 4.2.2 开始adb 引入了基于 RSA 密钥对的授权机制。第一次连接时adbd会检查$ADB_VENDOR_KEYS和/data/misc/adb/adb_keys里有没有你这台主机的公钥没有就返回unauthorized同时在设备上弹出是否允许 USB 调试的对话框点允许之后公钥才会被写进adb_keys。这个机制受ro.adb.secure控制ro.adb.secure1user/userdebug 默认启用授权需要弹窗确认。ro.adb.secure0adbd跳过授权检查连上就是设备。如果你做的是一台自己用的调试机或者产线上的测试机ro.adb.secure0基本是标配。否则每次换一台电脑、每次恢复出厂设置都得去屏幕上点一下那个弹窗机器要是没屏幕比如某些工控板、车机这就直接卡死了。改的位置在设备目录下的system.prop比如device/vendor/device/system.propro.adb.secure0注意ro.前缀的属性是只读且只读一次的由 init 在很早的阶段从build.prop其实是/system/build.prop、/vendor/build.prop等合并而来加载所以它必须走构建系统进build.prop在init.rc里用setprop是改不动的。2.2persist.sys.usb.config与UsbDeviceManager的联动persist.sys.usb.config描述的是当前 USB 口对外暴露哪些功能取值是逗号分隔的 function 列表常见的有adb、mtp、ptp、midi、rndis等。比如手机插上电脑默认是mtp,adb纯充电模式可能是空的。要让开机就带 adb在同一个system.prop里加上persist.sys.usb.configadb或者你想保留 MTPpersist.sys.usb.configmtp,adb这里有个细节值得说清楚persist.前缀的属性会被 property service 持久化到/data/property/目录下。这意味着它一旦被写过就会一直生效重启也不会丢。所以你第一次刷机改完是好的后面无论怎么改system.prop只要用户没清 data/data/property/persist.sys.usb.config里的旧值就会覆盖你的新值。这个坑我在第 4 节还会再提一次因为它是最容易让人怀疑人生的地方。另外一个容易忽略的点persist.sys.usb.config最终是被UsbDeviceManager读写的。你在system.prop里设的值只是给第一次开机、属性还没被持久化过时兜底用的。真正的权威状态是运行时由UsbDeviceManager依据ADB_ENABLED计算出来的。所以它俩必须一致否则就打架。在frameworks/base/services/usb/java/com/android/server/usb/UsbDeviceManager.java里你能看到这个类的构造函数里会去读一次初始值mAdbEnabled (Settings.Global.getInt(mContentResolver, Settings.Global.ADB_ENABLED, 0) 0);同时它注册了一个AdbSettingsObserverclass AdbSettingsObserver extends ContentObserver { Override public void onChange(boolean selfChange) { boolean enable Settings.Global.getInt(mContentResolver, Settings.Global.ADB_ENABLED, 0) 0; mHandler.sendMessage(MSG_ENABLE_ADB, enable); } }这两段代码就是两条链路会互相打架的源代码级证据。mAdbEnabled初值取自设置数据库observer 又会在数据库变化时反过来改属性。想让属性不被覆盖就必须让数据库那边的默认值也是开。这就引出了第三节。3. 让USB 调试开关一开机就是亮的改 SettingsProvider 默认值如果你只改了属性、没改设置数据库会出现一个很微妙的状态adb 可能能用但设置里那个开关是灰的关闭状态。用户一旦手贱去拨一下再拨回来UsbDeviceManager就会把你辛苦设置的属性覆盖掉。所以正确做法是把默认值也改掉让 UI 状态和属性状态一致。3.1def_adb_enabled在 defaults.xml 里的位置打开frameworks/base/packages/SettingsProvider/res/values/defaults.xml你能找到这么一行bool namedef_adb_enabledfalse/bool改成true即可。这个文件是SettingsProvider首次创建数据库时用的默认值来源系统里各种出厂默认都是从这里取的。改成true之后设备第一次开机、settings.db第一次被创建时ADB_ENABLED就被填成 1。顺手建议把开发者选项的总开关也打开否则用户在设置界面里根本看不到USB 调试这一项虽然功能上 adb 已经能连了但为了调试方便还是开了好bool namedef_development_settings_enabledtrue/bool3.2DatabaseHelper里那行loadBooleanSetting光改 XML 不够得确认它真的被加载了。进frameworks/base/packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java在loadGlobalSettings老版本是loadSecureSettings里能找到类似这样的一行loadBooleanSetting(stmt, Settings.Global.ADB_ENABLED, R.bool.def_adb_enabled);这说明def_adb_enabled这个资源确实会被写进Settings.Global.ADB_ENABLED。在 Android L 之前ADB_ENABLED一度属于Settings.Secure对应的加载函数是loadSecureSettings。如果你在改一个老平台比如 Android 4.4发现loadGlobalSettings里找不到这一行就去loadSecureSettings里翻。注意这里改的是数据库的初始值不是每次开机强制置位。如果用户在运行期间手动关掉了 USB 调试关掉的状态会被持久化进settings.db重启后依然是关的。这是符合预期的行为不要试图用这行配置去实现永远开。3.3 改完为什么必须清 data 或重新编译SettingsProvider的默认值只在数据库不存在的时候生效。如果你是在一台已经开机过的机器上 adb push 替换了SettingsProvider.apk那数据库早就存在了def_adb_enabled根本不会被执行到你会以为改动没用。所以验证时的正确姿势有三种恢复出厂设置或者手动删掉/data/system/users/0/settings_global.xml不同版本路径略有差异让它重建直接adb shell settings put global adb_enabled 1手动写一下应急验证整包重新编译烧写一枚干净镜像。我在产线做测试固件时习惯把这三步写成一个脚本烧写 → 清 data → 自动settings get global adb_enabled回读确认。省得每次靠肉眼翻设置页。4. 只改属性不改设置会怎样一次真实的时序踩坑记录这一段我想完整复现一次我遇到的排查过程因为这个现象特别容易把人带偏。4.1 现象开机后adb devices是空的通知栏开关却是灰的当时的情况是我在system.prop里加了persist.sys.usb.configadb和ro.adb.secure0烧完机开机adb devices什么都没有。第一反应是属性没生效于是adb shell进不去只能串口里敲getprop persist.sys.usb.config——结果打印出来是mtp我设的adb不见了。4.2 排查链路从getprop到dmesg到 configfs我按这个顺序往下查getprop | grep adb确认ro.adb.secure是 0ro.debuggable是 0当时是 user 版。ro.adb.secure0生效了说明build.prop加载正常。getprop persist.sys.usb.config看到是mtp说明属性被别的进程覆盖过。dmesg | grep -i usb看到 USB gadget 初始化时只挂了 mtp 相关节点没有 adb 的 function。ls /config/usb_gadget/g1/functions/只有ffs.adb没被 link 进去印证了第 3 步。settings get global adb_enabled返回 0。到这里就基本锁定了。4.3 根因UsbDeviceManager的初始值读取时机结合第 2.2 节那段源码就说得通了。UsbDeviceManager启动时先读ADB_ENABLED读到 0于是mAdbEnabledfalse接着它走getDefaultFunctions()计算默认 function 列表因为mAdbEnabled是 falseadb 被排除然后它把这个结果写回persist.sys.usb.config我原来设的adb就被覆盖成mtp了。整个过程里我改的属性只在UsbDeviceManager还没起来的短暂窗口里存在过。修法就是第 3 节说的把def_adb_enabled也改成 true让ADB_ENABLED初值就是 1。改完之后UsbDeviceManager启动时读到 1mAdbEnabledtrue它算出来的默认 function 列表里自然就带 adb属性也是它自己写进去的不存在被覆盖的问题。这个坑给我的教训是改属性只是喂给系统一个初始输入真正决定结果的是UsbDeviceManager的状态机。要改的是状态机的输入源设置数据库而不是它输出的结果属性。很多人卡在这就是因为把因果搞反了。5. 版本与平台差异从 Android 4.4 到新版改法差在哪这套逻辑不是一成不变的Android 每个大版本都在动 USB 和 adb 这块的实现。如果你手上平台跨度比较大下面这张对照表能帮你少走弯路。5.1 Android 4.xpersist.service.adb.enable时代在 Android 4.x 上adb 的持久化开关是persist.service.adb.enable不是persist.sys.usb.config。当时UsbDeviceManager里还留着对它的兼容读取代码里能看到一个LEGACY_PERSIST_ENABLE_ADB之类的常量。这个时期还有个persist.service.adb.enable1配上persist.service.usb.setting的组合写法。如果你在维护一个 4.4 的老车机项目翻资料时看到persist.service.adb.enable不要以为它是错的那是那个时代的正确写法。5.2 Android 5.0 到 9.0persist.sys.usb.config成为主流从 L 开始persist.sys.usb.config成为标准Settings.Global.ADB_ENABLED也从 Secure 迁到了 Global。这个区间的改法就是我上面第 2、3 节讲的也是流传最广的版本网上大部分教程都基于这个阶段。5.3 Android 10 以后configfs 与 ffs 的时序变化Android 10 之后USB gadget 全面转向 configfs FunctionFSinit.usb.rc里的 trigger 从原来简单的on property:sys.usb.configadb变成了要等sys.usb.ffs.ready和sys.usb.configfs一起就绪。这带来一个新的现象即使属性都对如果ffs.ready迟迟不来比如 daemon 启动慢开机瞬间 adb 也会短暂连不上要等几秒。版本区间持久化属性设置字段归属备注4.xpersist.service.adb.enableSettings.Secure老平台专用5.0-9.0persist.sys.usb.configSettings.Global主流改法10 以后persist.sys.usb.configSettings.Global需关注 configfs 时序在这个阶段如果你对开机后两三秒内必须能连上 adb有硬性要求产线自动化测试经常有这种需求光靠默认值是不够的得结合init.rc里的on boot阶段做兜底比如在一个on property:sys.boot_completed1的 trigger 里再确认一次功能列表。但要注意别写成死循环式的反复 setprop会拖慢开机。6. 默认开 adb 的代价以及几个更稳妥的替代方案把上面所有改动都做上去adb确实一开机就是通的。但作为一个要在各种项目里反复交付的人我必须把这事儿的代价讲清楚不然很容易在量产阶段翻车。6.1 安全与认证上的现实权衡ro.adb.secure0意味着任何物理接触设备的人插上线就能拿到一个 shell。对内部测试机没问题对出货机器就是明摆着的风险。所以在真机上我通常只做两件事把def_adb_enabled设成 true这样开发者选项里开关是亮的但ro.adb.secure保持 1仍然需要 RSA 授权同时把授权公钥预置进去。预置公钥的做法是把测试机的~/.android/adbkey.pub内容写进设备的/data/misc/adb/adb_keys这样adbd启动时就能直接匹配上不用弹窗。这个文件的 SELinux 上下文是adb_keys_file写的时候要注意权限一般用 init 的on post-fs-data阶段从/vendor/etc/adb_keys拷过去比较稳# init.device.rc on post-fs-data copy /vendor/etc/adb_keys /data/misc/adb/adb_keys chmod 0640 /data/misc/adb/adb_keys chown system shell /data/misc/adb/adb_keys这样既省了弹窗又没把认证彻底关掉——公钥对不上照样连不进来。6.2 只在 userdebug 上开或加隐藏入口如果你能控制构建流程最干净的办法是只在 userdebug 变体上默认开 adbuser 变体保持关闭。可以在设备目录的Android.mk或者.mk里用条件判断ifeq ($(TARGET_BUILD_VARIANT),userdebug) PRODUCT_PROPERTY_OVERRIDES \ persist.sys.usb.configmtp,adb endif量产走 user 变体调试走 userdebug物理隔离谁也不用担心谁。这是我在正式项目里最推荐的方案。如果确实必须在 user 版上开比如某些没有开发者模式的定制设备退一步的做法是加一个隐藏入口比如连续点击某个版本号 N 次之后才把ADB_ENABLED打开同时预置公钥。这样既保留了可调试性又不会让随便谁插上线就能进去。6.3 一个交付前的自检清单每次改完这套东西我都会跑一遍下面这几条确认没有漏项getprop ro.adb.secure是否符合预期getprop persist.sys.usb.config是否包含adbsettings get global adb_enabled是否为 1settings get global development_settings_enabled是否为 1拔插一次 USB 线看功能列表是否稳定排查 configfs 时序问题清一次 data 再开机验证默认值路径确实生效而不是靠上一次遗留的持久化属性在撑着。第 6 条是最容易被忽略的。我自己就吃过一次亏本地测得好好的一上产线全新机器就全空——因为我之前那台机器上的/data/property/persist.sys.usb.config早就被写成了正确的值我根本没验证到默认值那条路。做默认值这类改动必须用干净镜像验证这一条我踩过不止一次。
返回列表