1、音量键处理
如上,对于一般场景下,按音量上/下键,其事件传递到焦点窗口的根节点布局时(一般的Activity根节点布局都是DecorView)会走到DecorView的dispatchKeyEvent方法中,这里主要有两个分支,一个是Window.Callback类型的cb处理事件(这里如果Activity有处理音量键事件并返回true,则具体音量事件逻辑看应用自身逻辑,如果对应Activity在处理音量键逻辑中什么都不做,那音量键相当于也没有什么实际效果,也不会弹出系统音量条),还有一个分支则是PhoneWindow中调用onKeyDown和onKeyUp处理事件,这里也是一般的按音量键弹出音量条所走的逻辑
如上所示,在PhoneWindow的onKeyDown和onKeyUp方法中,对于音量上/下键事件的处理是调用MediaSessionManager的dispatchVolumeKeyEventAsSystemService方法,MediaSessionManager的服务端是MediaSessionService,MediaSessionManager的前面的方法调用都会调用到其服务端MediaSessionService中,在一般显示系统音量条过程中,MediaSessionService中的主要作用是构建调用AudioService调节音量的方法(包括调节的通道、调节的粒度和flags构建等),然后在AudioService中才是真正调节音量的主要代码,在音量调节中如果音量有改变,则会发送"android.media.VOLUME_CHANGED_ACTION"的广播通知音量变更,在音量调节后会调用sendVolumeUpdate方法去告知SystemUI音量变更
这里调用了VolumeController的postVolumeChanged,其中VolumeController是AudioService的静态内部类
VolumeController的postVolumeChanged方法即调用了其mController变量的volumeChanged方法,这里mController变量是调用VolumeController的setController方法进行设置的
在AudioService的setVolumeController方法中会调用VolumeController的setController方法
在AudioManager中也有setVolumeController,其会调用到AudioService的setVolumeController方法,而在SystemUI中有调用该方法,其设置的VolumeController是VolumeDialogControllerImpl的内部类VC的对象
所以上述音量变更后通知SystemUI既是调用了VolumeDialogControllerImpl的内部类VC的volumeChanged方法回调
2、系统音量条显示逻辑
2.1、音量条显示逻辑
在前面AudioService中音量变更后回调到SystemUI的VolumeDialogControllerImpl.VC的volumeChanged时,即会触发显示系统音量条的逻辑(注意,这里有传入两个参数,streamType和flags),然后会调用到VolumeDialogControllerImpl的onVolumeChangedW方法中
这里会根据flags和当前场景、一些定制来判断是否显示系统音量条,其中flags相关主要是需要包含AudioManager.FLAG_SHOW_UI,如果判断需要显示,则最后会调用到VolumeDialogImpl.java中的onShowRequested方法中去显示系统音量条
onShowRequested方法中会调用showH方法去显示系统音量条
在showH方法中有两处比较重要,一是rescheduleTimeoutH方法,该方法是计算系统音量条自动消失时间,发送延时消息去dismiss音量条,二是mDialog.show,在SystemUI启动时就已准备好了mDialog,在音量相关变更时会更新相关信息,然后在这里只需调用mDialog.show方法即会去显示系统音量条
2.2、音量条布局
在SystemUI启动时,会调用到VolumeDialogImpl的init方法初始化,其中initDialog方法即是对音量条的显示的重要部分,这里会创建mDialog并加载对应布局和设置显示相关参数,其中加载具体各通道音量条的相关代码如下
这里会对各通道的音量调用addRow方法,以将各通道的音量布局加载添加到mDialog布局中
在initRow方法中会根据传入的参数构建VolumeRow对象,其中会添加布局等相关信息
2.2.1、音量通道布局的显示与隐藏
前面可以看到,音量条一般有加载七个通道的布局,但其实一般的话可能只会显示一个通道的音量
在构建mDialog和音量相关变更时(onStateChanged)会触发updateRowsH方法调用
在updateRowsH方法中会遍历各通道的VolumeRow,调用shouldBeVisibleH方法来判断是否需要显示对应通道的音量条,然后设置对应控件可见性为VISIBLE或GONE
在音量相关变更时,会更新相关音量信息如音量大小等,然后会触发onStateChanged调用,其会调用到VolumeDialogImpl的onStateChangedH方法中更新视图相关,包括控件可见性,其也会调用updateVolumeRowH方法来更新各通道控件的信息如seekbar的进度
2.3、音量通道
前面有介绍系统音量条一般有加载七个不同通道的音量条,但其实通道并不止七个
其定义都在AudioSystem中
虽然有11个通道,但因为有些通道变更都是一起的,在音量变更时会有个映射,传到SystemUI的是其映射后的通道(其实在音量变更时,即使设置单个通道音量变更,其也会影响映射相关的多个通道)
其中mStreamVolumeAlias映射关系是在AudioService中赋值的
比如一般手机是PLATFORM_VOICE,则mStreamVolumeAlias则是STREAM_VOLUME_ALIAS_VOICE
上面即可看出其映射关系
2.4、activeStream
在AudioService中有个getActiveStreamType方法,其可获取当前activeStream,同一时间只有一个activeStream,一般使用中的音频通道即为activeStream,如播放音乐时一般activeStream是媒体通道STREAM_MUSIC,但也有其他情况,该方法中也说明了在多个音频通道同时播放或无音频播放时的activeStream
3、安全音量
在插入耳机后在调大音量时可能会弹出如上Dialog,在通过AudioService调节音量时会调用checkSafeMediaVolume方法
在一些特定设备如有线耳机等,且调节的音量通道是媒体通道,且调节音量大于安全音量时,checkSafeMediaVolume方法返回false,此时会调用AudioService$VolumeController的postDisplaySafeVolumeWarning方法
然后其流程与volumeChanged差不多,其会调用到SystemUI的VolumeDialogImpl的showSafetyWarningH方法来显示上面提示
4、自定义音量按键事件处理
很多视频播放界面都会自定义音量键处理和音量控件,其主要包括两部分,一是获取音量键事件,并调节音量,且注意应不让系统音量条显示,二是监听音量变更实现自定义的音量显示控件
如上,自己本地写了个小demo,在拦截音量上/下键并自己处理返回true后,在该界面按音量上/下键就不会弹系统音量条了(注意,这里adjustStreamVolume是调节音量的api,其第二个参数如果包含了AudioManager.FLAG_SHOW_UI那么仍是会弹出系统音量条的)
如上自己本地demo中写了个简单的seekbar的控件,然后监听了"android.media.VOLUME_CHANGED_ACTION"广播,收到广播后刷新seekbar进度
当然,如果需要音量控件是可滑动调节的,那么还需要监听对应控件的change事件,然后可调用AudioManager的setStreamVolume方法来设置音量
5、RingerMode
Android有三种响铃模式:响铃、振动、静音,在音量条上方按钮点击可切换不同模式,其在不同模式下主要会影响铃声相关的声音,如通知等
先查看下点击切换响铃模式按钮相关逻辑,响铃按钮是mRingerIcon,其点击事件如下
这里主要切换响铃模式逻辑是其中根据当前的模式和机器相关情况,计算新的响铃模式,然后调用mController.setRingerMode方法来设置新的响铃模式,其中mController是VolumeDialogControllerImpl的对象,其最后会调用到AudioManager的接口来设置新的响铃模式,这里按照上面逻辑会调用其setRingerModeInternal方法
AudioManager会直接调到AudioService中
这里主要会调用到AudioService的setRingerMode方法
其主要代码有三部分
一是setRingerModeInt方法,这里是主要部分逻辑,放在后面查看
二是mRingerModeDelegate相关回调,这里mRingerModeDelegate其实是ZenModeHelper的内部类RingerModeDelegate的对象,这里回调会记录响铃模式和可能调整勿扰模式
三是调用setRingerModeExt方法,这里会发送"android.media.RINGER_MODE_CHANGED"的广播
这里继续查看setRingerModeInt方法:
这里逻辑也比较多,简要介绍下
1、muteRingerModeStreams,该方法是这里主要部分逻辑,也是响铃模式切换的主要逻辑,在该方法中会根据响铃模式和其他如勿扰模式、设备等相关情况,判断是否需要将一些通道静音,如一般情况下,从响铃模式切换到振动或静音模式时,会将铃声相关通道mute,音量设为0,而切回响铃模式时,又会将音量设回去
2、如果persist参数为true,则一般会将新响铃模式设置到Settings.Global.MODE_RINGER
3、如果响铃模式变更,这里会发送"android.media.INTERNAL_RINGER_MODE_CHANGED_ACTION"广播
5.1、音量调节触发RingerMode变更及VolumePolicy
在调节铃声音量时,我们可能注意到,在响铃模式,将铃声音量调制最低时,会自动变为振动模式,而在振动模式,将铃声音量调高时,会自动退出振动模式变为响铃模式
这是因为在调节音量时会有相关检查
在设置音量时,其在AudioService中会触发onSetStreamVolume调用
其中getNewRingerMode一般会根据铃声音量来计算新的响铃模式
在调节音量时也可能会触发checkForRingerModeChange方法调用
这里未将checkForRingerModeChange方法全部贴出来,其也是会根据当前相关情景调节响铃模式
在上面一些判断中,除了手机有没有振动器等的判断,还有mVolumePolicy的变量的判断,VolumePolicy是个单独的类,其主要是保存有几个变量
一般这个volumepolicy是SystemUI调用setVolumePolicy有设置的,