Android升到5.0或5.1之后,细心的同学可能会发现,在我们调节音量的时候,无法调到静音模式。音量调到1的时候,再往下调,就变成震动模式了。如果你说再按下键不就是静音模式了吗?其实这不是静音模式,而是禁止打扰的情景模式ZenMode。ZenMode分为禁止打扰,仅容许优先打扰内容,一律容许打扰。即在音量seekBar下面的3个按钮。而静音模式,震动模式,正常模式是属于RingerMode的。你可以用你的手机测试一下。你按音量键往下调到震动,再往上调一格,此时音量显示为1,图标是正常模式,然后重启手机。手机重启之后,进入Settings->Sound & notification,你会发现Ring volume的seekBar为0,但是图标显示的是正常模式。明明我在重启之前手机音量1了,而且手机的Ringtone是有声音的,怎么重启之后就变成0了呢?网上有人说用这个方法把手机就调成静音模式了,其实这不是静音模式,而是音量为0的正常模式。
在我们按音量键调节音量时,首先在PhoneWindow.java中,被onKeyDown和onKeyUp捕获。最终的处理是在AudioService中实现,AudioService位于frameworks/base/media/java/android/media/下。
最终实现的方法是
private void adjustStreamVolume(int streamType, int direction, int flags,String callingPackage, int uid) {
………………
if (((flags & AudioManager.FLAG_ALLOW_RINGER_MODES) != 0) ||
(streamTypeAlias == getMasterStreamType())) {
………………
final int result = checkForRingerModeChange(aliasIndex, direction, step);
adjustVolume = (result & FLAG_ADJUST_VOLUME) != 0;
…………………
}
…………………
}
这部分是我们关心的,其中
final int result = checkForRingerModeChange(aliasIndex, direction, step);
adjustVolume = (result & FLAG_ADJUST_VOLUME) != 0;
调用了
checkForRingerModeChange(aliasIndex, direction, step);
我们看看这个方法做了什么。
private int checkForRingerModeChange(int oldIndex, int direction, int step) {
int result = FLAG_ADJUST_VOLUME;
int ringerMode = getRingerModeInternal();
switch (ringerMode) {
…………………
case RINGER_MODE_VIBRATE:
……………………
if ((direction == AudioManager.ADJUST_LOWER)) {
…………………
} else if (direction == AudioManager.ADJUST_RAISE) {
ringerMode = RINGER_MODE_NORMAL;
}
result &= ~FLAG_ADJUST_VOLUME;
break;
………………………
setRingerMode(ringerMode, TAG + ".checkForRingerModeChange", false /*external*/);
return result;
}
可以看到
result &= ~FLAG_ADJUST_VOLUME;
而回到adjustStreamVolume
adjustVolume = (result & FLAG_ADJUST_VOLUME) != 0;
显然adjustVolume为FALSE,因此不会进入这个分支,
if (adjustVolume && (direction != AudioManager.ADJUST_SAME)) {
……………
sendMsg(mAudioHandler,
MSG_SET_DEVICE_VOLUME,
SENDMSG_QUEUE,
device,
0,
streamState,
0);
…………………
}
这个消息是设置硬件音量,通过handleMessage调用
private void setDeviceVolume(VolumeStreamState streamState, int device) {
…………………
sendMsg(mAudioHandler,
MSG_PERSIST_VOLUME,
SENDMSG_QUEUE,
device,
0,
streamState,
PERSIST_DELAY);
}
省略的是具体的设置相关硬件的音量,而我们关心的是这个Message,通过handleMessage调用,
private void persistVolume(VolumeStreamState streamState, int device) {
…………………………
System.putIntForUser(mContentResolver,
streamState.getSettingNameForDevice(device),
(streamState.getIndex(device) + 5)/ 10,
UserHandle.USER_CURRENT);
}
这个方法很简单,就是将音量值保存到数据库。
在前面我们可以看到,在从0跳到1,与从1到2相比多了一个RingerMode的设置,也就是从震动模式到正常模式。因此不会发出设置音量的这个消息。但是此时ringtone确实是有声音的。其实是在checkForRingerModeChange方法中,调用了
setRingerMode(ringerMode, TAG + ".checkForRingerModeChange", false /*external*/);
因此它将设置音量的工作交给setRingerMode。既然RingerMode跟音量有关,这样做也是为了避免重复和冲突。而setRingerMode最终会调到setRingerModeInt方法。
private void setRingerModeInt(int ringerMode, boolean persist) {
…………………
for (int streamType = numStreamTypes - 1; streamType >= 0; streamType--) {
…………………………
if (!shouldMute) {
if ((isPlatformVoice() || mHasVibrator) &&
mStreamVolumeAlias[streamType] == AudioSystem.STREAM_RING) {
Set set = mStreamStates[streamType].mIndex.entrySet();
Iterator i = set.iterator();
while (i.hasNext()) {
Map.Entry entry = (Map.Entry)i.next();
if ((Integer)entry.getValue() == 0) {
entry.setValue(10);
}
}
}
}
……………………
}
………………………
}
通过循环将相关的Stream音量设为1,因此ringtone是有声音的,但是并没有send message来将音量值写到数据库中,此时数据库的值任然为0,重启手机需要这个值来初始化,因此出现我们在开头描述的现象。