Android 音效流程分析

来源:互联网 发布:信贷审批业务流程优化 编辑:程序博客网 时间:2024/06/05 03:25

[+]

音效流程分析

音效事件起源

Android 平台所有view类型控件的touch,遥控器按键等事件在系统音效开启时,都可以触发按键音。

音效事件流程分析

从touch事件为例,整个流程如下图所示:



 如图中所示,在touch事件触发后,onTouchEvent会被调用,接着performClick就会处理Click事件,
[java] view plain copy
  1. /** 
  2.  * Call this view's OnClickListener, if it is defined.  Performs all normal 
  3.  * actions associated with clicking: reporting accessibility event, playing 
  4.  * a sound, etc. 
  5.  * 
  6.  * @return True there was an assigned OnClickListener that was called, false 
  7.  *         otherwise is returned. 
  8.  */  
  9. public boolean performClick() {  
  10.     sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);  
  11.   
  12.     ListenerInfo li = mListenerInfo;  
  13.     if (li != null && li.mOnClickListener != null) {  
  14.         playSoundEffect(SoundEffectConstants.CLICK);  
  15.         li.mOnClickListener.onClick(this);  
  16.         return true;  
  17.     }  
  18.   
  19.     return false;  
  20. }  
在performClick里面主要做了两件事,调用监听器的onClick回调和playSoundEffect,监听器的回调是在setOnClickListener中注册下来,下面主要分析playSoundEffect
[java] view plain copy
  1. /** 
  2.  * Play a sound effect for this view. 
  3.  * 
  4.  * <p>The framework will play sound effects for some built in actions, such as 
  5.  * clicking, but you may wish to play these effects in your widget, 
  6.  * for instance, for internal navigation. 
  7.  * 
  8.  * <p>The sound effect will only be played if sound effects are enabled by the user, and 
  9.  * {@link #isSoundEffectsEnabled()} is true. 
  10.  * 
  11.  * @param soundConstant One of the constants defined in {@link SoundEffectConstants} 
  12.  */  
  13. public void playSoundEffect(int soundConstant) {  
  14.     if (mAttachInfo == null || mAttachInfo.mRootCallbacks == null || !isSoundEffectsEnabled()) {  
  15.         return;  
  16.     }  
  17.     mAttachInfo.mRootCallbacks.playSoundEffect(soundConstant);  
  18. }  
利用成员变量mAttachInfo的mRootCallbacks回调调用另外一个音效处理函数playSoundEffect,这里找到mAttachInfo和mRootCallbacks的源头就很重要,其实每个View控件
在创建后,都会绑定到对应窗口Window,下面看一下函数dispatchAttachedToWindow
[java] view plain copy
  1. void dispatchAttachedToWindow(AttachInfo info, int visibility) {  
  2.     //System.out.println("Attached! " + this);  
  3.     mAttachInfo = info;  
  4.     if (mOverlay != null) {  
  5.         mOverlay.getOverlayView().dispatchAttachedToWindow(info, visibility);  
  6.     }  
  7.     mWindowAttachCount++;  
  8.     // We will need to evaluate the drawable state at least once.  
  9.     mPrivateFlags |= PFLAG_DRAWABLE_STATE_DIRTY;  
  10.     if (mFloatingTreeObserver != null) {  
  11.         info.mTreeObserver.merge(mFloatingTreeObserver);  
  12.         mFloatingTreeObserver = null;  
  13.     }  
  14.     if ((mPrivateFlags&PFLAG_SCROLL_CONTAINER) != 0) {  
  15.         mAttachInfo.mScrollContainers.add(this);  
  16.         mPrivateFlags |= PFLAG_SCROLL_CONTAINER_ADDED;  
  17.     }  
  18.     performCollectViewAttributes(mAttachInfo, visibility);  
  19.     onAttachedToWindow();  
  20.   
  21.     ListenerInfo li = mListenerInfo;  
  22.     final CopyOnWriteArrayList<OnAttachStateChangeListener> listeners =  
  23.             li != null ? li.mOnAttachStateChangeListeners : null;  
  24.     if (listeners != null && listeners.size() > 0) {  
  25.         // NOTE: because of the use of CopyOnWriteArrayList, we *must* use an iterator to  
  26.         // perform the dispatching. The iterator is a safe guard against listeners that  
  27.         // could mutate the list by calling the various add/remove methods. This prevents  
  28.         // the array from being modified while we iterate it.  
  29.         for (OnAttachStateChangeListener listener : listeners) {  
  30.             listener.onViewAttachedToWindow(this);  
  31.         }  
  32.     }  
  33.   
  34.     int vis = info.mWindowVisibility;  
  35.     if (vis != GONE) {  
  36.         onWindowVisibilityChanged(vis);  
  37.     }  
  38.     if ((mPrivateFlags&PFLAG_DRAWABLE_STATE_DIRTY) != 0) {  
  39.         // If nobody has evaluated the drawable state yet, then do it now.  
  40.         refreshDrawableState();  
  41.     }  
  42.     needGlobalAttributesUpdate(false);  
  43. }  
函数开始处有mAttachInfo = info,就给mAttachInfo赋值了,继续向上索引,ViewRootImpl.performTraversals中调用host.dispatchAttachedToWindow(attachInfo, 0);
将ViewRootImpl.mAttachInfo设置下去,而此成员在ViewRootImpl构造函数中赋值。
[java] view plain copy
  1. public ViewRootImpl(Context context, Display display) {  
  2.     mContext = context;  
  3.     mWindowSession = WindowManagerGlobal.getWindowSession();  
  4.     mDisplay = display;  
  5.     mBasePackageName = context.getBasePackageName();  
  6.   
  7.     mDisplayAdjustments = display.getDisplayAdjustments();  
  8.   
  9.     mThread = Thread.currentThread();  
  10.     mLocation = new WindowLeaked(null);  
  11.     mLocation.fillInStackTrace();  
  12.     mWidth = -1;  
  13.     mHeight = -1;  
  14.     mDirty = new Rect();  
  15.     mTempRect = new Rect();  
  16.     mVisRect = new Rect();  
  17.     mWinFrame = new Rect();  
  18.     mWindow = new W(this);  
  19.     mTargetSdkVersion = context.getApplicationInfo().targetSdkVersion;  
  20.     mViewVisibility = View.GONE;  
  21.     mTransparentRegion = new Region();  
  22.     mPreviousTransparentRegion = new Region();  
  23.     mFirst = true// true for the first time the view is added  
  24.     mAdded = false;  
  25.     mAccessibilityManager = AccessibilityManager.getInstance(context);  
  26.     mAccessibilityInteractionConnectionManager =  
  27.         new AccessibilityInteractionConnectionManager();  
  28.     mAccessibilityManager.addAccessibilityStateChangeListener(  
  29.             mAccessibilityInteractionConnectionManager);  
  30.     mAttachInfo = new View.AttachInfo(mWindowSession, mWindow, display, this, mHandler, this);  
  31.     mViewConfiguration = ViewConfiguration.get(context);  
  32.     mDensity = context.getResources().getDisplayMetrics().densityDpi;  
  33.     mNoncompatDensity = context.getResources().getDisplayMetrics().noncompatDensityDpi;  
  34.     mFallbackEventHandler = PolicyManager.makeNewFallbackEventHandler(context);  
  35.     mChoreographer = Choreographer.getInstance();  
  36.   
  37.     PowerManager powerManager = (PowerManager) context.getSystemService(Context.POWER_SERVICE);  
  38.     mAttachInfo.mScreenOn = powerManager.isScreenOn();  
  39.     loadSystemProperties();  
  40. }  
mAttachInfo = new View.AttachInfo(mWindowSession, mWindow, display, this, mHandler, this); 
再看AttachInfo构造函数,如下:
[java] view plain copy
  1. /** 
  2.  * Creates a new set of attachment information with the specified 
  3.  * events handler and thread. 
  4.  * 
  5.  * @param handler the events handler the view must use 
  6.  */  
  7. AttachInfo(IWindowSession session, IWindow window, Display display,  
  8.         ViewRootImpl viewRootImpl, Handler handler, Callbacks effectPlayer) {  
  9.     mSession = session;  
  10.     mWindow = window;  
  11.     mWindowToken = window.asBinder();  
  12.     mDisplay = display;  
  13.     mViewRootImpl = viewRootImpl;  
  14.     mHandler = handler;  
  15.     mRootCallbacks = effectPlayer;  
  16. }  
mRootCallbacks 其实就是ViewRootImp的实例,这样View.playSoundEffect就进入了ViewRootImp.playSoundEffect.

音频系统接管音效控制

从ViewRootImp的函数playSoundEffect
[java] view plain copy
  1. public void playSoundEffect(int effectId) {  
  2.     checkThread();  
  3.   
  4.     if (mMediaDisabled) {  
  5.         return;  
  6.     }  
  7.   
  8.     try {  
  9.         final AudioManager audioManager = getAudioManager();  
  10.   
  11.         switch (effectId) {  
  12.             case SoundEffectConstants.CLICK:  
  13.                 audioManager.playSoundEffect(AudioManager.FX_KEY_CLICK);  
  14.                 return;  
  15.             case SoundEffectConstants.NAVIGATION_DOWN:  
  16.                 audioManager.playSoundEffect(AudioManager.FX_FOCUS_NAVIGATION_DOWN);  
  17.                 return;  
  18.             case SoundEffectConstants.NAVIGATION_LEFT:  
  19.                 audioManager.playSoundEffect(AudioManager.FX_FOCUS_NAVIGATION_LEFT);  
  20.                 return;  
  21.             case SoundEffectConstants.NAVIGATION_RIGHT:  
  22.                 audioManager.playSoundEffect(AudioManager.FX_FOCUS_NAVIGATION_RIGHT);  
  23.                 return;  
  24.             case SoundEffectConstants.NAVIGATION_UP:  
  25.                 audioManager.playSoundEffect(AudioManager.FX_FOCUS_NAVIGATION_UP);  
  26.                 return;  
  27.             default:  
  28.                 throw new IllegalArgumentException("unknown effect id " + effectId +  
  29.                         " not defined in " + SoundEffectConstants.class.getCanonicalName());  
  30.         }  
  31.     } catch (IllegalStateException e) {  
  32.         // Exception thrown by getAudioManager() when mView is null  
  33.         Log.e(TAG, "FATAL EXCEPTION when attempting to play sound effect: " + e);  
  34.         e.printStackTrace();  
  35.     }  
  36. }  
AudioManager接管音效控制权,接着AudioService的onPlaySoundEffect会被调用
[java] view plain copy
  1. private void onPlaySoundEffect(int effectType, int volume) {  
  2.     synchronized (mSoundEffectsLock) {  
  3.   
  4.         onLoadSoundEffects();  
  5.   
  6.         if (mSoundPool == null) {  
  7.             return;  
  8.         }  
  9.         float volFloat;  
  10.         // use default if volume is not specified by caller  
  11.         if (volume < 0) {  
  12.             volFloat = (float)Math.pow(10, (float)sSoundEffectVolumeDb/20);  
  13.         } else {  
  14.             volFloat = (float) volume / 1000.0f;  
  15.         }  
  16.   
  17.         if (SOUND_EFFECT_FILES_MAP[effectType][1] > 0) {  
  18.             mSoundPool.play(SOUND_EFFECT_FILES_MAP[effectType][1],  
  19.                                 volFloat, volFloat, 001.0f);  
  20.         } else {  
  21.             MediaPlayer mediaPlayer = new MediaPlayer();  
  22.             try {  
  23.                 String filePath = Environment.getRootDirectory() + SOUND_EFFECTS_PATH +  
  24.                             SOUND_EFFECT_FILES.get(SOUND_EFFECT_FILES_MAP[effectType][0]);  
  25.                 mediaPlayer.setDataSource(filePath);  
  26.                 mediaPlayer.setAudioStreamType(AudioSystem.STREAM_SYSTEM);  
  27.                 mediaPlayer.prepare();  
  28.                 mediaPlayer.setVolume(volFloat);  
  29.                 mediaPlayer.setOnCompletionListener(new OnCompletionListener() {  
  30.                     public void onCompletion(MediaPlayer mp) {  
  31.                         cleanupPlayer(mp);  
  32.                     }  
  33.                 });  
  34.                 mediaPlayer.setOnErrorListener(new OnErrorListener() {  
  35.                     public boolean onError(MediaPlayer mp, int what, int extra) {  
  36.                         cleanupPlayer(mp);  
  37.                         return true;  
  38.                     }  
  39.                 });  
  40.                 mediaPlayer.start();  
  41.             } catch (IOException ex) {  
  42.                 Log.w(TAG, "MediaPlayer IOException: "+ex);  
  43.             } catch (IllegalArgumentException ex) {  
  44.                 Log.w(TAG, "MediaPlayer IllegalArgumentException: "+ex);  
  45.             } catch (IllegalStateException ex) {  
  46.                 Log.w(TAG, "MediaPlayer IllegalStateException: "+ex);  
  47.             }  
  48.         }  
  49.     }  
  50. }  

sSoundEffectVolumeDb 是音效使用的音量大小值,mSoundPool.play完成音效的播放功能。


音频系统音效全局控制
[java] view plain copy
  1. /** 
  2.  * Plays a sound effect (Key clicks, lid open/close...) 
  3.  * @param effectType The type of sound effect. One of 
  4.  *            {@link #FX_KEY_CLICK}, 
  5.  *            {@link #FX_FOCUS_NAVIGATION_UP}, 
  6.  *            {@link #FX_FOCUS_NAVIGATION_DOWN}, 
  7.  *            {@link #FX_FOCUS_NAVIGATION_LEFT}, 
  8.  *            {@link #FX_FOCUS_NAVIGATION_RIGHT}, 
  9.  *            {@link #FX_KEYPRESS_STANDARD}, 
  10.  *            {@link #FX_KEYPRESS_SPACEBAR}, 
  11.  *            {@link #FX_KEYPRESS_DELETE}, 
  12.  *            {@link #FX_KEYPRESS_RETURN}, 
  13.  *            {@link #FX_KEYPRESS_INVALID}, 
  14.  * NOTE: This version uses the UI settings to determine 
  15.  * whether sounds are heard or not. 
  16.  */  
  17. public void  playSoundEffect(int effectType) {  
  18.     if (effectType < 0 || effectType >= NUM_SOUND_EFFECTS) {  
  19.         return;  
  20.     }  
  21.   
  22.     if (!querySoundEffectsEnabled()) {  
  23.         return;  
  24.     }  
  25.     //Ctv Patch Begin  
  26.     effectTypeForHandler = effectType;  
  27.     userHandler.removeCallbacks(queueRun);  
  28.     userHandler.postDelayed(queueRun, 20);  
  29.   
  30.     /*IAudioService service = getService(); 
  31.     try { 
  32.         service.playSoundEffect(effectType); 
  33.     } catch (RemoteException e) { 
  34.         Log.e(TAG, "Dead object in playSoundEffect"+e); 
  35.     }*/  
  36. }  
querySoundEffectsEnabled来查询系统是否设置开机全局按键音,而系统开启关闭按键也是通过Settings.System.SOUND_EFFECTS_ENABLED的设置来完成控制。

例如:Settings.System.putInt(mContext.getContentResolver(), Settings.System.SOUND_EFFECTS_ENABLED, 0/1) 关闭/开启 
0 0
原创粉丝点击