前段时间,因为公司需求与音量键的处理流程相关,于是跟了相关源码,因此记录在博客,分享给大家。
在音量键被按下后,Android输入系统将该事件一路派发给Activity,如果无人截获并处理这个事件,承载当前Activity的显示PhoneWindow类的onKeyDown()或onKeyUp()函数将会处理,从而开始通过音量键调整音量的处理流程。输入事件的派发机制及PhoneWindow类的作用将在后续章节中详细介绍,现在只需要知道,PhoneWindow描述了一片显示区域,用于显示与管理我们所看到的Activity和对话框等内容。同时,它还是输入事件的派发对象,而且只有显示在最上面的PhoneWindow才会收到事件。
按照Android的输入事件派发策略,Window对象在事件的派发队列中位于Activity的后面,所以应用程序可以重写自己的Activity.onKeyDown()函数以截获音量键的消息,将其用作其他的功能。比如说,在一个相机应用中,按下音量键所执行的动作是拍照而不是调节音量。
PhoneWindow的onKeyDown()函数实现如下(省略部分代码):
switch (keyCode) {
case KeyEvent.KEYCODE_VOLUME_UP:
case KeyEvent.KEYCODE_VOLUME_DOWN:
case KeyEvent.KEYCODE_VOLUME_MUTE: {
……
if (mMediaController != null) {
mMediaController.adjustVolume(direction, AudioManager.FLAG_SHOW_UI)
} else {
MediaSessionLegacyHelper.getHelper(getContext()).sendAdjustVolumeBy(
mVolumeControlStreamType, direction,
AudioManager.FLAG_SHOW_UI | AudioManager.FLAG_VIBRATE
| AudioManager.FLAG_FROM_KEY)
}
return true
}
上面的代码显示,PhoneWindow接收onKeyDown()事件处理时,先判断显示的音量调整UI是否存在,假如存在,就直接在当前的流类型上进行调整音量。假如不存在就通过MediaSession去创建UI并调整音量。
在这里Android从5.0开始使用MediaSession对音量进行控制。通过MeidaSession相关的类,最终在MeidaSessionService中调用AudioService的adjustSuggestedStreamVolume()进行真正的音量设置的初步处理。
AudioService的adjustSuggestedStreamVolume()实现如下(省略部分代码):
int streamType;
boolean isMute = isMuteAdjust(direction);
if (mVolumeControlStream != -1) {
streamType = mVolumeControlStream;
} else {
streamType = getActiveStreamType(suggestedStreamType);
}
ensureValidStreamType(streamType);
final int resolvedStream = mStreamVolumeAlias[streamType];
……
if (mVolumeController.suppressAdjustment(resolvedStream, flags, isMute)) {
direction = 0;
flags &= ~AudioManager.FLAG_PLAY_SOUND;
flags &= ~AudioManager.FLAG_VIBRATE;
if (DEBUG_VOL) Log.d(TAG, "Volume controller suppressed adjustment");
}
adjustStreamVolume(streamType, direction, flags, callingPackage, caller, uid);
adjustSuggestedStreamVolume()负责接收MeidaSessionService传入的信息,然后针对要修改流类型获取相应的映射,更改是否显示ui的标志,然后将具体的调整音量操作交给adjustStreamVolume()去完成。
另外,关于这个adjustSuggestedStreamVolume()有点是需要特别说明一下。它刚开始的时候有一个判断,条件是一个名为mVolumeControlStream的整型变量是否等于-1,从这块代码来看,mVolumeControlStream比参数传入的suggestedStreamType厉害多了,只要它不是-1,那么要调整音量的流类型就是它。那这么厉害的控制手段,是做什么用的呢?其实,mVolumeControlStream是VolumeDialog(6.0似乎并不是VolumePanel)通过forceVolumeControlStream()函数设置的。什么是VolumeDialog呢?就是我们按下音量键后的那个音量条提示框了。VolumeDialog在显示时会调用forceVolumeControlStream强制后续的音量键操作固定为促使它显示的那个流类型。并在它关闭时取消这个强制设置,即置mVolumeControlStream为-1。
AudioService的adjustStreamVolume()实现如下(省略部分代码):
ensureValidDirection(direction);
ensureValidStreamType(streamType);
int streamTypeAlias = mStreamVolumeAlias[streamType];
VolumeStreamState streamState = mStreamStates[streamTypeAlias];
final int device = getDeviceForStream(streamTypeAlias);
int aliasIndex = streamState.getIndex(device);
boolean adjustVolume = true;
int step;
……
if ((streamTypeAlias == AudioSystem.STREAM_MUSIC) &&
((device & mFixedVolumeDevices) != 0)) {
flags |= AudioManager.FLAG_FIXED_VOLUME;
if (mSafeMediaVolumeState == SAFE_MEDIA_VOLUME_ACTIVE &&
(device & mSafeMediaVolumeDevices) != 0) {
step = mSafeMediaVolumeIndex;
} else {
step = streamState.getMaxIndex();
}
if (aliasIndex != 0) {
aliasIndex = step;
}
} else {
step = rescaleIndex(10, streamType, streamTypeAlias);
}
……
final int result = checkForRingerModeChange(aliasIndex, direction, step,
streamState.mIsMuted);
adjustVolume = (result & FLAG_ADJUST_VOLUME) != 0;
……
} else if (streamState.adjustIndex(direction * step, device, caller)
|| streamState.mIsMuted) {
sendMsg(mAudioHandler,
MSG_SET_DEVICE_VOLUME,
SENDMSG_QUEUE,
device,
0,
streamState,
0);
}
……
int index = mStreamStates[streamType].getIndex(device);
sendVolumeUpdate(streamType, oldIndex, index, flags);
}
AudioService的adjustStreamVolume ()针对音量设置做了很多的操作,所以在这里简单地总结一下这个函数都作了什么:
1) 准备工作。计算按下音量键的音量步进值。主要通过rescaleIndex()函数的实现。
2) 检查是否需要改变情景模式。checkForRingerModeChange()和情景模式有关。调用adjustIndex()更改VolumeStreamState对象中保存的音量值。
3) 通过sendMsg()发送消息MSG_SET_DEVICE_VOLUME到mAudioHandler。
4) 调用sendVolumeUpdate()函数,通知外界音量发生了变化。
VolumeStreamState是AudioService的一个内部类,当进行音量或者铃声模式管理时,需要锁定这个对象,避免顺序出错。
下面是VolumeSteramState获取音量和保存音量的操作:
public void readSettings() {
synchronized (VolumeStreamState.class) {
……
String name = getSettingNameForDevice(device);
int defaultIndex = (device == AudioSystem.DEVICE_OUT_DEFAULT) ?
AudioSystem.DEFAULT_STREAM_VOLUME[mStreamType] : -1;
int index = Settings.System.getIntForUser(
mContentResolver, name, defaultIndex, UserHandle.USER_CURRENT);
……
}
......
public boolean setIndex(int index, int device, String caller) {
……
synchronized (VolumeStreamState.class) {
oldIndex = getIndex(device);
index = getValidIndex(index);
……
mIndexMap.put(device, index);
changed = oldIndex != index;
if (changed) {
int numStreamTypes = AudioSystem.getNumStreamTypes();
for (int streamType = numStreamTypes - 1; streamType >= 0; streamType--) {
if (streamType != mStreamType &&
mStreamVolumeAlias[streamType] == mStreamType) {
……
}
mVolumeChanged.putExtra(AudioManager.EXTRA_VOLUME_STREAM_VALUE, index);
mVolumeChanged.putExtra(AudioManager.EXTRA_PREV_VOLUME_STREAM_VALUE, oldIndex);
mVolumeChanged.putExtra(AudioManager.EXTRA_VOLUME_STREAM_TYPE_ALIAS,
mStreamVolumeAlias[mStreamType]);
sendBroadcastToAll(mVolumeChanged);
}
return changed;
}
VolumeSteramState可以直接通过调用Settings.System.getIntForUser()去获取数据库中的音量,但是,在更新音量时,它只是更新了内部保存的音量而没有做更多的处理。所以,真正的更新音量的操作应该由mAudioHandler去处理。
从AudioService的adjustStreamVolume ()可以知道,adjustStreamVolume()给AudioHandler发送了带有“MSG_SET_DEVICE_VOLUME”的消息。AudioHandler根据此消息会进行setDeviceVolume()处理。
private void setDeviceVolume(VolumeStreamState streamState, int device) {
synchronized (VolumeStreamState.class) {
streamState.applyDeviceVolume_syncVSS(device);
……
sendMsg(mAudioHandler,
MSG_PERSIST_VOLUME,
SENDMSG_QUEUE,
device,
0,
streamState,
PERSIST_DELAY);
}
从上面代码可以看出,AudioService通过setDeviceVolume()真正地更改了音量。setDeviceVolume()先用过VolumeStremState调用AudioSystem的setStreamVolumeIndex()设置音量到底层的AudioFlinger里面去,然后在通过AudioHandler.persistVolume()将音量真正保存起来。这样就完成了大部分的音量调整了。