Android按键音

按键音处理流程

按键音是用户按下后抬起时发出的声音,可在手机系统设置打开或者关闭。
代码分析基于 API 33

View层发起

逻辑在事件分发机制的onTouchEvent(MotionEvent event)方法中处理:


          switch (action) {
                case MotionEvent.ACTION_UP:
                    mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
                    if ((viewFlags & TOOLTIP) == TOOLTIP) {
                        handleTooltipUp();
                    }
                    if (!clickable) {
                        removeTapCallback();
                        removeLongPressCallback();
                        mInContextButtonPress = false;
                        mHasPerformedLongPress = false;
                        mIgnoreNextUpEvent = false;
                        break;
                    }
                    boolean prepressed = (mPrivateFlags & PFLAG_PREPRESSED) != 0;
                    if ((mPrivateFlags & PFLAG_PRESSED) != 0 || prepressed) {
                        // take focus if we don't have it already and we should in
                        // touch mode.
                        boolean focusTaken = false;
                        if (isFocusable() && isFocusableInTouchMode() && !isFocused()) {
                            focusTaken = requestFocus();
                        }

                        if (prepressed) {
                            // The button is being released before we actually
                            // showed it as pressed.  Make it show the pressed
                            // state now (before scheduling the click) to ensure
                            // the user sees it.
                            setPressed(true, x, y);
                        }

                        if (!mHasPerformedLongPress && !mIgnoreNextUpEvent) {
                            // This is a tap, so remove the longpress check
                            removeLongPressCallback();

                            // Only perform take click actions if we were in the pressed state
                            if (!focusTaken) {
                                // Use a Runnable and post this rather than calling
                                // performClick directly. This lets other visual state
                                // of the view update before click actions start.
                                if (mPerformClick == null) {
                                    mPerformClick = new PerformClick();
                                }
                                if (!post(mPerformClick)) {
                                	//点击处理
                                    performClickInternal();
                                }
                            }
                        }

                        if (mUnsetPressedState == null) {
                            mUnsetPressedState = new UnsetPressedState();
                        }

                        if (prepressed) {
                            postDelayed(mUnsetPressedState,
                                    ViewConfiguration.getPressedStateDuration());
                        } else if (!post(mUnsetPressedState)) {
                            // If the post failed, unpress right now
                            mUnsetPressedState.run();
                        }

                        removeTapCallback();
                    }
                    mIgnoreNextUpEvent = false;
                    break;

    private boolean performClickInternal() {
        // Must notify autofill manager before performing the click actions to avoid scenarios where
        // the app has a click listener that changes the state of views the autofill service might
        // be interested on.
        notifyAutofillManagerOnClick();

        return performClick();
    }

    public boolean performClick() {
        // We still need to call this method to handle the cases where performClick() was called
        // externally, instead of through performClickInternal()
        notifyAutofillManagerOnClick();

        final boolean result;
        final ListenerInfo li = mListenerInfo;
        if (li != null && li.mOnClickListener != null) {
        	//播放按键音 SoundEffectConstants.CLICK
            playSoundEffect(SoundEffectConstants.CLICK);
            li.mOnClickListener.onClick(this);
            result = true;
        } else {
            result = false;
        }

        sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);

        notifyEnterOrExitForAutoFillIfNeeded(true);

        return result;
    }

//声音类型
public class SoundEffectConstants {

    private SoundEffectConstants() {}
    private static final Random NAVIGATION_REPEAT_RANDOMIZER = new Random();
    private static int sLastNavigationRepeatSoundEffectId = -1;

	// 按键音 
    public static final int CLICK = 0;

    /** Effect id for a navigation left */
    public static final int NAVIGATION_LEFT = 1;
    /** Effect id for a navigation up */
    public static final int NAVIGATION_UP = 2;
    /** Effect id for a navigation right */
    public static final int NAVIGATION_RIGHT = 3;
    /** Effect id for a navigation down */
    public static final int NAVIGATION_DOWN = 4;
    /** Effect id for a repeatedly triggered navigation left, e.g. due to long pressing a button */
    public static final int NAVIGATION_REPEAT_LEFT = 5;
    /** Effect id for a repeatedly triggered navigation up, e.g. due to long pressing a button */
    public static final int NAVIGATION_REPEAT_UP = 6;
    /** Effect id for a repeatedly triggered navigation right, e.g. due to long pressing a button */
    public static final int NAVIGATION_REPEAT_RIGHT = 7;
    /** Effect id for a repeatedly triggered navigation down, e.g. due to long pressing a button */
    public static final int NAVIGATION_REPEAT_DOWN = 8;
}

   public void playSoundEffect(@SoundEffectConstants.SoundEffect int soundConstant) {
        if (mAttachInfo == null || mAttachInfo.mRootCallbacks == null || !isSoundEffectsEnabled()) {
            return;
        }
        mAttachInfo.mRootCallbacks.playSoundEffect(soundConstant);
    }

    /**
     * A set of information given to a view when it is attached to its parent
     * window.
     */
    final static class AttachInfo {

        interface Callbacks {
        	//实现类ViewRootImpl
            void playSoundEffect(int effectId);
            boolean performHapticFeedback(int effectId, boolean always);
        }
	public final class ViewRootImpl implements ViewParent,
        View.AttachInfo.Callbacks, ThreadedRenderer.DrawCallbacks,
        AttachedSurfaceControl {
		
    @Override
    public void playSoundEffect(@SoundEffectConstants.SoundEffect int effectId) {
        checkThread();

        try {
            final AudioManager audioManager = getAudioManager();

            if (mFastScrollSoundEffectsEnabled
                    && SoundEffectConstants.isNavigationRepeat(effectId)) {
                audioManager.playSoundEffect(
                        SoundEffectConstants.nextNavigationRepeatSoundEffectId());
                return;
            }

            switch (effectId) {
            	//按键声
                case SoundEffectConstants.CLICK:
                	//   public static final int FX_KEY_CLICK = 0;
                    audioManager.playSoundEffect(AudioManager.FX_KEY_CLICK);
                    return;
                case SoundEffectConstants.NAVIGATION_DOWN:
                case SoundEffectConstants.NAVIGATION_REPEAT_DOWN:
                    audioManager.playSoundEffect(AudioManager.FX_FOCUS_NAVIGATION_DOWN);
                    return;
                case SoundEffectConstants.NAVIGATION_LEFT:
                case SoundEffectConstants.NAVIGATION_REPEAT_LEFT:
                    audioManager.playSoundEffect(AudioManager.FX_FOCUS_NAVIGATION_LEFT);
                    return;
                case SoundEffectConstants.NAVIGATION_RIGHT:
                case SoundEffectConstants.NAVIGATION_REPEAT_RIGHT:
                    audioManager.playSoundEffect(AudioManager.FX_FOCUS_NAVIGATION_RIGHT);
                    return;
                case SoundEffectConstants.NAVIGATION_UP:
                case SoundEffectConstants.NAVIGATION_REPEAT_UP:
                    audioManager.playSoundEffect(AudioManager.FX_FOCUS_NAVIGATION_UP);
                    return;
                default:
                    throw new IllegalArgumentException("unknown effect id " + effectId +
                            " not defined in " + SoundEffectConstants.class.getCanonicalName());
            }
        } catch (IllegalStateException e) {
            // Exception thrown by getAudioManager() when mView is null
            Log.e(mTag, "FATAL EXCEPTION when attempting to play sound effect: " + e);
            e.printStackTrace();
        }
    }
AudioManager/**
     * Plays a sound effect (Key clicks, lid open/close...)
     * @param effectType The type of sound effect.
     * NOTE: This version uses the UI settings to determine
     * whether sounds are heard or not.
     */
    public void playSoundEffect(@SystemSoundEffect int effectType) {
        if (effectType < 0 || effectType >= NUM_SOUND_EFFECTS) {
            return;
        }

        if (!querySoundEffectsEnabled(Process.myUserHandle().getIdentifier())) {
            return;
        }
        //走到服务层
        final IAudioService service = getService();
        try {
            service.playSoundEffect(effectType);
        } catch (RemoteException e) {
            throw e.rethrowFromSystemServer();
        }
    }

AudioService


    /** @see AudioManager#playSoundEffect(int) */
    public void playSoundEffect(int effectType) {
        playSoundEffectVolume(effectType, -1.0f);
    }

    /** @see AudioManager#playSoundEffect(int, float) */
    public void playSoundEffectVolume(int effectType, float volume) {
        // do not try to play the sound effect if the system stream is muted
        if (isStreamMute(STREAM_SYSTEM)) {
            return;
        }

        if (effectType >= AudioManager.NUM_SOUND_EFFECTS || effectType < 0) {
            Log.w(TAG, "AudioService effectType value " + effectType + " out of range");
            return;
        }

		//重点  private static final int MSG_PLAY_SOUND_EFFECT = 5;
		// effectType是0,这里MSG_PLAY_SOUND_EFFECT 会作为msg在handle中处理
        sendMsg(mAudioHandler, MSG_PLAY_SOUND_EFFECT, SENDMSG_QUEUE,
                effectType, (int) (volume * 1000), null, 0);
    }

    private static void sendMsg(Handler handler, int msg,
            int existingMsgPolicy, int arg1, int arg2, Object obj, int delay) {
        if (existingMsgPolicy == SENDMSG_REPLACE) {
            handler.removeMessages(msg);
        } else if (existingMsgPolicy == SENDMSG_NOOP && handler.hasMessages(msg)) {
            return;
        }

        final long time = SystemClock.uptimeMillis() + delay;
        handler.sendMessageAtTime(handler.obtainMessage(msg, arg1, arg2, obj), time);
    }
    /** Handles internal volume messages in separate volume thread. */
    private class AudioHandler extends Handler {

	
@Override
        public void handleMessage(Message msg) {
            switch (msg.what) {
				// 有很多case,只截取了重点
                case MSG_PLAY_SOUND_EFFECT:
                    mSfxHelper.playSoundEffect(msg.arg1, msg.arg2);
                    break;
            }
        }
	}

9.0还没有这个类
/**
 * A helper class for managing sound effects loading / unloading
 * used by AudioService. As its methods are called on the message handler thread
 * of AudioService, the actual work is offloaded to a dedicated thread.
 * This helps keeping AudioService responsive.
 * 用于管理AudioService使用的声音效果加载/卸载的助手类。当在AudioService的消息处理程序线程上调用其方法时,实际工作被卸载到专用线程。这有助于保持AudioService的响应性。
 *
 * @hide
 */
class SoundEffectsHelper {

   /*package*/ void playSoundEffect(int effect, int volume) {
   		//  private static final int MSG_PLAY_EFFECT = 2;
        sendMsg(MSG_PLAY_EFFECT, effect, volume, null, 0);
    }
    
	 private void sendMsg(int msg, int arg1, int arg2, Object obj, int delayMs) {
	        mSfxHandler.sendMessageDelayed(mSfxHandler.obtainMessage(msg, arg1, arg2, obj), delayMs);
	} 
	
    private class SfxHandler extends Handler {
        @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {
                case MSG_LOAD_EFFECTS:
                    onLoadSoundEffects((OnEffectsLoadCompleteHandler) msg.obj);
                    break;
                case MSG_UNLOAD_EFFECTS:
                    onUnloadSoundEffects();
                    break;
                case MSG_PLAY_EFFECT:
                    final int effect = msg.arg1, volume = msg.arg2;
                    onLoadSoundEffects(new OnEffectsLoadCompleteHandler() {
                        @Override
                        public void run(boolean success) {
                            if (success) {
                                onPlaySoundEffect(effect, volume);
                            }
                        }
                    });
                    break;
                case MSG_LOAD_EFFECTS_TIMEOUT:
                    if (mSoundPoolLoader != null) {
                        mSoundPoolLoader.onTimeout();
                    }
                    break;
            }
        }
    }

    void onPlaySoundEffect(int effect, int volume) {
        float volFloat;
        // use default if volume is not specified by caller
        //计算音量
        if (volume < 0) {
        	//可配置音量 路径 :  /frameworks/base/core/res/res/values/config.xml
        	//      mSfxAttenuationDb = mContext.getResources().getInteger(com.android.internal.R.integer.config_soundEffectVolumeDb);
            volFloat = (float) Math.pow(10, (float) mSfxAttenuationDb / 20);
        } else {
            volFloat = volume / 1000.0f;
        }

        Resource res = mResources.get(mEffects[effect]);
        if (mSoundPool != null && res.mSampleId != EFFECT_NOT_IN_SOUND_POOL && res.mLoaded) {
        	// The SoundPool class manages and plays audio resources for applications.
        	//  SoundPool适合短声音播放
            mSoundPool.play(res.mSampleId, volFloat, volFloat, 0, 0, 1.0f);
        } else {
            MediaPlayer mediaPlayer = new MediaPlayer();
            try {
                String filePath = getResourceFilePath(res);
                mediaPlayer.setDataSource(filePath);
                mediaPlayer.setAudioStreamType(AudioSystem.STREAM_SYSTEM);
                mediaPlayer.prepare();
                mediaPlayer.setVolume(volFloat);
                mediaPlayer.setOnCompletionListener(new OnCompletionListener() {
                    public void onCompletion(MediaPlayer mp) {
                        cleanupPlayer(mp);
                    }
                });
                mediaPlayer.setOnErrorListener(new OnErrorListener() {
                    public boolean onError(MediaPlayer mp, int what, int extra) {
                        cleanupPlayer(mp);
                        return true;
                    }
                });
                mediaPlayer.start();
            } catch (IOException ex) {
                Log.w(TAG, "MediaPlayer IOException: " + ex);
            } catch (IllegalArgumentException ex) {
                Log.w(TAG, "MediaPlayer IllegalArgumentException: " + ex);
            } catch (IllegalStateException ex) {
                Log.w(TAG, "MediaPlayer IllegalStateException: " + ex);
            }
        }
    }
}
// 初始化
/**
 * AudioAttributes.USAGE_ASSISTANCE_SONIFICATION
 * 使用量超声化时使用的使用值,例如用户界面声音。
 * 常量值:13(0x0000000d)
 *
 *
 */
 mSoundPool = new SoundPool.Builder()
                .setMaxStreams(NUM_SOUNDPOOL_CHANNELS)
                .setAudioAttributes(new AudioAttributes.Builder()
                        .setUsage(AudioAttributes.USAGE_ASSISTANCE_SONIFICATION)
                        .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
                        .build())
                .build();
        loadSoundAssets();
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值