Android Input输入系统之五:KeyEvent按键调节音量加减流程

本文深入探讨了Android系统中按键Input事件,特别是KeyEvent如何处理音量加减的过程。从dispatchKeyEvent()开始,详细解析了 DecorView 分发事件、Activity回调、MediaController、MediaSessionRecord、VolumeProvider等组件在音量调节中的作用,最后到AudioService执行音量变更,完成了整个音量调节的流程分析。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

《Android按键Input KeyEvent》
《Android Input输入系统之一:KeyEvent事件监听及事件分发流程》
《Android Input输入系统之二:KeyEvent注入事件及事件分发流程》
《Android Input输入系统之三:KeyEvent事件分发和上层应用层对事件的接收》
《Android Input输入系统之四:KeyEvent事件中的InputChannel通信》
《Android Input输入系统之五:KeyEvent按键调节音量加减流程》

在上一章节,我们讲到了将按键消息传递分发给View。

在ViewPostImeInputStage阶段,调用了processKeyEvent(),其中:

// Deliver the key to the view hierarchy.
if (mView.dispatchKeyEvent(event)) {
   
    return FINISH_HANDLED;
}

通过dispatchKeyEvent将按键消息分发给了View.

那我们就从dispatchKeyEvent()说起。

View的根类是DecorView。

最终都会调用到DecorView中的dispatchKeyEvent()。
frameworks\base\core\java\com\android\internal\policy\DecorView.java

    @Override
    public boolean dispatchKeyEvent(KeyEvent event) {
   
        final int keyCode = event.getKeyCode();
        final int action = event.getAction();
        final boolean isDown = action == KeyEvent.ACTION_DOWN;

        if (isDown && (event.getRepeatCount() == 0)) {
   
            // First handle chording of panel key: if a panel key is held
            // but not released, try to execute a shortcut in it.
            if ((mWindow.mPanelChordingKey > 0) && (mWindow.mPanelChordingKey != keyCode)) {
   
                boolean handled = dispatchKeyShortcutEvent(event);
                if (handled) {
   
                    return true;
                }
            }

            // If a panel is open, perform a shortcut on it without the
            // chorded panel key
            if ((mWindow.mPreparedPanel != null) && mWindow.mPreparedPanel.isOpen) {
   
                if (mWindow.performPanelShortcut(mWindow.mPreparedPanel, keyCode, event, 0)) {
   
                    return true;
                }
            }
        }

        if (!mWindow.isDestroyed()) {
   
            final Window.Callback cb = mWindow.getCallback();
            final boolean handled = cb != null && mFeatureId < 0 ? cb.dispatchKeyEvent(event)
                    : super.dispatchKeyEvent(event);
            if (handled) {
   
                return true;
            }
        }

        return isDown ? mWindow.onKeyDown(mFeatureId, event.getKeyCode(), event)
                : mWindow.onKeyUp(mFeatureId, event.getKeyCode(), event);
    }

cb 是一个Activity或者Dialog对象。用于回调上层应用app中监听的dispatchKeyEvent();

如果经过所有应用层的View,Activity等都没有消耗掉key事件,最终会走到PhoneWindow中:

return isDown ? mWindow.onKeyDown(mFeatureId, event.getKeyCode(), event)
        : mWindow.onKeyUp(mFeatureId, event.getKeyCode(), event);

frameworks\base\core\java\com\android\internal\policy\PhoneWindow.java

    /**
     * A key was pressed down and not handled by anything else in the window.
     *
     * @see #onKeyUp
     * @see android.view.KeyEvent
     */
    protected boolean onKeyDown(int featureId, int keyCode, KeyEvent event) {
   
        /* ****************************************************************************
         * HOW TO DECIDE WHERE YOUR KEY HANDLING GOES.
         *
         * If your key handling must happen before the app gets a crack at the event,
         * it goes in PhoneWindowManager.
         *
         * If your key handling should happen in all windows, and does not depend on
         * the state of the current application, other than that the current
         * application can override the behavior by handling the event itself, it
         * should go in PhoneFallbackEventHandler.
         *
         * Only if your handling depends on the window, and the fact that it has
         * a DecorView, should it go here.
         * ****************************************************************************/

        final KeyEvent.DispatcherState dispatcher =
                mDecor != null ? mDecor.getKeyDispatcherState() : null;
        //Log.i(TAG, "Key down: repeat=" + event.getRepeatCount()
        //        + " flags=0x" + Integer.toHexString(event.getFlags()));

        switch (keyCode) {
   
            case KeyEvent.KEYCODE_VOLUME_UP:
            case KeyEvent.KEYCODE_VOLUME_DOWN:
            case KeyEvent.KEYCODE_VOLUME_MUTE: {
   
                // If we have a session send it the volume command, otherwise
                // use the suggested stream.
                if (mMediaController != null) {
   
                    mMediaController.dispatchVolumeButtonEventAsSystemService(event);
                } else {
   
                    getMediaSessionManager().dispatchVolumeKeyEventAsSystemService(event,
                            mVolumeControlStreamType);
                }
                return true;
            }
            
			//省略一部分代码
			//...
			
         }
         
		//省略一部分代码
		//...
		
    }

因为我们只分析音量的调节,这里列出来了KeyEvent.KEYCODE_VOLUME_UP的按键消息。

下面即使我们比较熟悉的音量调节流程了。我们走一遍这个流程。

这个流程分两个:
//UI 按钮画面上进行音量调节
dispatchVolumeButtonEventAsSystemService()

//硬件按键进行调节
dispatchVolumeKeyEventAsSystemService()

我们需要关注的是按键的调节。按钮调节逻辑简单跟一下。

按钮执行音量调节

dispatchVolumeButtonEventAsSystemService
frameworks\base\media\java\android\media\session\MediaController.java

public void dispatchVolumeButtonEventAsSystemService(@NonNull KeyEvent keyEvent) {
   
    switch (keyEvent.getAction()) {
   
        case KeyEvent.ACTION_DOWN: {
   
            int direction = 0;
            switch (keyEvent.getKeyCode()) {
   
                case KeyEvent.KEYCODE_VOLUME_UP:
                    direction = AudioManager.ADJUST_RAISE;
                    break;
                case KeyEvent.KEYCODE_VOLUME_DOWN:
                    direction = AudioManager.ADJUST_LOWER;
                    break;
                case KeyEvent.KEYCODE_VOLUME_MUTE:
                    direction = AudioManager.ADJUST_TOGGLE_MUTE;
                    break;
            }
            try {
   
                mSessionBinder.adjustVolume(mContext.getPackageName(), mCbStub, true, direction,
                        AudioManager.FLAG_SHOW_UI);
            } catch (RemoteException e) {
   
                Log.wtf(TAG, "Error calling adjustVolumeBy", e);
            }
        }

        case KeyEvent.ACTION_UP: {
   
            final int flags = AudioManager.FLAG_PLAY_SOUND | AudioManager.FLAG_VIBRATE
                    | AudioManager.FLAG_FROM_KEY;
            try {
   
                mSessionBinder.adjustVolume(mContext.getPackageName(), mCbStub, true, 0, flags);
            } catch (RemoteException e) {
   
                Log.wtf(TAG, "Error calling adjustVolumeBy", e);
            }
        }
    }
}

/frameworks/base/services/core/java/com/android/server/media/MediaSessionRecord.java
通过ISessionController的AIDL方式调用到MediaSessionRecord中的adjustVolume().
通过SessionCb中的回调,调用mCb.onAdjustVolume()即MediaSession中的onAdjustVolume().

\frameworks\base\media\java\android\media\session\MediaSession.java

@Override
public void onAdjustVolume(String packageName, int pid, 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

sunxiaolin2016

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值