前言
當我們點擊屏幕按鍵時,就會聽到touch音,那麼touch音是如何播放起來的呢,由於最近項目需求順便熟悉下了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類源碼解析,這裏touch邏輯
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了,音量控制,聲音焦點申請等。接着看
public void playSoundEffect(int effectType) {
if (effectType < 0 || effectType >= NUM_SOUND_EFFECTS) {
return;
}
//查詢是否開啓透徹音,如果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裏對消息的處理,關鍵代碼如下
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()的方法
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上等。
總結
touch音的流程就簡單分析到這裏,歡迎大家交流指正。
努力學習ing~