遇到的問題:
用戶的應(yīng)用通過遙控器按鍵切換焦點(diǎn)時(shí),已經(jīng)開啟了系統(tǒng)音量,但是沒有切換焦點(diǎn)時(shí)沒有提示聲迎膜。
所以有了本篇文章书斜,一是Key的事件分發(fā)邏輯伦连,二是AudioManager.playSoundEffect源碼分析。源碼基于android12袭异。
1. Key事件的分發(fā)邏輯
/frameworks/base/core/java/android/view/ViewRootImpl.java
private int processKeyEvent(QueuedInputEvent q) {
final KeyEvent event = (KeyEvent) q.mEvent;
if (mUnhandledKeyManager.preViewDispatch(event)) {
return FINISH_HANDLED;
}
// Deliver the key to the view hierarchy.
if (mView.dispatchKeyEvent(event)) {
return FINISH_HANDLED;
}
if (shouldDropInputEvent(q)) {
return FINISH_NOT_HANDLED;
}
// This dispatch is for windows that don't have a Window.Callback. Otherwise,
// the Window.Callback usually will have already called this (see
// DecorView.superDispatchKeyEvent) leaving this call a no-op.
if (mUnhandledKeyManager.dispatch(mView, event)) {
return FINISH_HANDLED;
}
int groupNavigationDirection = 0;
if (event.getAction() == KeyEvent.ACTION_DOWN
&& event.getKeyCode() == KeyEvent.KEYCODE_TAB) {
if (KeyEvent.metaStateHasModifiers(event.getMetaState(), KeyEvent.META_META_ON)) {
groupNavigationDirection = View.FOCUS_FORWARD;
} else if (KeyEvent.metaStateHasModifiers(event.getMetaState(),
KeyEvent.META_META_ON | KeyEvent.META_SHIFT_ON)) {
groupNavigationDirection = View.FOCUS_BACKWARD;
}
}
// If a modifier is held, try to interpret the key as a shortcut.
if (event.getAction() == KeyEvent.ACTION_DOWN
&& !KeyEvent.metaStateHasNoModifiers(event.getMetaState())
&& event.getRepeatCount() == 0
&& !KeyEvent.isModifierKey(event.getKeyCode())
&& groupNavigationDirection == 0) {
if (mView.dispatchKeyShortcutEvent(event)) {
return FINISH_HANDLED;
}
if (shouldDropInputEvent(q)) {
return FINISH_NOT_HANDLED;
}
}
// Apply the fallback event policy.
if (mFallbackEventHandler.dispatchKeyEvent(event)) {
return FINISH_HANDLED;
}
if (shouldDropInputEvent(q)) {
return FINISH_NOT_HANDLED;
}
// Handle automatic focus changes.
if (event.getAction() == KeyEvent.ACTION_DOWN) {
if (groupNavigationDirection != 0) {
if (performKeyboardGroupNavigation(groupNavigationDirection)) {
return FINISH_HANDLED;
}
} else {
if (performFocusNavigation(event)) {
return FINISH_HANDLED;
}
}
}
return FORWARD;
}
- 在處理按鍵事件之前钠龙,首先會(huì)調(diào)用mUnhandledKeyManager.preViewDispatch(event)來判斷是否有未處理的按鍵事件。如果有未處理的事件,則直接返回FINISH_HANDLED表示該事件已被處理碴里。
- 如果沒有未處理的事件沈矿,則調(diào)用mView.dispatchKeyEvent(event)將按鍵事件分發(fā)給View層次結(jié)構(gòu)。如果View層次結(jié)構(gòu)中的任何一個(gè)View處理了該事件咬腋,則直接返回FINISH_HANDLED表示該事件已被處理细睡。
- 如果按鍵事件不能被處理或應(yīng)該被丟棄,則調(diào)用shouldDropInputEvent(q)方法來決定是否應(yīng)該丟棄該事件帝火。如果應(yīng)該丟棄溜徙,則返回FINISH_NOT_HANDLED表示該事件未被處理;否則繼續(xù)處理該事件犀填。
- 如果事件仍未被處理蠢壹,則調(diào)用mUnhandledKeyManager.dispatch(mView, event)來判斷是否有未處理的事件。如果有九巡,則直接返回FINISH_HANDLED表示該事件已被處理图贸。
- 如果按鍵事件是一個(gè)特定的按鍵(如Tab鍵)并且同時(shí)滿足一些特定的條件,則設(shè)置groupNavigationDirection變量并調(diào)用performKeyboardGroupNavigation方法來處理自動(dòng)的聚焦變化冕广。
- 如果按鍵事件是一個(gè)快捷鍵(即同時(shí)按下一個(gè)或多個(gè)修飾鍵和另一個(gè)鍵)疏日,則調(diào)用mView.dispatchKeyShortcutEvent(event)將事件分發(fā)給View層次結(jié)構(gòu)來嘗試解釋該按鍵事件。如果該事件被處理撒汉,則返回FINISH_HANDLED表示該事件已被處理沟优。
- 如果事件仍未被處理,則調(diào)用mFallbackEventHandler.dispatchKeyEvent(event)應(yīng)用回退事件策略來嘗試處理該事件睬辐。如果該事件被處理挠阁,則返回FINISH_HANDLED表示該事件已被處理。
- 如果事件仍未被處理溯饵,則調(diào)用performFocusNavigation方法來處理自動(dòng)的聚焦變化侵俗。如果該事件被處理,則返回FINISH_HANDLED表示該事件已被處理丰刊。
- 最后隘谣,如果事件仍未被處理,則返回FORWARD表示該事件應(yīng)該被傳遞給下一個(gè)事件處理程序啄巧。
private boolean performFocusNavigation(KeyEvent event) {
int direction = 0;
switch (event.getKeyCode()) {
case KeyEvent.KEYCODE_DPAD_LEFT:
if (event.hasNoModifiers()) {
direction = View.FOCUS_LEFT;
}
break;
case KeyEvent.KEYCODE_DPAD_RIGHT:
if (event.hasNoModifiers()) {
direction = View.FOCUS_RIGHT;
}
break;
case KeyEvent.KEYCODE_DPAD_UP:
if (event.hasNoModifiers()) {
direction = View.FOCUS_UP;
}
break;
case KeyEvent.KEYCODE_DPAD_DOWN:
if (event.hasNoModifiers()) {
direction = View.FOCUS_DOWN;
}
break;
case KeyEvent.KEYCODE_TAB:
if (event.hasNoModifiers()) {
direction = View.FOCUS_FORWARD;
} else if (event.hasModifiers(KeyEvent.META_SHIFT_ON)) {
direction = View.FOCUS_BACKWARD;
}
break;
}
if (direction != 0) {
View focused = mView.findFocus();
if (focused != null) {
View v = focused.focusSearch(direction);
if (v != null && v != focused) {
// do the math the get the interesting rect
// of previous focused into the coord system of
// newly focused view
focused.getFocusedRect(mTempRect);
if (mView instanceof ViewGroup) {
((ViewGroup) mView).offsetDescendantRectToMyCoords(
focused, mTempRect);
((ViewGroup) mView).offsetRectIntoDescendantCoords(
v, mTempRect);
}
if (v.requestFocus(direction, mTempRect)) {
Log.i(TAG, "v.requestFocus == true");
boolean isFastScrolling = event.getRepeatCount() > 0;
playSoundEffect(
SoundEffectConstants.getConstantForFocusDirection(direction,
isFastScrolling));
return true;
}
}
// Give the focused view a last chance to handle the dpad key.
if (mView.dispatchUnhandledMove(focused, direction)) {
return true;
}
} else {
if (mView.restoreDefaultFocus()) {
return true;
}
}
}
return false;
}
該方法用于處理焦點(diǎn)導(dǎo)航相關(guān)的按鍵事件寻歧,例如方向鍵和Tab鍵等。該方法接收一個(gè)KeyEvent對(duì)象作為參數(shù)棵帽,根據(jù)不同的按鍵碼和修飾符熄求,計(jì)算出焦點(diǎn)導(dǎo)航的方向,然后嘗試在View樹中找到新的焦點(diǎn)逗概,并將其設(shè)置為當(dāng)前焦點(diǎn)弟晚。如果找到新的焦點(diǎn)并成功設(shè)置為當(dāng)前焦點(diǎn),則播放焦點(diǎn)變化時(shí)的聲音效果,并返回true表示焦點(diǎn)變化事件已被處理卿城。如果沒有找到新的焦點(diǎn)枚钓,或者新的焦點(diǎn)不接受焦點(diǎn)設(shè)置請求,則返回false表示該事件未被處理瑟押。
具體而言搀捷,該方法會(huì)首先根據(jù)按鍵碼和修飾符計(jì)算出焦點(diǎn)導(dǎo)航的方向,然后調(diào)用View的focusSearch方法來查找新的焦點(diǎn)多望。如果找到了新的焦點(diǎn)嫩舟,則計(jì)算出前一個(gè)焦點(diǎn)和新焦點(diǎn)之間的位置關(guān)系,并將此位置關(guān)系轉(zhuǎn)換為新焦點(diǎn)的坐標(biāo)系中的位置怀偷,然后將焦點(diǎn)設(shè)置為新的焦點(diǎn)家厌,并播放相應(yīng)的聲音效果。如果沒有找到新的焦點(diǎn)椎工,則嘗試將焦點(diǎn)設(shè)置為默認(rèn)的焦點(diǎn)饭于,并返回true表示焦點(diǎn)變化事件已被處理。如果前一個(gè)焦點(diǎn)無法處理該焦點(diǎn)導(dǎo)航事件维蒙,則返回false表示該事件未被處理掰吕。
/frameworks/base/core/java/com/android/internal/policy/DecorView.java
@Override
public boolean dispatchKeyEvent(KeyEvent event) {
final int keyCode = event.getKeyCode();
final int action = event.getAction();
final boolean isDown = action == KeyEvent.ACTION_DOWN;
if (isDown && (event.getRepeatCount() == 0)) {
// First handle chording of panel key: if a panel key is held
// but not released, try to execute a shortcut in it.
if ((mWindow.mPanelChordingKey > 0) && (mWindow.mPanelChordingKey != keyCode)) {
boolean handled = dispatchKeyShortcutEvent(event);
if (handled) {
return true;
}
}
// If a panel is open, perform a shortcut on it without the
// chorded panel key
if ((mWindow.mPreparedPanel != null) && mWindow.mPreparedPanel.isOpen) {
if (mWindow.performPanelShortcut(mWindow.mPreparedPanel, keyCode, event, 0)) {
return true;
}
}
}
if (!mWindow.isDestroyed()) {
final Window.Callback cb = mWindow.getCallback();
final boolean handled = cb != null && mFeatureId < 0 ? cb.dispatchKeyEvent(event)
: super.dispatchKeyEvent(event);
if (handled) {
return true;
}
}
boolean result = isDown ? mWindow.onKeyDown(mFeatureId, event.getKeyCode(), event)
: mWindow.onKeyUp(mFeatureId, event.getKeyCode(), event);
return result;
}
這是DecoreView類中的一個(gè)方法,用于處理按鍵事件的分發(fā)和處理颅痊。
該方法接收一個(gè)KeyEvent對(duì)象殖熟,并提取出鍵碼和事件類型。如果該事件為按下事件并且重復(fù)次數(shù)為0八千,則執(zhí)行以下操作:
首先吗讶,處理面板按鍵的彈奏:如果面板按鍵已按下但未釋放燎猛,則嘗試在其中執(zhí)行一個(gè)快捷方式恋捆。如果已處理該快捷方式,則返回true表示事件已處理重绷。
然后沸停,如果面板已經(jīng)打開,則在沒有面板按鍵的情況下執(zhí)行其上的快捷方式昭卓。如果已處理該快捷方式愤钾,則返回true表示事件已處理。
如果事件仍未被處理候醒,則檢查Window對(duì)象是否已被銷毀能颁,如果沒有,則獲取Window.Callback對(duì)象并將事件分派給它倒淫。如果已處理該事件伙菊,則返回true表示事件已處理。
最后,如果事件仍未被處理镜硕,則將其傳遞給Window對(duì)象進(jìn)行處理运翼,并返回該事件是否為按下事件的結(jié)果。
public boolean superDispatchKeyEvent(KeyEvent event) {
// Give priority to closing action modes if applicable.
if (event.getKeyCode() == KeyEvent.KEYCODE_BACK) {
final int action = event.getAction();
// Back cancels action modes first.
if (mPrimaryActionMode != null) {
if (action == KeyEvent.ACTION_UP) {
mPrimaryActionMode.finish();
}
return true;
}
}
if (super.dispatchKeyEvent(event)) {
return true;
}
boolean handle = (getViewRootImpl() != null) && getViewRootImpl().dispatchUnhandledKeyEvent(event);
return handle;
}
其作用是對(duì) KeyEvent 事件進(jìn)行分發(fā)處理兴枯。該方法首先判斷事件是否為返回鍵血淌,如果是則優(yōu)先處理當(dāng)前的操作模式,如果存在操作模式則先結(jié)束操作模式财剖,否則將事件交由父類ViewGroup進(jìn)行處理悠夯。如果父類能夠處理該事件,則返回 true躺坟,否則返回 false疗疟,并將事件交由該 DecorView 所對(duì)應(yīng)的 ViewRootImpl 實(shí)例進(jìn)行處理。
其中瞳氓,getViewRootImpl() 方法返回當(dāng)前 DecorView 所在的 ViewRootImpl 實(shí)例策彤。如果該實(shí)例存在,則調(diào)用 dispatchUnhandledKeyEvent(event) 方法進(jìn)行處理匣摘,否則返回 false店诗。dispatchUnhandledKeyEvent(event) 方法用于將該事件交由輸入法進(jìn)行處理
/frameworks/base/core/java/android/app/Activity.java
public boolean dispatchKeyEvent(KeyEvent event) {
onUserInteraction();
// Let action bars open menus in response to the menu key prioritized over
// the window handling it
final int keyCode = event.getKeyCode();
if (keyCode == KeyEvent.KEYCODE_MENU &&
mActionBar != null && mActionBar.onMenuKeyEvent(event)) {
return true;
}
Window win = getWindow();
if (win.superDispatchKeyEvent(event)) {
return true;
}
View decor = mDecor;
if (decor == null) decor = win.getDecorView();
boolean handler = event.dispatch(this, decor != null
? decor.getKeyDispatcherState() : null, this);
return handler;
}
該方法用于分發(fā)鍵事件,當(dāng)用戶按下或釋放某個(gè)按鍵時(shí)音榜,該方法將被調(diào)用庞瘸。首先,方法調(diào)用 onUserInteraction()赠叼,用于通知 Activity 用戶正在與應(yīng)用程序交互擦囊。
接著,方法檢查事件是否為菜單鍵事件嘴办,如果是瞬场,則首先檢查 ActionBar 是否存在,并且將事件傳遞給 ActionBar 的 onMenuKeyEvent() 方法進(jìn)行處理涧郊。如果 ActionBar 成功處理該事件贯被,則直接返回 true,表示事件已被處理妆艘。
如果事件不是菜單鍵事件或者 ActionBar 無法處理該事件彤灶,則將事件傳遞給窗口處理,并返回窗口處理結(jié)果批旺。如果窗口處理了該事件幌陕,則直接返回 true,表示事件已被處理汽煮。
如果事件既不是菜單鍵事件厅须,也無法被窗口處理,則將事件分發(fā)給 DecorView(該 Activity 的根 View)叹誉,并返回 DecorView 的 dispatchKeyEvent() 方法的處理結(jié)果召嘶。
/frameworks/base/core/java/com/android/internal/policy/PhoneWindow.java
public boolean superDispatchKeyEvent(KeyEvent event) {
return mDecor.superDispatchKeyEvent(event);
}
/frameworks/base/core/java/android/view/ViewGroup.java
@Override
public boolean dispatchKeyEvent(KeyEvent event) {
if (mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onKeyEvent(event, 1);
}
if ((mPrivateFlags & (PFLAG_FOCUSED | PFLAG_HAS_BOUNDS))
== (PFLAG_FOCUSED | PFLAG_HAS_BOUNDS)) {
if (super.dispatchKeyEvent(event)) {
Log.e("ViewRootImpl","super.dispatchKeyEvent(event)");
return true;
}
} else if (mFocused != null && (mFocused.mPrivateFlags & PFLAG_HAS_BOUNDS)
== PFLAG_HAS_BOUNDS) {
if (mFocused.dispatchKeyEvent(event)) {
Log.e("ViewRootImpl","Focused.dispatchKeyEvent(event)");
return true;
}
}
if (mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onUnhandledEvent(event, 1);
}
return false;
}
用于將按鍵事件分派到該ViewGroup及其子View。該方法首先通過檢查ViewGroup本身的狀態(tài)(是否擁有焦點(diǎn)且有邊界)來決定是否自己處理KeyEvent,如果ViewGroup本身滿足條件,則通過調(diào)用父類的dispatchKeyEvent方法處理事件,并返回true表示已處理摆寄。如果ViewGroup本身不滿足條件,則將KeyEvent分派到當(dāng)前擁有焦點(diǎn)的子View坯门,如果該子View處理了事件微饥,則返回true表示已處理。如果KeyEvent最終未被處理古戴,則返回false表示未處理欠橘。此方法還包含一些調(diào)試代碼,用于確保事件派發(fā)的一致性现恼。
/frameworks/base/core/java/android/view/View.java
public boolean dispatchKeyEvent(KeyEvent event) {
if (mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onKeyEvent(event, 0);
}
// Give any attached key listener a first crack at the event.
//noinspection SimplifiableIfStatement
ListenerInfo li = mListenerInfo;
if (li != null && li.mOnKeyListener != null && (mViewFlags & ENABLED_MASK) == ENABLED
&& li.mOnKeyListener.onKey(this, event.getKeyCode(), event)) {
Log.e("ViewRootImpl","mOnKeyListener.onKey");
return true;
}
if (event.dispatch(this, mAttachInfo != null
? mAttachInfo.mKeyDispatchState : null, this)) {
Log.e("ViewRootImpl","event.dispatch");
return true;
}
if (mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onUnhandledEvent(event, 0);
}
return false;
}
該方法用于分派鍵盤事件到對(duì)應(yīng)的視圖肃续,并根據(jù)事件的處理結(jié)果返回一個(gè)布爾值。
方法的參數(shù) KeyEvent event 表示一個(gè)鍵盤事件叉袍,該事件將被分派到相應(yīng)的視圖始锚。方法中的第一步是調(diào)用 mInputEventConsistencyVerifier.onKeyEvent(event, 0) 方法來記錄鍵盤事件的一些基本信息,用于后續(xù)的事件一致性檢查喳逛。然后會(huì)首先調(diào)用視圖的 OnKeyListener 對(duì)象(如果有的話)的 onKey 方法瞧捌,如果該方法返回 true,則表示該鍵盤事件被該監(jiān)聽器處理润文,方法返回 true姐呐,否則,該鍵盤事件被傳遞給了該視圖的 dispatch 方法典蝌。在這里調(diào)用了 event.dispatch(this, mAttachInfo != null ? mAttachInfo.mKeyDispatchState : null, this) 方法曙砂,該方法會(huì)根據(jù)鍵盤事件的類型,將其分發(fā)給視圖的 onKeyDown 或 onKeyUp 方法進(jìn)行處理赠法。如果該事件被處理了麦轰,則返回 true,否則砖织,會(huì)調(diào)用 mInputEventConsistencyVerifier.onUnhandledEvent(event, 0) 方法記錄該事件未被處理的信息,并返回 false 表示該事件未被處理末荐。
/frameworks/base/core/java/android/view/KeyEvent.java
public final boolean dispatch(Callback receiver, DispatcherState state,
Object target) {
switch (mAction) {
case ACTION_DOWN: {
mFlags &= ~FLAG_START_TRACKING;
if (DEBUG) Log.v(TAG, "Key down to " + target + " in " + state
+ ": " + this);
boolean res = receiver.onKeyDown(mKeyCode, this);
if (state != null) {
if (res && mRepeatCount == 0 && (mFlags&FLAG_START_TRACKING) != 0) {
if (DEBUG) Log.v(TAG, " Start tracking!");
state.startTracking(this, target);
} else if (isLongPress() && state.isTracking(this)) {
try {
if (receiver.onKeyLongPress(mKeyCode, this)) {
if (DEBUG) Log.v(TAG, " Clear from long press!");
state.performedLongPress(this);
res = true;
}
} catch (AbstractMethodError e) {
}
}
}
return res;
}
case ACTION_UP:
if (DEBUG) Log.v(TAG, "Key up to " + target + " in " + state
+ ": " + this);
if (state != null) {
state.handleUpEvent(this);
}
return receiver.onKeyUp(mKeyCode, this);
case ACTION_MULTIPLE:
final int count = mRepeatCount;
final int code = mKeyCode;
if (receiver.onKeyMultiple(code, count, this)) {
return true;
}
if (code != KeyEvent.KEYCODE_UNKNOWN) {
mAction = ACTION_DOWN;
mRepeatCount = 0;
boolean handled = receiver.onKeyDown(code, this);
if (handled) {
mAction = ACTION_UP;
receiver.onKeyUp(code, this);
}
mAction = ACTION_MULTIPLE;
mRepeatCount = count;
return handled;
}
return false;
}
return false;
}
這段代碼是KeyEvent事件分發(fā)的核心代碼侧纯,主要處理按鍵事件的分發(fā)。當(dāng)一個(gè)按鍵事件被分發(fā)到一個(gè)View時(shí)甲脏,該View首先嘗試處理該事件眶熬。如果該View無法處理該事件妹笆,則事件將被分發(fā)給它的parent View或Activity,直到事件被處理或到達(dá)了View層級(jí)的最頂層娜氏。
具體來說拳缠,該方法根據(jù)KeyEvent的不同Action,分別進(jìn)行處理贸弥。如果Action是ACTION_DOWN窟坐,即按下按鍵的事件,首先會(huì)調(diào)用Callback接口的onKeyDown方法來處理該事件绵疲,并根據(jù)事件處理的結(jié)果進(jìn)行相應(yīng)的處理哲鸳。如果返回true,表示事件被成功處理盔憨,并且設(shè)置了FLAG_START_TRACKING標(biāo)志位徙菠,則會(huì)調(diào)用DispatcherState的startTracking方法來開始追蹤該事件。如果事件是長按事件郁岩,且當(dāng)前正在追蹤該事件婿奔,則會(huì)調(diào)用Callback接口的onKeyLongPress方法來處理長按事件。
如果Action是ACTION_UP问慎,即松開按鍵的事件脸秽,會(huì)調(diào)用Callback接口的onKeyUp方法來處理該事件,并根據(jù)DispatcherState是否存在蝴乔,調(diào)用DispatcherState的handleUpEvent方法來結(jié)束追蹤該事件记餐。
如果Action是ACTION_MULTIPLE,即按鍵事件包含多個(gè)重復(fù)事件薇正,會(huì)調(diào)用Callback接口的onKeyMultiple方法來處理該事件片酝,并根據(jù)KeyEvent的repeatCount和keyCode信息,依次調(diào)用Callback接口的onKeyDown和onKeyUp方法來處理每一個(gè)重復(fù)事件挖腰,最后返回處理結(jié)果雕沿。
總的來說,KeyEvent類的dispatch方法實(shí)現(xiàn)了按鍵事件的分發(fā)和處理猴仑,為Android應(yīng)用程序提供了豐富的按鍵事件處理能力审轮。
2. AudioManager.playSoundEffect源碼分析
framework/base/media/java/android/media/AudioManager.java
public void playSoundEffect(@SystemSoundEffect int effectType, int userId) {
if (effectType < 0 || effectType >= NUM_SOUND_EFFECTS) {
return;
}
if (!querySoundEffectsEnabled(userId)) {
return;
}
final IAudioService service = getService();
try {
service.playSoundEffect(effectType);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
framework/base/services/core/java/com/android/server/audio/AudioService.java
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;
}
sendMsg(mAudioHandler, MSG_PLAY_SOUND_EFFECT, SENDMSG_QUEUE,
effectType, (int) (volume * 1000), null, 0);
}
case MSG_PLAY_SOUND_EFFECT:
mSfxHelper.playSoundEffect(msg.arg1, msg.arg2);
break;
framework/base/services/core/java/com/android/server/audio/SoundEffectsHelper.java
private void onLoadSoundEffects(OnEffectsLoadCompleteHandler onComplete) {
if (mSoundPoolLoader != null) {
// 如果已經(jīng)有一個(gè)SoundPoolLoader在加載聲音,則將新的OnEffectsLoadCompleteHandler添加到該SoundPoolLoader中辽俗,
// 并返回
mSoundPoolLoader.addHandler(onComplete);
return;
}
if (mSoundPool != null) {
// 如果SoundPool已經(jīng)被初始化疾渣,則直接運(yùn)行onComplete回調(diào)函數(shù)并返回
if (onComplete != null) {
onComplete.run(true /*success*/);
}
return;
}
logEvent("effects loading started");
// 創(chuàng)建一個(gè)新的SoundPool實(shí)例
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();
// 創(chuàng)建一個(gè)新的SoundPoolLoader實(shí)例,并將onComplete添加到其handler列表中
mSoundPoolLoader = new SoundPoolLoader();
mSoundPoolLoader.addHandler(new OnEffectsLoadCompleteHandler() {
@Override
public void run(boolean success) {
// 當(dāng)SoundPoolLoader完成加載聲音時(shí)崖飘,將其置為null榴捡,并在加載失敗時(shí)卸載所有聲音
mSoundPoolLoader = null;
if (!success) {
Log.w(TAG, "onLoadSoundEffects(), Error while loading samples");
onUnloadSoundEffects();
}
}
});
mSoundPoolLoader.addHandler(onComplete);
int resourcesToLoad = 0;
for (Resource res : mResources) {
// 對(duì)于每個(gè)Resource對(duì)象,獲取其文件路徑并使用SoundPool加載聲音文件
String filePath = getResourceFilePath(res);
int sampleId = mSoundPool.load(filePath, 0);
if (sampleId > 0) {
// 如果成功加載朱浴,則將資源標(biāo)記為“未加載”
res.mSampleId = sampleId;
res.mLoaded = false;
resourcesToLoad++;
} else {
// 如果加載失敗吊圾,則記錄日志
logEvent("effect " + filePath + " rejected by SoundPool");
Log.w(TAG, "SoundPool could not load file: " + filePath);
}
}
if (resourcesToLoad > 0) {
// 如果有資源需要加載达椰,則設(shè)置超時(shí)定時(shí)器
sendMsg(MSG_LOAD_EFFECTS_TIMEOUT, 0, 0, null, SOUND_EFFECTS_LOAD_TIMEOUT_MS);
} else {
// 如果沒有需要加載的資源,則加載完成
logEvent("effects loading completed, no effects to load");
mSoundPoolLoader.onComplete(true /*success*/);
}
}
/*package*/ void playSoundEffect(int effect, int volume) {
sendMsg(MSG_PLAY_EFFECT, effect, volume, null, 0);
}
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;
void onPlaySoundEffect(int effect, int volume) {
float volFloat;
// use default if volume is not specified by caller
if (volume < 0) {
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) {
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);
}
}
}
函數(shù) onPlaySoundEffect 接收兩個(gè)參數(shù):effect 表示要播放的聲音效果的標(biāo)識(shí)项乒,volume 表示要播放的音量啰劲。如果 volume 參數(shù)小于 0,就使用默認(rèn)音量檀何,否則使用指定的音量蝇裤。函數(shù)內(nèi)部首先根據(jù) effect 參數(shù)獲取對(duì)應(yīng)的資源,然后檢查當(dāng)前是否存在可用的 SoundPool 對(duì)象并且該資源已經(jīng)加載成功埃碱。如果滿足這些條件猖辫,就使用 SoundPool 對(duì)象播放聲音效果;否則砚殿,創(chuàng)建一個(gè)新的 MediaPlayer 對(duì)象啃憎,將聲音效果的資源設(shè)置給它,設(shè)置音量似炎、設(shè)置播放完成和錯(cuò)誤監(jiān)聽器辛萍,最后播放聲音。
整個(gè)函數(shù)的核心是根據(jù)參數(shù)播放聲音效果羡藐,如果能使用 SoundPool贩毕,就使用它進(jìn)行播放,否則使用 MediaPlayer 進(jìn)行播放仆嗦。播放完成和錯(cuò)誤監(jiān)聽器的設(shè)置可以保證在播放完成或者出現(xiàn)錯(cuò)誤時(shí)能及時(shí)釋放資源辉阶,避免資源的泄露。