概述
按键音处理流程
- View层发起
- AudioService
按键音是用户按下后抬起时发出的声音,可在手机系统设置打开或者关闭。
代码分析基于 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();
最后
以上就是香蕉香氛为你收集整理的Android按键音View层发起AudioService的全部内容,希望文章能够帮你解决Android按键音View层发起AudioService所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
发表评论 取消回复