音效流程分析

音效事件起源

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;    }
在performClick里面主要做了两件事,调用监听器的onClick回调和playSoundEffect,监听器的回调是在setOnClickListener中注册下来,下面主要分析playSoundEffect
    /**     * Play a sound effect for this view.     *     * 

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. * *

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); }

利用成员变量mAttachInfo的mRootCallbacks回调调用另外一个音效处理函数playSoundEffect,这里找到mAttachInfo和mRootCallbacks的源头就很重要,其实每个View控件 在创建后,都会绑定到对应窗口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 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);    }
函数开始处有mAttachInfo = info,就给mAttachInfo赋值了,继续向上索引,ViewRootImpl.performTraversals中调用host.dispatchAttachedToWindow(attachInfo, 0); 将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();    }
mAttachInfo = new View.AttachInfo(mWindowSession, mWindow, display, this, mHandler, this);  再看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;        }
mRootCallbacks 其实就是ViewRootImp的实例,这样View.playSoundEffect就进入了ViewRootImp.playSoundEffect.

音频系统接管音效控制

从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();        }    }
AudioManager接管音效控制权,接着AudioService的onPlaySoundEffect会被调用
        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);        }*/    }
querySoundEffectsEnabled来查询系统是否设置开机全局按键音,而系统开启关闭按键也是通过Settings.System.SOUND_EFFECTS_ENABLED的设置来完成控制。
例如:Settings.System.putInt(mContext.getContentResolver(), Settings.System.SOUND_EFFECTS_ENABLED, 0/1) 关闭/开启 









更多相关文章

  1. [置顶] android6.0源码分析之Activity启动过程
  2. 一个超简单的 android 延时函数
  3. Android(安卓)6.0 存储权限管理
  4. Android菜鸟日记 22音乐- MediaPlayer
  5. Android(安卓)调用 startActivityForResult(intent,requestCode)
  6. 解读Android之Service(3)AIDL
  7. Android(安卓)Java层的anti-hooking技巧
  8. Android复习笔记(8) - 服务(Service)
  9. Android(安卓)数据共享标准:ContentProvider 简介

随机推荐

  1. Android 的自定义Spinner组件实现方式
  2. Android(安卓)判断app是否在最上层展示
  3. android中自定义attr,以及style杂谈
  4. Android UI设计:TextView与EditText
  5. 〖Android〗arm-linux-androideabi-gdb报
  6. ubuntu下无法更新android sdk问题 adt更
  7. android中activity如何之间互传list
  8. android常见错误-android:android.conten
  9. Android应用程序版本切换
  10. android 9.0 打开 MTK ENGMODE里面的 手