android 点击播放声音,浅谈Android O Touch声音播放流程

前言

当我们点击屏幕按键时,就会听到touch音,那么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类源码解析,我们从performFocusNavigation()入手

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;

}

//查询是否开启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里对消息的处理,关键代码如下

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~

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值