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