audio系列之MediaButton--基于Android7.0

AudioManager#registreMediaButtonBroadcastReceiver(PendingIntent);

 

/**
     * @hide
     * no-op if (pi == null) or (eventReceiver == null)
     */
    public void registerMediaButtonIntent(PendingIntent pi, ComponentName eventReceiver) {
        if (pi == null) {
            Log.e(TAG, "Cannot call registerMediaButtonIntent() with a null parameter");
            return;
        }
        MediaSessionLegacyHelper helper = MediaSessionLegacyHelper.getHelper(getContext());
        helper.addMediaButtonListener(pi, eventReceiver, getContext());
    }


下面看下MediaSessionLegacyHelper#addMediaButtonListener():

 

 

public void addMediaButtonListener(PendingIntent pi, ComponentName mbrComponent,
            Context context) {
        if (pi == null) {
            Log.w(TAG, "Pending intent was null, can't addMediaButtonListener.");
            return;
        }
        SessionHolder holder = getHolder(pi, true);
        if (holder == null) {
            return;
        }
        if (holder.mMediaButtonListener != null) {
            // Already have this listener registered
            if (DEBUG) {
                Log.d(TAG, "addMediaButtonListener already added " + pi);
            }
        }
        holder.mMediaButtonListener = new MediaButtonListener(pi, context);
        // TODO determine if handling transport performer commands should also
        // set this flag
        holder.mFlags |= MediaSession.FLAG_HANDLES_MEDIA_BUTTONS;
        holder.mSession.setFlags(holder.mFlags);
        holder.mSession.setMediaButtonReceiver(pi);
        holder.update();
        if (DEBUG) {
            Log.d(TAG, "addMediaButtonListener added " + pi);
        }
    }


其中在getHolder()中用到了mSessions,是一个ArrayMap。

 

 

 

holder.mSession.setMediaButtonReceiver(pi);分析这句:其中mSession是MediaSession类型

 

/**
     * Set a pending intent for your media button receiver to allow restarting
     * playback after the session has been stopped. If your app is started in
     * this way an {@link Intent#ACTION_MEDIA_BUTTON} intent will be sent via
     * the pending intent.
     *
     * @param mbr The {@link PendingIntent} to send the media button event to.
     */
    public void setMediaButtonReceiver(@Nullable PendingIntent mbr) {
        try {
            mBinder.setMediaButtonReceiver(mbr);
        } catch (RemoteException e) {
            Log.wtf(TAG, "Failure in setMediaButtonReceiver.", e);
        }
    }

   


其中用到了mBinder(ISession),这是一个IInterface,就肯定是把Receiver注册到一个远程对象中了,就是一个注册到一个系统服务中,而这个系统服务持有这个ISession实例。MediaSession应该是媒体会话的意思

 

它的实现者是private final class SessionStub extends ISession.Stub,而SessionStub是MediaSessionRecord的内部类。

SessionStub#setMediaButtonReceiver()

 

@Override
        public void setMediaButtonReceiver(PendingIntent pi) {
            mMediaButtonReceiver = pi;
        }

由上可知SessionStub和一个媒体会话,即一个MediaButtonReceiver对应。MediaSession和MediaSessionRecord也是。
 

 

 

那在AudioManager注册完Receiver后,AudioManager在截取了ACTION_MEDIA_BUTTONG广播后是如果分配给这些Receiver的,下面接着分析:

MediaSessionLegacyHelper#sendMediaButtonEvent(KeyEvent keyEvent, boolean needWakeLock)是入口:

 

public void sendMediaButtonEvent(KeyEvent keyEvent, boolean needWakeLock) {
        if (keyEvent == null) {
            Log.w(TAG, "Tried to send a null key event. Ignoring.");
            return;
        }
        mSessionManager.dispatchMediaKeyEvent(keyEvent, needWakeLock);
        if (DEBUG) {
            Log.d(TAG, "dispatched media key " + keyEvent);
        }
    }


最终去到class SessionManagerImpl extends ISessionManager.Stub #public void dispatchMediaKeyEvent(KeyEvent keyEvent, boolean needWakeLock),

 

而SessionManagerImpl是MediaSessionService的内部类。

 

/**
         * Handles the dispatching of the media button events to one of the
         * registered listeners, or if there was none, broadcast an
         * ACTION_MEDIA_BUTTON intent to the rest of the system.
         *
         * @param keyEvent a non-null KeyEvent whose key code is one of the
         *            supported media buttons
         * @param needWakeLock true if a PARTIAL_WAKE_LOCK needs to be held
         *            while this key event is dispatched.
         */
        @Override
        public void dispatchMediaKeyEvent(KeyEvent keyEvent, boolean needWakeLock) {
            if (keyEvent == null || !KeyEvent.isMediaKey(keyEvent.getKeyCode())) {
                Log.w(TAG, "Attempted to dispatch null or non-media key event.");
                return;
            }

            final int pid = Binder.getCallingPid();
            final int uid = Binder.getCallingUid();
            final long token = Binder.clearCallingIdentity();
            try {
                if (DEBUG) {
                    Log.d(TAG, "dispatchMediaKeyEvent, pid=" + pid + ", uid=" + uid + ", event="
                            + keyEvent);
                }
                if (!isUserSetupComplete()) {
                    // Global media key handling can have the side-effect of starting new
                    // activities which is undesirable while setup is in progress.
                    Slog.i(TAG, "Not dispatching media key event because user "
                            + "setup is in progress.");
                    return;
                }

                synchronized (mLock) {
                    // If we don't have a media button receiver to fall back on
                    // include non-playing sessions for dispatching
                    UserRecord ur = mUserRecords.get(ActivityManager.getCurrentUser());
                    boolean useNotPlayingSessions = (ur == null) ||
                            (ur.mLastMediaButtonReceiver == null
                                && ur.mRestoredMediaButtonReceiver == null);
                    MediaSessionRecord session = mPriorityStack
                            .getDefaultMediaButtonSession(mCurrentUserId, useNotPlayingSessions);
                    if (isVoiceKey(keyEvent.getKeyCode())) {
                        handleVoiceKeyEventLocked(keyEvent, needWakeLock, session);
                    } else {
                        dispatchMediaKeyEventLocked(keyEvent, needWakeLock, session);
                    }
                }
            } finally {
                Binder.restoreCallingIdentity(token);
            }
        }

重点分析这个两句:

 

 

MediaSessionRecord session = mPriorityStack
                            .getDefaultMediaButtonSession(mCurrentUserId, useNotPlayingSessions);
                    if (isVoiceKey(keyEvent.getKeyCode())) {
                        handleVoiceKeyEventLocked(keyEvent, needWakeLock, session);
                    } else {
                        dispatchMediaKeyEventLocked(keyEvent, needWakeLock, session);
                    }
                    

mPriorityStack是MediaSessionStack类型,看getDefaultMEdiaButtonSession()源码,这部分非常重要,就是按怎样的规则去选择栈中record(MediaSessionRecord)的:

 

 

/**
     * Get the highest priority session that can handle media buttons.
     *
     * @param userId The user to check.
     * @param includeNotPlaying Return a non-playing session if nothing else is
     *            available
     * @return The default media button session or null.
     */
    public MediaSessionRecord getDefaultMediaButtonSession(int userId, boolean includeNotPlaying) {
        if (mGlobalPrioritySession != null && mGlobalPrioritySession.isActive()) {
            return mGlobalPrioritySession;
        }
        if (mCachedButtonReceiver != null) {
            return mCachedButtonReceiver;//这个成员在特定情况会被置null
        }//只有在上面两句不符合条件时,才重新获取优先级列表。
        ArrayList records = getPriorityListLocked(true,
                MediaSession.FLAG_HANDLES_MEDIA_BUTTONS, userId);
        if (records.size() > 0) {
            //在records列表中扫描
            MediaSessionRecord record = records.get(0);
           //判断给session(会话)是否正在播放,如果是则把事件给它。
            if (record.isPlaybackActive(false)) {//优先正在播放的
                // Since we're going to send a button event to this record make
                // it the last interesting one.
                mLastInterestingRecord = record;
                mCachedButtonReceiver = record;
            } else if (mLastInterestingRecord != null) {
                if (records.contains(mLastInterestingRecord)) {
                    mCachedButtonReceiver = mLastInterestingRecord;
                } else {
                    // That record is no longer used. Clear its reference.
                    mLastInterestingRecord = null;
                }
            }
        //如果includeNotPlaying标志位true,且cache为空,则record不是正在播放也分给它。
             if (includeNotPlaying && mCachedButtonReceiver == null) {
                // If we really want a record and we didn't find one yet use the
                // highest priority session even if it's not playing.
                mCachedButtonReceiver = record;
            }
        }
        return mCachedButtonReceiver;
    }

 

mLastInterestingRecord: session add 进来时,被指为mLastInterestingRecord; playstate 改变的那个session

 

                                            被指为mLastInterestingRecord;

mCacheButtonReceiver: 上次被分派MediaButton 的播放/暂停键事件的session会被指为

                                             mLastInterestingRecord;

mLastInterestingRecord和mLastInterestingRecord在playstate改变、add session、remove session时被清空。

当然,mLastInterestingRecord在playstate改变时,是清空后,重新赋值。

所以如果想增加自己优先级,可以在某些时候add一下,因为mSessions是一个数组,不怕重复。如果怕以后会改成元素不能重复的数据结构,那么可以在add前,先unregister,然后定时的执行UNregister和register操作,因为有些音乐app焦点抢夺很厉害,比如QQ音乐。

如果想要最高的优先级,就给那个session设置一个参数:

public static final int FLAG_EXCLUSIVE_GLOBAL_PRIORITY = 1 << 16;(MediaSession)

但是不知道怎么去设置。

主要分析一下:

 

 

ArrayList records = getPriorityListLocked(true,
                MediaSession.FLAG_HANDLES_MEDIA_BUTTONS, userId);

下面是MediaSessionStack#getPriorityLocked()源码:

 

 

 

/**
     * Get a priority sorted list of sessions. Can filter to only return active
     * sessions or sessions with specific flags.
     *
     * @param activeOnly True to only return active sessions, false to return
     *            all sessions.
     * @param withFlags Only return sessions with all the specified flags set. 0
     *            returns all sessions.
     * @param userId The user to get sessions for. {@link UserHandle#USER_ALL}
     *            will return sessions for all users.
     * @return The priority sorted list of sessions.
     */
    private ArrayList getPriorityListLocked(boolean activeOnly, int withFlags,
            int userId) {
        ArrayList result = new ArrayList();
        int lastLocalIndex = 0;
        int lastActiveIndex = 0;
        int lastPublishedIndex = 0;

        int size = mSessions.size();
        for (int i = 0; i < size; i++) {
            final MediaSessionRecord session = mSessions.get(i);

            if (userId != UserHandle.USER_ALL && userId != session.getUserId()) {
                // Filter out sessions for the wrong user
                continue;
            }
            if ((session.getFlags() & withFlags) != withFlags) {
                // Filter out sessions with the wrong flags
                continue;
            }
            if (!session.isActive()) {//若非active
                if (!activeOnly) {//若没有规定只要active的
                    // If we're getting unpublished as well always put them at
                    // the end
                    result.add(session);
                }
                continue;
            }

            if (session.isSystemPriority()) {//若是系统优先级
                // System priority sessions are special and always go at the
                // front. We expect there to only be one of these at a time.
                result.add(0, session);//加到最前面
                lastLocalIndex++;
                lastActiveIndex++;
                lastPublishedIndex++;
            } else if (session.isPlaybackActive(true)) {//若该session的进程在后台播放
                // TODO this with real local route check
                if (true) {
                    // Active local sessions get top priority
                    result.add(lastLocalIndex, session);//加在isplaybackactive的session后面
                    lastLocalIndex++;
                    lastActiveIndex++;
                    lastPublishedIndex++;
                } else {
                    // Then active remote sessions
                    result.add(lastActiveIndex, session);//加在active的session的后面
                    lastActiveIndex++;
                    lastPublishedIndex++;
                }
            } else {//若非在后台播放
                // inactive sessions go at the end in order of whoever last did
                // something.
                result.add(lastPublishedIndex, session);
                lastPublishedIndex++;
            }
        }

        return result;
    }

看上面的源码加载arraylist的index为位置的record优先级是最高的。

 

isSystemPriority()         isPlayBackActive()  isActive()然后是其他的,只要是Record的userId对的话都会加入arraylist中,

只是优先级不一样。

 

 

其中 dispatchMediaKeyEventLocked(keyEvent, needWakeLock, session);,看源码:

 

 private void dispatchMediaKeyEventLocked(KeyEvent keyEvent, boolean needWakeLock,
                MediaSessionRecord session) {
            if (session != null) {
                if (DEBUG) {
                    Log.d(TAG, "Sending media key to " + session.toString());
                }
                if (needWakeLock) {
                    mKeyEventReceiver.aquireWakeLockLocked();
                }
                // If we don't need a wakelock use -1 as the id so we
                // won't release it later
                session.sendMediaButton(keyEvent,
                        needWakeLock ? mKeyEventReceiver.mLastTimeoutId : -1,
                        mKeyEventReceiver);
            } else {
                // Launch the last PendingIntent we had with priority
                int userId = ActivityManager.getCurrentUser();
                UserRecord user = mUserRecords.get(userId);
                if (user.mLastMediaButtonReceiver != null
                        || user.mRestoredMediaButtonReceiver != null) {
                    if (DEBUG) {
                        Log.d(TAG, "Sending media key to last known PendingIntent "
                                + user.mLastMediaButtonReceiver + " or restored Intent "
                                + user.mRestoredMediaButtonReceiver);
                    }
                    if (needWakeLock) {
                        mKeyEventReceiver.aquireWakeLockLocked();
                    }
                    Intent mediaButtonIntent = new Intent(Intent.ACTION_MEDIA_BUTTON);
                    mediaButtonIntent.putExtra(Intent.EXTRA_KEY_EVENT, keyEvent);
                    try {
                        if (user.mLastMediaButtonReceiver != null) {
                            user.mLastMediaButtonReceiver.send(getContext(),
                                    needWakeLock ? mKeyEventReceiver.mLastTimeoutId : -1,
                                    mediaButtonIntent, mKeyEventReceiver, null);
                        } else {
                            mediaButtonIntent.setComponent(user.mRestoredMediaButtonReceiver);
                            getContext().sendBroadcastAsUser(mediaButtonIntent,
                                    new UserHandle(userId));
                        }
                    } catch (CanceledException e) {
                        Log.i(TAG, "Error sending key event to media button receiver "
                                + user.mLastMediaButtonReceiver, e);
                    }
                } else {
                    if (DEBUG) {
                        Log.d(TAG, "Sending media key ordered broadcast");
                    }
                    if (needWakeLock) {
                        mMediaEventWakeLock.acquire();
                    }
                    // Fallback to legacy behavior
                    Intent keyIntent = new Intent(Intent.ACTION_MEDIA_BUTTON, null);
                    keyIntent.putExtra(Intent.EXTRA_KEY_EVENT, keyEvent);
                    if (needWakeLock) {
                        keyIntent.putExtra(EXTRA_WAKELOCK_ACQUIRED,
                                WAKELOCK_RELEASE_ON_FINISHED);
                    }
                    getContext().sendOrderedBroadcastAsUser(keyIntent, UserHandle.CURRENT,
                            null, mKeyEventDone, mHandler, Activity.RESULT_OK, null, null);
                }
            }
        }

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

你可能感兴趣的:(移动开发综合)