最近一直在跟某项目的系统定制需求较劲,客户提了一个很典型的诉求:“指纹这块的config_biometric_sensors能不能做成一份common默认配置,全系机型一套通用,省得每款机器都单独调?”我盯着这个配置名看了半天,第一反应是能省不少事,但真正拆完一轮之后发现,这个问题远没有表面看起来那么简单。Android R之后,生物识别早就不只是指纹驱动挂没挂上的事,而是系统服务、传感器描述、HAL进程、设备树多点联动的一套完整链路。这篇就把我实际做过的通用定制方案掰开揉碎讲清楚,该踩的坑一个不落。
先给结论:“common配置统一”可以做,但讲究怎么个common法。无脑把所有机型的生物识别信息塞进一份默认配置文件,大概率换来的不是省事,而是A机型刚适配完,B机型识别率下降、C机型直接报“指纹硬件不可用”。如果你正好也在做Android R(Android 11)的BSP/框架定制,或者准备把手上的设备配置抽成公共方案,这篇文章应该能帮你少走几趟弯路。
1. 先弄清config_biometric_sensors到底控制了什么
1.1 Android R的生物识别架构和旧版本有什么区别
在Android 9、10时代,系统里提到生物识别,大家第一反应是FingerprintService,上层Settings里指纹入口调用的就是它,底层直接跟指纹HAL的fingerprint.hal交互。到了Android R(Android 11),Google把这块做了重构,把指纹、人脸、虹膜等统一收拢到了更上层的生物识别框架里,不再是一根管子插到底。
这套新框架里有几个关键角色:系统侧的BiometricService负责统一管理所有类型的生物识别传感器,向下通过IBiometricsFingerprint这类HAL接口跟厂商驱动通信,向上给应用提供BiometricPrompt等统一API。而设备配置里描述“这台机器有哪些传感器、传感器类型是什么、行为怎么表现”的,正是config_biometric_sensors这一族配置。
说直白点,Android R之后,系统想知道“你的手机有几个指纹、有没有人脸、传感器的行为逻辑是什么”,不是自己去猜,而是靠一份XML来告诉它。配置文件要是写得不全、写得不对,上面的服务再强大也白搭。
1.2 配置文件在代码树里的真实位置与加载关系
在AOSP默认路径下,这份传感器描述XML位于frameworks/base/core/res/res/xml/目录,常见叫法类似config_biometric_sensors.xml。单看AOSP里的默认版本,内容非常朴素,基本只保留Google参考设备用的Sensor描述,绝大部分手机厂商的设备指纹信息是不会直接出现在AOSP里的。
厂商在定制时,一般不会去直接改frameworks/base的源码,而是走Overlay资源覆盖机制。设备树目录下建一层Overlay,例如:
device/某厂商/某项目/overlay/frameworks/base/core/res/res/xml/config_biometric_sensors.xml编译时,RR ROM资源会跟设备Overlay合并,原包里的同名XML被替换成厂商自定义版本,系统启动时读到的就是这份新配置。整个链路里的关键点在于:这份XML只是资源文件,它不会像驱动一样直接控制硬件,它描述的是“系统眼里有什么硬件”。真正让硬件动起来的,还是底层的指纹HAL和内核驱动。
所以你看,一个看似简单的配置文件,实际影响范围是系统服务、Settings显示、快捷设置、应用权限等多个模块。把几款机型的配置硬捏成一个default,后续框架层做传感器查询时,可能每一处行为都会跟你的预期不一样。
2. 通用定制的诱惑与边界:三个必须想清楚的拆分公司
2.1 通用化的本质:把“差异”藏到哪一层
做common方案的本质,是希望在多个机型之间复用一套配置和驱动逻辑。听起来很美,但关键问题是谁来兜住“差异”。
指纹模组的差异通常体现在这几个方面:
- 传感器硬件型号不同,比如光学方案、电容方案、超声波方案,它们的内核驱动和HAL实现完全不同。
- HAL进程加载的库和参数不同,比如同一款SoC平台,A机型用的底层库带特定算法版本,B机型用的库可能需要不同calibration数据。
- 设备树中注册的中断GPIO、复位脚、供电策略可能不同。
- 上层的传感器优先级不同,有的机型把指纹作为主传感器,有的机型同时有人脸和指纹,需要区分主次。
如果把这些差异全部压在config_biometric_sensors.xml这一层,配置会变得极其复杂且脆弱。因为你是在用一个静态XML去描述多套动态硬件状态,一旦某款机型的驱动加载路径变了,这份配置又没跟上,那系统里就会出现“明明配了传感器,可实际HAL上报却是空”的尴尬局面。
2.2 三个绕不开的分叉点:类型、能力、行为
我在实际项目中提炼了三个必须提前想清楚的分叉点,这也是判断common方案能不能落地的核心依据。
第一是传感器类型的差异。Android R的配置里会把传感器区分成Fingerprint、Face、Iris等类型,不同类型对应的HAL接口不同。如果common配置里只写Fingerprint,那边上另一款型号明明带人脸,就会出现人脸相关功能缺失。反过来说,把人脸也统进common,那没有对应硬件的机型会在启动时候触发无意义的加载流程,白白增加系统业务逻辑的复杂度。
第二是能力组合的差异。这里的“能力”包括是否支持手势导航(fingerprint navigation)、是否支持多次按压唤醒、是否支持常驻识别等。能力项写得多了,没有对应能力的机型会在系统服务里出现invalid sensor能力标识;写得少了,有能力的机型又会被砍功能。
第三是系统级行为的差异。例如锁屏时的识别优先级、应用层调用时的返回策略、录入流程的引导逻辑。这些行为有一部分是写在框架里的,配置只是入口。系统服务在初始化传感器列表时,如果发现配置里出现了两个相同的“虚拟”传感器,需要靠id字段区分,id给错了,会很隐蔽地造成回调错乱。
提醒一句:做配置抽取前,先跟指纹方案商确认清楚,他们的HAL是否支持同库多机型动态识别。HAL不支持的话,配置文件再通用也白搭。
3. 复盘一套真实落地过的common方案(模拟项目X)
3.1 整个方案的目录结构与overlay组织
去年我在某项目上做了一套“一配置适配三款机型”的方案,产品的共性是同一颗SoC平台、同一个HAL基础版本、但屏幕分辨率不同,指纹模组有两款不同方案。当时的设计思路是把公共部分下沉到common目录,机型特有部分留在各自设备目录覆盖。
目录大致长这样:
device/某厂商/ ├─ common/ │ ├─ overlay/frameworks/base/core/res/res/xml/ │ │ └─ config_biometric_sensors.xml │ └─ fingerprint/ │ └─ (公共HAL适配层、公共init.rc片段) ├─ project_A/ │ └─ overlay/frameworks/base/core/res/res/xml/ │ └─ config_biometric_sensors.xml // 里面对应A款模组 ├─ project_B/ │ └─ overlay/frameworks/base/core/res/res/xml/ │ └─ config_biometric_sensors.xml // 里面对应B款模组 └─ project_C/ └─ overlay/frameworks/base/core/res/res/xml/ └─ config_biometric_sensors.xml编译的时候,project_A、project_B、project_C都会继承common里的公共部分,但各自的Overlay又允许它们针对自己的硬件做微调。这就是我说的“common兜底、device修正”的折中做法,而不是把三套配置硬编码进同一个XML。
3.2 配置文件里几个关键字段的取舍
拿一份相对典型的config_biometric_sensors.xml来举例,大意如下(不同平台字段命名有差异,思路通用):
<biometric-sensors> <sensor type="fingerprint"> <sensor-id value="0" /> <fingerprint-navigation value="true" /> <fingerprint-location value="rear" /> </sensor> <sensor type="face"> <sensor-id value="1" /> </sensor> </biometric-sensors>在common版本里,我的选择是:只保留所有机型都具备的传感器条目,并且只开最小功能集。比如项目X里只有一款机型有人脸模组,那我不会把face配置写进common,而是放到那款机型自己目录的XML里。这样其余两款机型在初始化时根本不会碰到face sensor的加载流程。
指纹部分,三款机型都是rear指纹,所以fingerprint-location统一写rear没问题。但fingerprint-navigation不是每个机型都支持,我把它放在common里默认置为false,支持navigation的机型再在其各自Overlay里改写成true。原因是:navigation功能即便关了,系统也只损失一个手势功能,不会导致功能异常;但如果你在common里默认开,不支持的机型在系统上层就会多出一个虚假能力标识,反而更容易出问题。
3.3 设备树和HAL侧需要同步配合的内容
只改Overlay配置文件是跑不通整条链路的。common方案里,设备树也需要抽一份公共的fingerprint.dtsi,统一给三种机型挂载指纹节点。
例如公共节点里定义好中断和复位引脚,个别机型如果有差异,再在其设备树里用overlay方式重新覆盖引脚。公共部分固定好compatible字段,让同一个指纹驱动可以匹配所有机型。因为两款指纹模组是不同的方案,最终HAL层是选择动态加载不同库,还是同一套HAL兼容两种模组,需要跟方案商确认。
我当时的做法是,HAL层抽了一层公共适配逻辑,根据设备树里的模组型号字段去选择底层库。这样,config_biometric_sensors.xml里不用区分模组型号,因为真正区分逻辑已经下沉到HAL。配置在系统侧只负责描述“有指纹传感器”,而HAL负责处理“用什么库、跑哪套算法”。
这个过程里很容易出现一个误区:配置文件写得再细,也不能让HAL去适配错误的库。配置文件的作用是告诉系统“你该以什么身份跟HAL对话”,而不是让系统直接去决定HAL内部跑什么算法。
3.4 把三款机型跑在同一套common下的注意点
编译阶段要特别注意Overlay的合并顺序。有的平台是device/common/overlay先被产品Overlay覆盖,有的平台则是前后顺序反过来。如果合并顺序反了,你原本想在机型B里把navigation改回false,结果产品Overlay优先级更低,改了个寂寞。
建议是编完以后,直接去out目录下检查合并后的XML是否跟预期一致:
find out/ -name "config_biometric_sensors.xml" | xargs grep -n "fingerprint-navigation"这条命令能查到编译产物里最终生效的属性值。我习惯编完直接dump出来核对一遍,比任何代码review都实在。
HAL侧则要注意公共init脚本的属主权限。手上这个项目就吃过亏,common目录里的fingerprint.rc把/dev/fingerprint设备节点的权限设成了system,三款机型中有两款正常,另一款的内核驱动在init阶段切换了节点路径,导致HAL一直拿不到设备节点,日志里全是open失败。
4. “common”的边界怎么划,才算真正被想明白了
4.1 判断维度一:传感器方案是否同源
做配置抽取前,先回答一个问题:你的多款机型,指纹部分用的是不是同一套HAL方案。如果A机型用的国产电容方案,B机型用的是超声波方案,这两者底层接口、算法逻辑、描述方式都不同,硬放到同一个common XML里,系统只会把它们都当成“fingerprint”传感器,但HAL根本没法用一个进程同时响应两套完全不同协议。
这种情况我更推荐把公共部分抽象在设备树和HAL调度层,而不是把配置强行统一。配置统一是表,HAL统一是里,表里必须一致才行。
4.2 判断维度二:多代产品是否同时维护
如果你要做的common方案要兼容上一代、这一代、甚至下一代产品,情况会更复杂。Android大版本升级时,配置字段本身可能就有差异。Android R里能用的属性,到Android S、T里可能已经被拆分或替换。
我在维护某个跨Android版本的项目时,发现config_biometric_sensors.xml里有些行为会被框架侧新增的逻辑忽略。做跨版本common等于自己给自己挖坑,除非你有专门的框架适配层兜底。
4.3 我推荐的定边界方式
拿我自己做过的项目来说,比较稳的做法是:
- 同一SoC平台、同一HAL方案、同一传感器位置的产品,可以收进common配置。
- 产品功能有差异的,只在各自Overlay里覆盖差异字段。
- 一般不把“是否支持人脸”“是否支持navigation”这些能力差异写进common,避免无中生有。
- common里的XML保持保守,配少不配多,缺失能力最多功能没了,虚假能力会导致系统服务行为错乱。
一个原则:common配置是用来描述最低公共能力的,不是用来做功能聚合的。你能说清楚这个边界,方案就已经成功了一半。
5. 我踩过的坑:常见问题与排查实录
5.1 Overlay配置没生效,系统还在用AOSP默认值
这个是我被问过最多的问题。修改了Overlay里的XML,编完烧进去,Settings里还是没有任何指纹入口,或者在dumpsys时看到传感器列表为空。
排查思路:
adb shell dumpsys biometric | grep -i sensor如果输出为空,说明系统完全没有识别到配置里的传感器。接着去查编译的产物里有没有你自己的XML:
adb shell ls /system/framework/framework-res.apk方案一:用apktool或aapt去parse framework-res.apk里的xml,确认合并后的配置内容跟预期一致。如果看到的还是AOSP默认内容,那说明Overlay目录根本没有被编译进去。
这种情况常见原因是Overlay目录名的路径不对。Android R的Overlay要求包名路径严格匹配frameworks/base/core/res,目录结构漏一层就不生效。另一个是产品makefile里的PRODUCT_PACKAGE_OVERLAYS没有包含common目录,或者被别的变量优先级覆盖了。
5.2 “指纹硬件不可用”反复出现,HAL进程起不来
配置没问题,XML也在System里读到了,但点击添加指纹时提示硬件不可用。把logcat打开,大概率会看到vendor.fingerprint服务反复重启,或者是HAL进程无法连接。
这背后常见原因有三类:HAL进程的SELinux上下文不对,拿不到设备节点;驱动没有正确注册到Input子系统或字符设备;config_biometric_sensors里配置的sensor id跟HAL上报进来的id对不上。
第三类尤其隐蔽。我踩过一次,配置里写的是0,HAL因为读取某配置值偏移了一位,上报的是1,两边完全错频。光看日志根本看不出来,最后是给HAL加了临时日志,dump出上报id才定位到。
5.3 多机型共用一份HAL,但某机型执行特定功能时偶发重启
这种事排查难度比较大。因为是偶发,而且不是每台机器都必现,一开始怀疑驱动内存问题,后来才发现是配置里把某个机型不支持的能力项开了,HAL跑起来以后,算法模块按这个能力项做了额外计算,触发了一个隐藏的内存越界。
最后处理方式很简单:把projects里不支持这项能力的XML覆盖为关闭,问题消失。配置里一个不起眼的布尔开关,可能在底层变成一个完全不同的代码分支路径。
5.4 修改“默认通用”后旧机型回归失败
还有一次是光顾着适配新项目,把common里的fingerprint-location从rear改成了front,结果原本正常的旧机型录入开始报错。因为旧产品的人脸识别和指纹识别逻辑会读取这个字段来改变UI引导方向,一个location不对,整个交互流程就乱了。
从那以后我定了一条规矩:common配置的每次改动,都要对存量产品做一次编译回归。虽然Android系统项目编译成本高,但这个动作绝不能省。
6. 从项目稳定性角度,我的最终取舍建议
做了一轮完整方案之后,我现在的态度很明确:common可以做,但要做“最小公共子集式的common”,不要做“全功能聚合式的common”。一份配置如果能把多款机型的共同底座描述清楚,就已经完成了它的使命。那些机型特有的能力,交给各自的Overlay去扩展,反而更可控。
在实际动手时,建议把配置拆分跟设备树拆分的节奏对齐。配置描述的是“系统眼里有什么”,设备树描述的是“硬件实际有什么”,HAL描述的是“两者之间怎么翻译”。三层保持同样的粒度,后面不管做单机型维护还是跨平台迁移,都不会出现牵一发动全身的被动局面。
对我来说,与其问“common可好”,不如先问“common的边界在哪里”。边界划清楚,它就是提效利器;边界模糊,它就是一个定时炸弹。最后分享一个我一直在用的小习惯:每次改完这类生物识别配置,我都会产出一份“共性字段记录表”,把每款机型实际生效的字段值列出来,跟XML逐项比对,比口头约定靠谱得多。