一、按键音调用流程
摘要:按键音播放的总体逻辑是先找到系统中按键音的资源,然后调用SoundPool.load让系统加载音频资源,加载成功后在onLoadComplete回调中会返回一个非0的soundID ,用于播放时指定特定的音频,最后在需要播放按键音的时候直接根据soundID播放
1.Android按键音接口
Android按键音只有两个常用接口,分别是:
- 原生设置APP中SoundFragment.java调用的设置按键音开关的接口:mAudioManager.loadSoundEffects()和mAudioManager.unloadSoundEffects()
private void setSoundEffectsEnabled(boolean enabled) {
mAudioManager = (AudioManager) getActivity().getSystemService(Context.AUDIO_SERVICE); //1
if (enabled) {
mAudioManager.loadSoundEffects();
} else {
mAudioManager.unloadSoundEffects();
}
Settings.System.putInt(getActivity().getContentResolver(),
Settings.System.SOUND_EFFECTS_ENABLED, enabled ? 1 : 0);
}
先调用AudioManager的loadSoundEffects方法,然后会调用到AudioService的loadSoundEffects方法
- View.java中播放按键音的接口:playSoundEffect
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) {
playSoundEffect(SoundEffectConstants.CLICK);//调用会经过ViewRootImpl.java,最终调用到AudioService中
li.mOnClickListener.onClick(this);
result = true;
} else {
result = false;
}
sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);
notifyEnterOrExitForAutoFillIfNeeded(true);
return result;
}
最终会调用到AudioService的playSoundEffect方法
2.onLoadSoundEffects()方法
上述的两个方法调用到AudioService之后,分别通过sendMsg向handler发送MSG_LOAD_SOUND_EFFECTS和MSG_PLAY_SOUND_EFFECT信息,handler在收到信息后会进行相应的操作,但是不管是哪个操作,都会调用到onLoadSoundEffects()方法
loadSoundEffects的调用流程(非重点):
public boolean loadSoundEffects() {
int attempts = 3;
LoadSoundEffectReply reply = new LoadSoundEffectReply();
synchronized (reply) {
//调用sendMsg方法
sendMsg(mAudioHandler, MSG_LOAD_SOUND_EFFECTS, SENDMSG_QUEUE, 0, 0, reply, 0);
while ((reply.mStatus == 1) && (attempts-- > 0)) {
try {
reply.wait(SOUND_EFFECTS_LOAD_TIMEOUT_MS);
} catch (InterruptedException e) {
Log.w(TAG, "loadSoundEffects Interrupted while waiting sound pool loaded.");
}
}
}
return (reply.mStatus == 0);
}
//sendMsg方法是对handler.sendMessageAtTime的封装
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;
}
synchronized (mLastDeviceConnectMsgTime) {
long time = SystemClock.uptimeMillis() + delay;
if (msg == MSG_SET_A2DP_SRC_CONNECTION_STATE ||
msg == MSG_SET_A2DP_SINK_CONNECTION_STATE ||
msg == MSG_SET_HEARING_AID_CONNECTION_STATE ||
msg == MSG_SET_WIRED_DEVICE_CONNECTION_STATE ||
msg == MSG_A2DP_DEVICE_CONFIG_CHANGE ||
msg == MSG_BTA2DP_DOCK_TIMEOUT) {
if (mLastDeviceConnectMsgTime >= time) {
// add a little delay to make sure messages are ordered as expected
time = mLastDeviceConnectMsgTime + 30;
}
mLastDeviceConnectMsgTime = time;
}
handler.sendMessageAtTime(handler.obtainMessage(msg, arg1, arg2, obj), time);
}
}
//在handleMessage中处理消息
@Override
public void handleMessage(Message msg) {
......
case MSG_PLAY_SOUND_EFFECT:
//调用onPlaySoundEffect
onPlaySoundEffect(msg.arg1, msg.arg2);
break;
}
private void onPlaySoundEffect(int effectType, int volume) {
synchronized (mSoundEffectsLock) {
//最终会调用到onLoadSoundEffects
onLoadSoundEffects();
......
}
playSoundEffect的调用流程(非重点):
public void playSoundEffect(int effectType) {
playSoundEffectVolume(effectType, -1.0f);
}
public void playSoundEffectVolume(int effectType, float volume) {
// do not try to play the sound effect if the system stream is muted
if (isStreamMutedByRingerOrZenMode(STREAM_SYSTEM)) {
return;
}
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