Android客制化------音量键的处理流程

来源:互联网 发布:淘宝开店条件和流程 编辑:程序博客网 时间:2024/06/06 09:10
前段时间,因为公司需求与音量键的处理流程相关,于是跟了相关源码,因此记录在博客,分享给大家。在音量键被按下后,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: {           ……           /* 在这里,先判断mMediaController是否为空(显示的音量调整UI是否还存在),假如存在,就直接调用mMediaController的adjustVolume进行调整音量。不存在就通过MediaSession去创建UI并调整。 */           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 {              //通过getActiveStreamType()函数获取要控制的流类型              streamType = getActiveStreamType(suggestedStreamType);          }          ensureValidStreamType(streamType);          final int resolvedStream = mStreamVolumeAlias[streamType];          ……          // For notifications/ring, show the ui before making any adjustments          // Don't suppress mute/unmute requests          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);          // 首先还是获取streamType映射到的流类型。          int streamTypeAlias = mStreamVolumeAlias[streamType];          VolumeStreamState streamState = mStreamStates[streamTypeAlias];          final int device = getDeviceForStream(streamTypeAlias);          //然后获取这个streamType的当前音量           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);          }           ……          //判断是否该改变情景模式。例如当从震动转换成响铃时,不需要更改音量。adjustVolume作为一个控制量,控制是否需要更改音量。              final int result = checkForRingerModeChange(aliasIndex, direction, step,                      streamState.mIsMuted);              adjustVolume = (result & FLAG_ADJUST_VOLUME) != 0;              ……              //调用adjustIndex()更改VolumeStreamState对象中保存的音量值             } else if (streamState.adjustIndex(direction * step, device, caller)                      || streamState.mIsMuted) {  //发送消息给AudioHandle,更改音量。                  sendMsg(mAudioHandler,                          MSG_SET_DEVICE_VOLUME,                          SENDMSG_QUEUE,                          device,                          0,                          streamState,                          0);              }              ……          //最后通过sendVolumeUpdate去通知音量已经发生变化了。          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;                      //通过读取setting数据库去获取值  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中保存设置的音量值                  mIndexMap.put(device, index);                  changed = oldIndex != index;                  if (changed) {                      // 同时设置所有映射到当前流类型的其他流的音量                    boolean currentDevice = (device == getDeviceForStream(mStreamType));                      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) {                  //通过VolumeStreamState调用AudioSystem的setStreamVolumeIndex()设置音量到底层的AudioFlinger里面去                  streamState.applyDeviceVolume_syncVSS(device);                  ……              //继续给AudioHandler发送信息,调用persistVolume(),通过System.putIntForUser()将目标音量保存在Setting数据库中              sendMsg(mAudioHandler,                      MSG_PERSIST_VOLUME,                      SENDMSG_QUEUE,                      device,                      0,                      streamState,                      PERSIST_DELAY);          }  
从上面代码可以看出,AudioService通过setDeviceVolume()真正地更改了音量。setDeviceVolume()先用过VolumeStremState调用AudioSystem的setStreamVolumeIndex()设置音量到底层的AudioFlinger里面去,然后在通过AudioHandler.persistVolume()将音量真正保存起来。这样就完成了大部分的音量调整了。
0 0
原创粉丝点击