记一次基于android audioservice/policy开发新功能经历

来源:互联网 发布:迦陵论诗丛稿 知乎 编辑:程序博客网 时间:2024/06/01 14:30

楼主目前在某家移动互联网公司实习,主要参与第三方android ROM的开发。跟着mentor一起做多媒体相关模块。这篇博客主要是想记录一下楼主上个星期纠结了一星期的一个change。
首先由于我们的主要工作室开发第三方android rom,所以用户反馈很重要,我们需要及时的解决用户报的bug,并且开发一些新功能。这次经历的来源是有许多用户反馈了以下这种情况:他们在外面的时候,由于外界声音嘈杂,所以会把铃声的音量设置的很大,以防漏接电话。但是回到家后,带上耳机听歌,由于没有把铃声调回来,这时候突然来了个电话/短信,耳机里的铃音特别大,把他们吓住了。。。所以之后经过和产品经理的沟通,我们决定将扬声器的铃声音量和耳机的铃声音量分开设置,即你带上耳机后的铃声音量和你不插耳机是不一样的。
这个功能乍一看很简单,其实仔细分析有以下两个难点:
1. 读过AudioPolicyManager/Servvice和AudioService源码的人都应该知道,铃声的策略是不管你插没插耳机,你在framework层get到的ring流的设备永远都只有speaker。所以如何做到在插耳机和不插耳机时具有不同的音量呢?
2. 即时你在framework层成功的设置了耳机的音量,但是如果铃声真的来了,会不会去播放你耳机的音量呢?因为policy在播放铃声流的时候也是默认播放speaker的音量的。
3. 如果关机时插着耳机,再重启不插耳机,开机时能够恢复到speaker的音量吗?或者说是完全没有恢复,显示的还是上次关机前的音量,还是只刷新了ui但是并灭有设到HAL层(硬件驱动)去?

当然,楼主一开始并没有想那么多,就感觉这个问题很容易实现嘛~所以楼主一开始是想参考music的方式,因为music的音量就是耳机和speaker分开的。起名叫方案一吧~
方案一:但是楼主仔细一分析,发现policy不一样啊,我们在framework层(audioservice)调用getdeviceforstream的时候最终会调用到AudioPolicyManager中的getDeviceForStream方法,这个方法会调用getStrategyForStream方法和getDeviceForStrategy方法获得设备,但是STREAM_RING和STREAM_MUSIC的strategy是不一样的,人家music插着耳机在AudioService get到的就是个耳机,但是ring最终得到的确实一个speaker(其实是speaker和headset,但是AS的getDeviceForStream方法认为只要有speaker,就只返回speaker)。所以这条路算是行不通了。
方案二:既然参考已有的不行,那就只能自己想了。要使插耳机和不插耳机的音量不同,肯定要持久化这两个音量,可以用preference。并且在插拔耳机的时候是有广播发出的,所以可以使用一个recevier,注册一下这个广播,在接受到广播的时候更新音量~所以做了如下更改:

    代码目前还没放上来,等年后放上来

乍一看没什么问题,虽然有一点hardcode的嫌疑,可扩展性不好,但是目前这个场景好像可以应用了。所以进了代码。之后就有用户反馈说,如果插着耳机重启,在拔掉耳机,音量和插着耳机的时候一样。这个问题是因为错误的写入了preference,因为所谓的preDevice在开机时就不存在,这个bug可以加一个判断来控制是否写入preference。还有一个问题就是如果插着耳机关机,拔掉耳机开机,这个时候由于系统没有发出插拔耳机的广播,所以不会从preference读取值,而是会从settings_system.xml取值,这个里面的值是关机前最后一次设置的音量值,也就是耳机的值,所以又不对了。这个问题可以通过多注册一个BOOT_COMPLETED广播来修复。在开机完成后从preference中再读取一次值。但是这个代码有一个致命的问题是preference数据的有效性得不到保证,因为preference只有在插拔耳机的时候才会更新。这是一个致命的问题。
方案三:在AudioPolicyManager中进行修改。在这里修改很危险,因为要影响面很大,尤其是修改policy逻辑或者public方法,虽然做了如下修改很好的完成了指定功能,但是最后还是没有让进代码,因为考虑到第三方软件可能会乱调接口出现一些不知道的问题,代码如下:

    代码目前还没放上来,等年后放上来

方案四:在AudioService中进行修改,这样修改使得当你带着耳机的时候,get 铃声流的设备可以get到耳机,即和music是一个逻辑,这是settings数据库中也会有volume_ring_speaker和volume_ring_headset两个音量。但是ui界面使用可以正常更新,但是当铃声来的时候,并没有播放正确的音量,这是因为播放的时候一定遵从policy,也即当插着耳机的时候,播放音量为speaker的音量,播放设备为耳机和speaker。所以形势渐渐变得明朗,要么你就改policy,要么你就只能用一个音量即volume_ring_speaker来表示铃声音量,因为policy只会播这一个音量。修改policy是不好的(方案三),所以我们采用后者的思路。方案四的代码如下:

    代码目前还没放上来,等年后放上来

方案五:其实饶了这么一圈还是绕回了方案二,对于方案二数据有效性的问题,我决定在每次set完之后都对这个preference进行更新,从而是数据时刻保证是最新有效的(settings数据库用的数据就是这个原理),代码如下:

    代码目前还没放上来,等年后放上来

但是这么修改出现了如下问题:即每次重启后preference都找不到了,我仔细的查看了logcat,发现开机时去读取preference的线程是和set preference的线程不是一个线程,我在想这是不是权限的问题,但是我将preference的权限改为MODE_WORLD_READABLE也还是不行,重启后get不到原来的preference。
方案六:上面的问题并没有去细想,因为时间比较急,我又不想加班。。。所以我想既然我在模仿数据库中数据的设置方式来保证数据的有效性,为什么不直接把这两个值起一个新名字放到数据库里面呢?所以最终的代码修改如下:

    代码目前还没放上来,等年后放上来

这个代码最后通过了review,因为改的大多都是private方法,并且没有对本来的逻辑产生其他的影响,不会出现方案三那种上下不对称的情况,也不会出现方案二那种数据有效性的问题。也算是成功的解决了这个问题。

楼主的水平还比较低,刚刚接触android一个月,来实习之前对android一无所知,如果有写的不对的地方或者大神们有什么意见,可以私信或者评论哦~~

0 0
原创粉丝点击