这个系列写到第五篇,前面几篇我们把音频系统的骨架——AudioFlinger、HAL、AudioTrack/AudioRecord通路——基本摸了一遍。写第一篇时我就说过,Android音频最劝退新人的地方不是AOSP代码量,而是“东西太多,不知道从哪条线串起来”。今天这篇,就是把那条线里最关键的一个决策中枢单独拎出来讲透:AudioPolicyService的策略管理。
它到底负责什么?说人话就是一句话:你的App播放一个声音时,用什么设备播、多大音量、要不要打断别人、来电时它该不该闪避,这四件事全是它拍板。这些看似零散的问题,在代码里被抽象成“策略(Policy)”。所以标题里的“策略管理”,本质是研究AudioPolicyService怎么根据使用场景、设备状态、音量映射,做出一系列决策。
这篇适合正在看framework层代码、做ROM定制、搞车机/电视音频适配的兄弟们。如果你是刚入门,我会尽量把决策链路拆开揉碎,从一次播放的完整旅程讲起,保证你跟着走完一遍,回去再看AOSP里的audio_policy代码会有一种“原来就这”的通透感。
需要先说清楚一点:虽然标题挂着Android 15,但AudioPolicyService的主体框架从Android 8引入AudioPolicyEngine、Android 11引入ProductStrategy之后,已经基本定型。Android 15不是一次推翻式重构,更多是行为细节的渐进式调整与加固。下文我会在相关章节里标注哪些是Android 15前后值得关注的变化点,方便你们对照自己的代码版本。
1. 先搞清楚AudioPolicyService在整个音频系统里的位置
1.1 从一次“按下播放键”的旅程说起
你打开音乐App点了一下播放,这背后发生了什么?用最粗的粒度讲:App通过AudioTrack把音频数据写进AudioFlinger管理的共享内存,AudioFlinger里的MixerThread负责混音,混完之后把数据交给Audio HAL,HAL再写进声卡驱动。这条链路听起来很顺畅,但有一个关键问题被跳过了:App播放时,系统怎么知道要把声音送到扬声器、耳机还是蓝牙?
答案就是AudioPolicyService。AudioTrack在创建的时候,会通过AudioFlinger向AudioPolicyService发起一次getOutputForAttr查询。这个查询带着App的使用场景(比如媒体、导航、通话)、音频属性(采样率、声道数、格式),还带着当前系统的设备状态。AudioPolicyService根据这些输入,返回一个“输出”的句柄,告诉AudioFlinger:这条流应该往哪个声卡、哪个端口写。
如果用一个比喻,AudioFlinger是酒店前台,负责安排房间里的事务;AudioPolicyService是大堂经理,他知道今天哪些房间能住人、哪个客人是什么身份、该给什么待遇。你按下播放键的瞬间,真正替你决定“住哪间房”的不是前台,而是大堂经理。
1.2 策略服务管的四件事
把AudioPolicyService的职责拆开,其实就四件事:
- 路由策略:根据使用场景、设备可用状态、强制设备偏好,决定输出到哪个设备。这是最核心、也最容易出问题的一块。
- 音量策略:管理各种流类型的音量索引,把音量的“格数”换算成具体衰减dB值,并针对不同设备类别给出不同曲线。
- 并发与焦点策略:协调多个App同时播放时的行为,比如导航播报时压低音乐音量、来电时暂停其他播放。这就是AudioFocus机制。
- 动态策略与设备连接:处理耳机插拔、蓝牙连接、USB声卡插入等动态事件,同时提供运行时增加/修改路由策略的接口。
这四件事在代码里分别对应AudioPolicyInterface接口族中的getOutputForAttr、setStreamVolume/getStreamVolume、requestAudioFocus、setDeviceConnectionState等关键方法。掌握了这四块,你基本就掌握了AudioPolicyService的全部核心逻辑。
1.3 Android 15这个节点,策略层有什么变化
很多兄弟一看到“Android 15”就以为策略层又大改了一版,其实没有。Android 15在audio_policy层面真正值得关注的变化有这几个:
第一个是AudioFocus的默认行为更“懂得谦让”了。对targetSdk 35的应用,焦点丢失时的淡化(duck)处理更加平滑,系统会尽量避免突然一击的停顿感。第二个是通信设备路由(communication device)选择逻辑在持续完善,setCommunicationDevice相关API成为语音类应用的推荐做法。第三个是底层被要求适配16KB page size,这意味着所有native库的链接和对齐方式都有调整,HAL厂商如果还抱着老构建不放,在Android 15的三方兼容性测试上会很痛苦。
这些变化属于“底层加固、上层行为微调”,AudioPolicyManager的职责边界和决策流程本身没有翻天覆地。真正推翻式的重构,是Android 8引入AudioPolicyEngine、Android 11引入ProductStrategy那两次。所以读这篇文章的时候,你手里的代码不管是Android 12还是Android 15,核心思路都能对上,只是部分API名和配置项有差异。
2. 策略管理的心脏:AudioPolicyManager与AudioPolicyEngine
2.1 AudioPolicyManager:策略决策的“程序”
AudioPolicyManager是整个策略服务的实现核心。它不是一个独立进程,而是跑在audioserver进程里的一个核心对象。audioserver启动时,会创建AudioPolicyService,AudioPolicyService的onFirstRef里会实例化AudioPolicyManager,由后者读取配置文件、初始化引擎、注册各种回调。
从类关系上看,AudioPolicyService是binder服务的外壳,负责IPC通信;AudioPolicyManager才是真正干活的类。它继承自AudioPolicyInterface,实现了几百个方法,但真正的策略决策逻辑并不全在它身上——很多规则细节被下沉到了AudioPolicyEngine。
这里要提一个重要变化:Android 8之前,策略规则是写死在AudioPolicyManager代码里的,各种if-else判断“如果是有线耳机就优先耳机、如果是媒体流就选A2DP”之类的逻辑。那时候做ROM定制的同学都有体会,想调整设备优先级,得改C++代码然后重新编译system镜像,成本极高。所以Android 8开始,AOSP把“规则”从“程序”里抽了出来,变成了可配置的XML,这就是AudioPolicyEngine的由来。
2.2 AudioPolicyEngine:把决策规则从代码中抽出来
AudioPolicyEngine不是一个独立进程,也不处理音频数据,它只做一件事:根据输入的“条件”,用配置好的“规则”算出输出。这些规则定义在/vendor/etc/audio_policy_engine_configuration.xml(或/system/etc)这个文件里。
配置文件里几个核心概念先理清:
- Selector(选择器):引擎的入口条件,比如输入的使用场景(usage)、音频流类型(stream type)、设备连接状态。
- ProductStrategy(产品策略):一组使用场景的集合,比如
STRATEGY_MEDIA聚合了USAGE_MEDIA、USAGE_GAME等。引擎根据Selector命中某个ProductStrategy,再根据该策略去选设备。 - VolumeGroup(音量组):将音频流按策略收入不同的音量组,一组共用一条音量曲线。
- VolumeCurve(音量曲线):定义音量索引从0到max时对应的衰减dB值,可以按设备类别(device category)区分。
一个简化版的engine配置长这样:
<audioPolicyEngine> <productStrategies> <productStrategy name="STRATEGY_MEDIA"> <attributes> <usage name="USAGE_MEDIA"/> <usage name="USAGE_GAME"/> </attributes> <deviceCategories> <deviceCategory name="DEVICE_CATEGORY_SPEAKER"/> <deviceCategory name="DEVICE_CATEGORY_HEADSET"/> </deviceCategories> <volumeGroup name="MEDIA_VOLUME_GROUP"/> </productStrategy> </productStrategies> </audioPolicyEngine>实际AOSP里的配置远比这个复杂,但核心就是这个结构:什么场景进什么策略,策略对应哪些设备类别,设备类别又挂哪个音量组。你改配置的时候,本质上就是在改这张“映射表”。
2.3 engine与manager的协作流程:以选设备为例
那AudioPolicyManager和AudioPolicyEngine到底怎么配合?我用getDeviceForStrategy这个最经典的流程走一遍:
- AudioPolicyManager先从调用方拿到音频属性(AudioAttributes),内部转成usage、stream type等信息。
- manager先检查是否存在“强制设备”(比如
setPreferredDevice设了偏好设备,或者forceUse配置了强制场景),如果有,直接返回强制设备,这是最高优先级。 - 如果没强制设备,manager调用engine的接口,传入product strategy和当前设备状态,engine根据配置算出候选设备列表。
- manager拿到候选设备列表后,还要和当前可用的设备集合求交集,同时根据调用方的flags(比如是否是offload、是否是direct输出)做过滤。
- 最后落到一个具体的device descriptor上,再返回对应的输出。
这个流程可以用一段伪代码表示:
audio_devices_t AudioPolicyManager::getDeviceForProductStrategy( product_strategy_t strategy, bool fromCache) { // 1. 强制设备优先级最高 if (mForcedDeviceForStrategy[strategy] != AUDIO_DEVICE_NONE) { return mForcedDeviceForStrategy[strategy]; } // 2. 交给engine算规则结果 auto devices = mEngine->getDevicesForStrategy(strategy); // 3. 和当前可用设备求交 devices &= getAvailableDevicesMask(); // 4. 处理flag过滤,比如offload/direct devices = filterDevicesByFlags(devices, ...); // 5. 候选非空则选第一个 if (devices != AUDIO_DEVICE_NONE) { return popCount(devices) > 1 ? selectBestDevice(devices) : devices; } // 保底:primary输出 return AUDIO_DEVICE_OUT_SPEAKER; }这个过程里最值得玩味的是第4步。filterDevicesByFlags看起来只是过滤flag,但它实际上决定了你有没有资格使用低延迟、offload、直通这些高级通路。很多“声音能出来但音质不对”或者“延迟贼高”的反馈,根源就在这一步把该用的通路过滤掉了。
2.4 配置加载失败的坑
改配置文件踩过的坑,值得先提前说一嘴:AudioPolicyManager构造时会加载所有XML配置,一旦解析失败,整个audioserver会反复重启,设备直接没有声音。最麻烦的是,有时候语法没问题,而是设备端口名对不上——engine配置里的设备名和audio_policy_configuration.xml里的设备端口名不匹配,同样会导致加载失败。
我的经验是:改完XML先做语法校验,再检查名称一致性,最后才放到设备上。即便是开发机,也要做好“变砖”的心理准备,最好有串口或adb能及时捞日志。
3. 路由策略的完整链路:一个AudioTrack是怎么被指派设备的
3.1 从Usage到ProductStrategy:场景怎么变成策略
路由决策的第一步,是理解“场景”。App在创建AudioTrack的时候,会带上AudioAttributes,里面最重要的字段是usage和contentType。usage描述的是“我要做什么”,比如USAGE_MEDIA、USAGE_ASSISTANCE_NAVIGATION_GUIDANCE、USAGE_VOICE_COMMUNICATION;contentType描述的是“内容是什么”,比如音乐、语音、声效。
到了native层,AudioPolicyManager会把这个usage映射到一个整数枚举的product strategy。Android 11之前叫strategy,之后叫product strategy,本质没有变化,只是语义更丰富了。拿一份典型映射表来说:
| usage | product strategy |
|---|---|
| USAGE_MEDIA / USAGE_GAME / USAGE_UNKNOWN | STRATEGY_MEDIA |
| USAGE_ASSISTANT / USAGE_ASSISTANCE_NAVIGATION_GUIDANCE | STRATEGY_ENFORCED_NAVIGATION 或 STRATEGY_ASSISTANT |
| USAGE_VOICE_COMMUNICATION / USAGE_CALL_ASSISTANT | STRATEGY_VOICE_COMMUNICATION |
| USAGE_NOTIFICATION / USAGE_NOTIFICATION_RINGTONE | STRATEGY_SONIFICATION |
| USAGE_ALARM | STRATEGY_ALARM |
为什么Android 11要做这个升级?因为老的strategy和stream type耦合太紧,而stream type只有11个,很多新场景表达不了。比如“导航播报的时候音乐要闪避”,老方案里你得单独判断usage是不是导航,否则它和媒体共用一条策略,根本没法差异化处理;有了product strategy,每个场景集合可以独立配置设备类别、音量组、闪避规则,定制空间一下就打开了。
3.2 AudioPolicyService::getOutputForAttr里面发生了什么
从AudioFlinger往AudioPolicyService发的路由查询,核心入口是getOutputForAttr。我简化一下它的调用链:
AudioFlinger::createTrack -> AudioPolicyService::getOutputForAttr -> AudioPolicyManager::getOutputForAttr -> getDeviceForProductStrategy -> checkOutputForAttributes / selectOutput这里有个和旧版本不同的地方:Android 12之后,路由选择开始区分“在旧输出上重定向”和“打开新输出”两种。如果当前输出流(mixer thread)的属性可以复用,manager会直接返回现有输出,而不是重新开一条。这个决策对资源开销影响很大,因为每条输出都对应一个独立的混音线程。
getDeviceForProductStrategy内部查找设备时,会把device descriptor(设备描述符)拿来做综合打分。评分维度包括:设备类型、是否支持采样率转换、是否是位完美(bit-perfect)路径、是否支持offload等。说人话就是:系统最终选的设备,不一定是“硬编码优先级最高”的,而是“在当前条件下最划算”的。
我在Android 14上调试过一个案例:USB DAC插入后,媒体播放没有自动切到USB,而是继续留在扬声器。日志里看起来路由逻辑正常,候选设备里也有USB,但最终分数被“采样率不支持44.1kHz原样输出”给扣掉了,因为USB DAC的profile只声明了48kHz的PCM能力。这种问题不做完整链路追踪,光看路由结果根本定位不到。
3.3 设备插拔时的动态重路由
路由决策不只是创建Track时做一次。你戴着耳机听歌,突然把耳机拔了,声音要在几百毫秒内切到扬声器;反过来,你正在外放,突然连上蓝牙耳机,系统得决定要不要立刻切过去。这些都是AudioPolicyService的“动态重路由”机制在起作用。
触发入口是setDeviceConnectionState。它干的事情分三步:更新设备连接状态,把它加入或移出可用设备集合;然后遍历所有正在活动的client,检查当前路由在新设备状态下是否还是最优;如果不是最优,调用AudioFlinger的restoreOutputRouting或线程重路由,把已经打开的流切到新设备上。
关键一点:切换不是无条件的。举个例子,如果你正在打电话,蓝牙耳机连上,系统会立刻切到蓝牙;但如果只是放音乐,大多数策略实现里,即使蓝牙连上也不会打断正在播放的扬声器音乐,这是为了避免频繁切换带来的体验割裂。不同厂商在这个“切换阈值”上的调节力度差异很大,有的人喜欢即时切换,有的人喜欢等到下次播放再切。这块没有绝对对错,完全是策略取舍。
3.4 输出profile与端口匹配的细节
很多做HAL的兄弟容易忽略一件事:路由选择的不仅是“设备”,还有“输出端口组合”。同一个USB声卡,驱动里可能声明了多个profile:一个支持PCM 16bit 44.1/48kHz,另一个支持DSD。AudioPolicyManager在拿到候选device之后,会遍历这台设备的所有profile,寻找和调用方属性最匹配的那一个。
profile匹配失败的常见表现是:声音出来了,但HAL层一直在做重采样;或者干脆匹配不到任何profile,只能退回到primary output的默认路径。日志里通常会看到类似getOutputForAttr: no output available之类的字眼,但往往不会告诉你为什么profile匹配失败。
调试这类问题的建议是:先确认配置文件里声明的采样率、通道数、格式是不是和HAL驱动真实能力一致,特别是第三方声卡,文档参数和实际能力跑偏是常事。然后检查AudioFlinger里那条mixer thread的采样率是不是被某些高优先级流锁定了。我处理过一个诡异case,一个App播放192kHz的流把mixer线程采样率锁上去后,所有其他流全部被迫重采样,音质劣化明显,根源就是profile匹配时忽略了mixer线程的已有设定。
4. 音量策略:VolumeGroup与VolumeCurve的配置化设计
4.1 音量不再是“流类型+一个数字”
Android早期的音量模型很简单:每个stream type一个音量索引,系统提供一个0到15或0到7的滑块,然后按一个固定的衰减公式换算成dB。这个模型有两个天生缺陷:第一,音量调节粒度和用户体验严重脱节,很多用户反映“低一格太轻,高一格太响”;第二,不同设备类别(耳机、扬声器、蓝牙)的听觉灵敏度差异很大,同一索引值在不同设备上听感完全不同。
VolumeGroup的出现就是为了解决这两个问题。它不再以一个stream type为单位管理音量,而是以“策略”为单位,把多个stream type聚合到一个组里,共用一条音量曲线。举个例子:STRATEGY_MEDIA对应的音量组可能叫MEDIA_VOLUME_GROUP,它同时收纳了STREAM_MUSIC、STREAM_GAME、STREAM_SYSTEM等多个stream type。媒体音量调节时,这些流一起跟着变,从用户视角看就是“媒体音量”。
更重要的是,音量曲线现在可以按设备类别分开配置。同样是媒体音量,接耳机时用一条高灵敏度曲线,戴头戴耳机时用另一条,外放用第三条。这在以前是不可能的。
4.2 VolumeCurve曲线的定义与换算
VolumeCurve在engine配置文件里长这样:
<volumeGroup name="MEDIA_VOLUME_GROUP"> <volumeCurve> <volumePoint> <volume index="0" attenuation="-1000"/> <!-- 静音 --> <volume index="10" attenuation="-560"/> <!-- 最小可听 --> <volume index="50" attenuation="-200"/> <volume index="100" attenuation="0"/> <!-- 最大 --> </volumePoint> </volumeCurve> </volumeGroup>这里的attenuation单位是毫dB(mdB),-1000表示静音,0表示最大增益。系统在计算实际音量时,先找到当前音量索引对应的两个point,然后做线性插值,得到最终衰减值,再叠加到流增益上。
注意,这里说的是线性插值,但人耳对音量的感知是对数级的。所以配置曲线时,如果想做出“听起来是均匀变化”的效果,度数的增减应该尽量维持感知一致性,插值出来的点往往不是直线,而是一条下凹或上凸的曲线。很多厂商第一次调参时会疑惑“为什么我配了直线,音量还是前段变化大后段变化小”,因为你给的是mdB对数域,感知本身就是非线性的。
4.3 定制音量策略时最容易踩的坑
做音量策略定制,我总结了几条血泪经验:
第一个坑:只改stream音量映射,没确认VolumeGroup归属。很多App间的音量互相干扰,其实是stream type被映射到了错误的volume group里。比如把STREAM_SYSTEM错误地归到了闹钟组,导致系统音量和闹钟音量绑在一起。
第二个坑:曲线点太少。只有一个静音点和最大点,中间全靠插值出来的结果往往会让低音量段衰减过快。建议至少配置5个以上的point,尤其是音量索引30以内的区间,那里的听感变化最敏感。
第三个坑:设备类别配置过粗。engine配置里默认的device category往往只有speaker和headset,如果你只改了speaker曲线的最大音量,耳机、蓝牙、USB全部受影响。我建议至少拆成SPEAKER、WIRED_HEADSET、BT_A2DP、USB_DEVICE四类分别调。
第四个坑:改完XML忘记清数据和重启。音量配置在服务启动时读入内存,不是每次都重新读文件。改完配置不重启audioserver的话,你会看到dumpsys里还是旧曲线,然后开始怀疑人生。
这条经验同样适合Android 15:系统对音量配置的加载时机没变,改完配置必须adb shell stop && adb shell start,或者直接重启。
5. 动态策略:运行时调整策略的那些路子
5.1 DynamicPolicy到底能干什么
有些场景,比如录屏直播、正在通话时要采集另一侧的声音,这种需求没法在启动配置里预判,因为触发时机完全由App决定。这时候就需要动态策略接口了。AudioPolicyService实现了一个IDynamicPolicy接口,典型方法包括registerPolicyMixes、unregisterPolicyMixes等。
动态策略的核心概念是AudioMix(音频混音规则)。一个mix包含三类关键信息:允许或排除哪些usage、允许哪些设备、路由方式是强制还是建议。通过注册一个mix,App可以请求系统把某个usage的音频流路由到一个特定的输出,或者从某个输入源采集声音。最常见的场景就是录屏App注册一个REMOTE_SUBMIX类型的mix,之后系统把内录音频和麦克风采集混好交给应用,这就是录屏“录制内部声音”功能的底层实现。
5.2 AudioMix与并发场景的管理
动态mix最容易被低估的是它和现有路由策略的交互。系统里同时存在静态策略和动态mix时,AudioPolicyManager会做一个“合并决策”:如果动态mix匹配了正在播放的usage,会优先按动态mix的路由来,但音量、焦点等策略逻辑仍然走原有框架。
这带来一个很有意思的问题:动态mix不应该无限制地拦截音频流。一个设计良好的动态mix会声明明确的usage白名单和路由规则,而不是“我全要”。我见过有厂商在录屏功能里把mix写成“capture all”,结果所有App的声音都往录屏通道走,用户正常外放直接没声音。定位了三天,最后发现是动态mix的usage过滤配置漏写了。
这告诉我们:动态策略用得好是神器,用不好是事故源头。
5.3 焦点也是策略的一部分
音频焦点(AudioFocus)虽然名义上归AudioManager管,但它对策略的影响极大,我会把它看作策略管理的一部分。每个App要开始“重要的”声音播放之前,应该先申请焦点;系统根据焦点持有者的优先级,决定是让新的播放开始、打断旧的播放,还是让旧的播放压低音量(duck)。
Android 15对焦点机制做了一些体验层的改进:对target 35的应用,焦点丢失时的淡化处理更平滑,系统默认帮你做渐变衰减,而不是瞬间“啪”一下断开。开发者在适配的时候需要注意申请焦点时正确设置AudioFocusRequest的setAcceptsDelayedFocus和setWillPauseWhenDucked,否则你的App在焦点竞争场景下会表现出非常僵硬的行为。
对做策略定制的人来说,焦点状态是路由和音量决策的重要输入变量。比如导航播报时,音乐音量下滑3dB以上,这个动作就是AudioPolicyService在焦点事件里做出的音量调整。理解了焦点机制,你才真正理解了为什么有些播放行为看起来像“被策略管住了”。
6. 常见问题与排查技巧实录
6.1 播放没有声音,或者声音从错误的设备出来
这类问题排第一,几乎每天都有开发者来问。我的排查习惯是分四步走:
第一,看路由结果。执行adb shell dumpsys audio,重点看Outputs和Routes两个字段,确认当前流被路由到了哪个设备。如果路由结果是NONE或者回退到了默认设备,那问题就在策略层。
第二,看设备状态。执行adb shell dumpsys media.audio_policy,确认耳机、蓝牙等设备是否在available devices列表里。设备不在列表里,后面全是白搭。
第三,看决策过程。logcat里过滤AudioPolicyManager和AudioPolicyEngine,找到getOutputForAttr附近的关键日志,看候选设备列表和flags过滤情况。
第四,确认HAL层接收到的设备ID。如果HAL层收到的输出设备ID完全不对,那可能是策略配置文件和HAL上报的device端口对不上。
| 现象 | 优先排查方向 |
|---|---|
| 路由到默认设备 | 检查strategy的设备优先级和可用设备集合 |
| 路由到NONE | flags过滤或profile匹配失败 |
| 设备在但切不过去 | 检查动态重路由阈值和mix策略 |
| 时好时坏 | 怀疑设备连接状态上报不稳定 |
6.2 音量条拖着没反应,或低音量直接没声
音量问题里最典型的有两类:音量条有反应但实际音量不变;音量调低一两格就完全静音。
前者最常见的原因是stream type被映射到了一个没有VolumeCurve的volume group里。系统操作音量条修改的是stream volume,但如果它对应的volume group没有配置曲线,最终增益换算就会落到错误路径上。
后者基本是曲线设计问题。前面说过,插值在低音量段如果衰减过陡,会导致音量索引稍微调低就触底。排查时先看dumpsys audio里的volume group配置,确认当前stream的索引绑定的曲线point分布是否合理。
还有一个容易忽略的点:masterMute。有些设备在启动时因为音频策略初始化失败,会置一个全局静音,表现是“无论怎么调音量都没声音”。日志里搜setMasterMute,能复现的话大概率就是策略加载阶段出了问题。
6.3 策略配置改崩了,audioserver反复重启怎么办
修改audio_policy_configuration.xml或audio_policy_engine_configuration.xml后,最容易碰到的硬故障就是audioserver反复重启。这时候设备通常没有声音,因为音频整个服务没起来。
第一步是捞日志。adb logcat -b crash和adb shell dmesg都看一眼,重点是找到AudioPolicyManager初始化失败的堆栈。第二步是核对配置。常见问题有几类:XML语法错误、设备port名称拼写不一致、引用了不存在的usage、volume curve的point越界。
我的习惯是做一个“最小化改动”测试:先只改一个字段,验证没问题再继续,避免一次性引入多个变量。这里强烈建议配置文件和数据目录分开管理,线上回归时方便快速回滚。
6.4 适配Android 15的几个建议
最后聊聊Android 15适配的实操建议。
对于HAL厂商,第一优先确认16KB page size下so库的对齐没问题,否则挑设备概率极大。第二检查AIDL HAL接口里枚举值和标志位的对齐,Android 15清理了不少隐藏的type转换bug,如果你的实现用了隐式转换,编译期可能没问题,运行期就说不准了。
对于framework定制方,Android 15对焦点行为的调整意味着所有播放器应用都需要重新测试一遍焦点场景。不要默认老代码能无缝迁移,建议把“来电打断”“导航闪避”“多App并发播放”这几个场景全部回归一遍。
对于做产品策略的同学,我的建议是“少改多验证”。产品策略的调整动一发牵全身,一个strategy变动会影响路由、音量、焦点三套机制。每次改动,先在模拟器上跑通基础场景,再上真机做完整音频回归。
最后再分享一个小技巧:调试AudioPolicyService时,dumpsys media.audio_policy里的信息比dumpsys audio更细,很多厂商log里没有的东西都在这里有。遇到疑难杂症,先把这两个dump完整拉下来,对照着看,往往能找到突破点。我自己做车机项目两年,最深的体会就是音频策略这件事,配置远比代码更容易成为瓶颈,而排查配置问题,靠的从来不是灵感,而是一步一步把链路走完的耐心。