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
- Android客制化------音量键的处理流程
- Android TV的音量键实现流程
- Android 4.4音量键控制音量流程
- Android 音量键的监听
- android 音量控制 流程梳理
- android 音量控制 流程梳理
- Android 音量控制流程分析
- android audio音量控制流程
- Android Audio音量设置流程
- 005 音量上下键调节音量流程
- android音量键调节听筒音量的大小
- Android 按音量键的调用栈
- Android 4.4 音量调节流程分析(一)
- Android 4.4 音量调节流程分析(二)
- Android 4.4 音量调节流程分析(一)
- Android 4.4 音量调节流程分析
- Android音量设置流程干货版
- Android音量设置流程干货版
- 服务注册与发现
- HDU 1950 Bridging signals (最长递增子序列(nlogn算法)
- EditText限制输入的字符数并弹出Toast提示字数已达上限
- 理解神经网络梯度爆炸和梯度弥散问题
- DTO、Model,ViewModel,Object,Entity作用(转载)
- Android客制化------音量键的处理流程
- Spark函数传递:闭包和单例模式
- 第一周:[LeetCode]6. ZigZag Conversion
- Android 自定义View (三) 圆环交替 等待效果
- 为什么redis不共享包含字符串的对象
- Java资源大全中文版
- C# 控制台应用程序
- Java程序实现将数组元素存储到mysql数据库表中
- Matlab画图线型、符号及颜色汇总