当咱们点击屏幕按键时,就会听到touch音,那么touch音是如何播放起来的呢,因为最近项目需求顺便熟悉下了touch音的逻辑。java
谈touch逻辑首先要说下这个类ViewRootImpl.java,位于frameworks/base/core/java/android/view下,ViewRootImpl的主要功能:
A:连接WindowManager和DecorView的纽带,更广一点能够说是Window和View之间的纽带。
B:完成View的绘制过程,包括measure、layout、draw过程。
C:向DecorView分发收到的用户发起的event事件,如按键,触屏等事件。
关于ViewRootImpl的源码可参照博客ViewRootImpl类源码解析,咱们从performFocusNavigation()入手android
private boolean performFocusNavigation(KeyEvent event) { //略 if (v.requestFocus(direction, mTempRect)) { playSoundEffect(SoundEffectConstants.getContantForFocusDirection(direction)); return true; } //略 return false; }
当咱们点击某个控件时,会先触发performFocusNavigation()这个方法,而后当控件获取到focus后便会调用playSoundEffect()方法,我只截取了performFocusNavigation()中关键代码playSoundEffect()部分,来看下playSoundEffect()这个方法学习
public void playSoundEffect(int effectId) { checkThread(); try { final AudioManager audioManager = getAudioManager(); switch (effectId) { case SoundEffectConstants.CLICK: audioManager.playSoundEffect(AudioManager.FX_KEY_CLICK); return; case SoundEffectConstants.NAVIGATION_DOWN: audioManager.playSoundEffect(AudioManager.FX_FOCUS_NAVIGATION_DOWN); return; case SoundEffectConstants.NAVIGATION_LEFT: audioManager.playSoundEffect(AudioManager.FX_FOCUS_NAVIGATION_LEFT); return; case SoundEffectConstants.NAVIGATION_RIGHT: audioManager.playSoundEffect(AudioManager.FX_FOCUS_NAVIGATION_RIGHT); return; case SoundEffectConstants.NAVIGATION_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的playSoundEffect()方法,audiomanager就不说了,接触android audio最早接触的可能就是AudioManager了,音量控制,声音焦点申请等。接着看.net
public void playSoundEffect(int effectType) { if (effectType < 0 || effectType >= NUM_SOUND_EFFECTS) { return; } //查询是否开启touch音,若是settings中关闭了,则直接返回 if (!querySoundEffectsEnabled(Process.myUserHandle().getIdentifier())) { return; } final IAudioService service = getService(); try { //调用到AudioService的playSoundEffect() service.playSoundEffect(effectType); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } }
查询touch音是否可播放,由于毕竟在android的setting中有个touch音的开关,若是可播放则调用到AudioService的playSoundEffect()线程
public void playSoundEffect(int effectType) { playSoundEffectVolume(effectType, -1.0f); } public void playSoundEffectVolume(int effectType, float volume) { if (effectType >= AudioManager.NUM_SOUND_EFFECTS || effectType < 0) { Log.w(TAG, "AudioService effectType value " + effectType + " out of range"); return; } sendMsg(mAudioHandler, MSG_PLAY_SOUND_EFFECT, SENDMSG_QUEUE, effectType, (int) (volume * 1000), null, 0); }
其实AudioService初始化的时候会建立一个子线HandlerThread,HandlerThread主要处理一些相对耗时的操做,这里将播放touch音的功能放在了这个子线程中去执行,这样避免了主线程的阻塞,其实你们在作mediaplayer播放时也建议放在子线程去播放,接下来看看handler里对消息的处理,关键代码以下code
case MSG_PLAY_SOUND_EFFECT: if (msg.obj == null) { onPlaySoundEffect(msg.arg1, msg.arg2, 0); } else { onPlaySoundEffect(msg.arg1, msg.arg2, (int) msg.obj); } break;
直接调用onPlaySoundEffect()的方法orm
private void onPlaySoundEffect(int effectType, int volume) { synchronized (mSoundEffectsLock) { //初始化mSoundPool和要播放的资源文件 onLoadSoundEffects(); if (mSoundPool == null) { return; } float volFloat; // use default if volume is not specified by caller if (volume < 0) { volFloat = (float)Math.pow(10, (float)sSoundEffectVolumeDb/20); } else { volFloat = volume / 1000.0f; } if (SOUND_EFFECT_FILES_MAP[effectType][1] > 0) { //播放touch音 mSoundPool.play(SOUND_EFFECT_FILES_MAP[effectType][1], volFloat, volFloat, 0, 0, 1.0f); } else { MediaPlayer mediaPlayer = new MediaPlayer(); try { String filePath = Environment.getRootDirectory() + SOUND_EFFECTS_PATH + SOUND_EFFECT_FILES.get(SOUND_EFFECT_FILES_MAP[effectType][0]); 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); } } } }
最终经过soundPool来播放指定的资源文件实现了touch音的播放,所以你们在工做中若是有什么须要对应touch音的逻辑,可参照AudioService的onPlaySoundEffect()中的逻辑。
好比指定touch音的AudioAttributes使touch音输出到指定的device上等。blog
touch音的流程就简单分析到这里,欢迎你们交流指正。
努力学习ing~事件