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

前段时间,因为公司需求与音量键的处理流程相关,于是跟了相关源码,因此记录在博客,分享给大家。
在音量键被按下后,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()将音量真正保存起来。这样就完成了大部分的音量调整了。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值