音效流程分析
音效事件起源
Android 平台所有view类型控件的touch,遥控器按键等事件在系统音效开启时,都可以触发按键音。
音效事件流程分析
从touch事件为例,整个流程如下图所示:
如图中所示,在touch事件触发后,onTouchEvent会被调用,接着performClick就会处理Click事件,
- /**
- * Call this view's OnClickListener, if it is defined. Performs all normal
- * actions associated with clicking: reporting accessibility event, playing
- * a sound, etc.
- *
- * @return True there was an assigned OnClickListener that was called, false
- * otherwise is returned.
- */
- public boolean performClick() {
- sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);
- ListenerInfo li = mListenerInfo;
- if (li != null && li.mOnClickListener != null) {
- playSoundEffect(SoundEffectConstants.CLICK);
- li.mOnClickListener.onClick(this);
- return true;
- }
- return false;
- }
- /**
- * Play a sound effect for this view.
- *
- * <p>The framework will play sound effects for some built in actions, such as
- * clicking, but you may wish to play these effects in your widget,
- * for instance, for internal navigation.
- *
- * <p>The sound effect will only be played if sound effects are enabled by the user, and
- * {@link #isSoundEffectsEnabled()} is true.
- *
- * @param soundConstant One of the constants defined in {@link SoundEffectConstants}
- */
- public void playSoundEffect(int soundConstant) {
- if (mAttachInfo == null || mAttachInfo.mRootCallbacks == null || !isSoundEffectsEnabled()) {
- return;
- }
- mAttachInfo.mRootCallbacks.playSoundEffect(soundConstant);
- }
在创建后,都会绑定到对应窗口Window,下面看一下函数dispatchAttachedToWindow
- void dispatchAttachedToWindow(AttachInfo info, int visibility) {
- //System.out.println("Attached! " + this);
- mAttachInfo = info;
- if (mOverlay != null) {
- mOverlay.getOverlayView().dispatchAttachedToWindow(info, visibility);
- }
- mWindowAttachCount++;
- // We will need to evaluate the drawable state at least once.
- mPrivateFlags |= PFLAG_DRAWABLE_STATE_DIRTY;
- if (mFloatingTreeObserver != null) {
- info.mTreeObserver.merge(mFloatingTreeObserver);
- mFloatingTreeObserver = null;
- }
- if ((mPrivateFlags&PFLAG_SCROLL_CONTAINER) != 0) {
- mAttachInfo.mScrollContainers.add(this);
- mPrivateFlags |= PFLAG_SCROLL_CONTAINER_ADDED;
- }
- performCollectViewAttributes(mAttachInfo, visibility);
- onAttachedToWindow();
- ListenerInfo li = mListenerInfo;
- final CopyOnWriteArrayList<OnAttachStateChangeListener> listeners =
- li != null ? li.mOnAttachStateChangeListeners : null;
- if (listeners != null && listeners.size() > 0) {
- // NOTE: because of the use of CopyOnWriteArrayList, we *must* use an iterator to
- // perform the dispatching. The iterator is a safe guard against listeners that
- // could mutate the list by calling the various add/remove methods. This prevents
- // the array from being modified while we iterate it.
- for (OnAttachStateChangeListener listener : listeners) {
- listener.onViewAttachedToWindow(this);
- }
- }
- int vis = info.mWindowVisibility;
- if (vis != GONE) {
- onWindowVisibilityChanged(vis);
- }
- if ((mPrivateFlags&PFLAG_DRAWABLE_STATE_DIRTY) != 0) {
- // If nobody has evaluated the drawable state yet, then do it now.
- refreshDrawableState();
- }
- needGlobalAttributesUpdate(false);
- }
将ViewRootImpl.mAttachInfo设置下去,而此成员在ViewRootImpl构造函数中赋值。
- public ViewRootImpl(Context context, Display display) {
- mContext = context;
- mWindowSession = WindowManagerGlobal.getWindowSession();
- mDisplay = display;
- mBasePackageName = context.getBasePackageName();
- mDisplayAdjustments = display.getDisplayAdjustments();
- mThread = Thread.currentThread();
- mLocation = new WindowLeaked(null);
- mLocation.fillInStackTrace();
- mWidth = -1;
- mHeight = -1;
- mDirty = new Rect();
- mTempRect = new Rect();
- mVisRect = new Rect();
- mWinFrame = new Rect();
- mWindow = new W(this);
- mTargetSdkVersion = context.getApplicationInfo().targetSdkVersion;
- mViewVisibility = View.GONE;
- mTransparentRegion = new Region();
- mPreviousTransparentRegion = new Region();
- mFirst = true; // true for the first time the view is added
- mAdded = false;
- mAccessibilityManager = AccessibilityManager.getInstance(context);
- mAccessibilityInteractionConnectionManager =
- new AccessibilityInteractionConnectionManager();
- mAccessibilityManager.addAccessibilityStateChangeListener(
- mAccessibilityInteractionConnectionManager);
- mAttachInfo = new View.AttachInfo(mWindowSession, mWindow, display, this, mHandler, this);
- mViewConfiguration = ViewConfiguration.get(context);
- mDensity = context.getResources().getDisplayMetrics().densityDpi;
- mNoncompatDensity = context.getResources().getDisplayMetrics().noncompatDensityDpi;
- mFallbackEventHandler = PolicyManager.makeNewFallbackEventHandler(context);
- mChoreographer = Choreographer.getInstance();
- PowerManager powerManager = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
- mAttachInfo.mScreenOn = powerManager.isScreenOn();
- loadSystemProperties();
- }
再看AttachInfo构造函数,如下:
- /**
- * Creates a new set of attachment information with the specified
- * events handler and thread.
- *
- * @param handler the events handler the view must use
- */
- AttachInfo(IWindowSession session, IWindow window, Display display,
- ViewRootImpl viewRootImpl, Handler handler, Callbacks effectPlayer) {
- mSession = session;
- mWindow = window;
- mWindowToken = window.asBinder();
- mDisplay = display;
- mViewRootImpl = viewRootImpl;
- mHandler = handler;
- mRootCallbacks = effectPlayer;
- }
音频系统接管音效控制
从ViewRootImp的函数playSoundEffect
- public void playSoundEffect(int effectId) {
- checkThread();
- if (mMediaDisabled) {
- return;
- }
- 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(TAG, "FATAL EXCEPTION when attempting to play sound effect: " + e);
- e.printStackTrace();
- }
- }
- private void onPlaySoundEffect(int effectType, int volume) {
- synchronized (mSoundEffectsLock) {
- 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 = (float) volume / 1000.0f;
- }
- if (SOUND_EFFECT_FILES_MAP[effectType][1] > 0) {
- 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);
- }
- }
- }
- }
sSoundEffectVolumeDb 是音效使用的音量大小值,mSoundPool.play完成音效的播放功能。
音频系统音效全局控制
- /**
- * Plays a sound effect (Key clicks, lid open/close...)
- * @param effectType The type of sound effect. One of
- * {@link #FX_KEY_CLICK},
- * {@link #FX_FOCUS_NAVIGATION_UP},
- * {@link #FX_FOCUS_NAVIGATION_DOWN},
- * {@link #FX_FOCUS_NAVIGATION_LEFT},
- * {@link #FX_FOCUS_NAVIGATION_RIGHT},
- * {@link #FX_KEYPRESS_STANDARD},
- * {@link #FX_KEYPRESS_SPACEBAR},
- * {@link #FX_KEYPRESS_DELETE},
- * {@link #FX_KEYPRESS_RETURN},
- * {@link #FX_KEYPRESS_INVALID},
- * NOTE: This version uses the UI settings to determine
- * whether sounds are heard or not.
- */
- public void playSoundEffect(int effectType) {
- if (effectType < 0 || effectType >= NUM_SOUND_EFFECTS) {
- return;
- }
- if (!querySoundEffectsEnabled()) {
- return;
- }
- //Ctv Patch Begin
- effectTypeForHandler = effectType;
- userHandler.removeCallbacks(queueRun);
- userHandler.postDelayed(queueRun, 20);
- /*IAudioService service = getService();
- try {
- service.playSoundEffect(effectType);
- } catch (RemoteException e) {
- Log.e(TAG, "Dead object in playSoundEffect"+e);
- }*/
- }
例如:Settings.System.putInt(mContext.getContentResolver(), Settings.System.SOUND_EFFECTS_ENABLED, 0/1) 关闭/开启