???????前段時間在做項目中接手了一項功能,具體就是有音樂播放時,需要在狀態欄上進行實時展示,并進行雙向控制,實現上使用到了MediaSession,為什么使用MediaSession呢?主要是為了解耦,減少了狀態欄與各音樂應用進程的直接通信,主流音樂應用都會使用MediaSession,閑暇之余看了一下MediaSession的相關邏輯實現,本文針對Android11對MediaSession的主要功能進行分析。
???????在開始分析之前,先對本文進行概括一下,主要分為4部分:
???????1.創建MediaSession服務端,實現對應的功能;
???????2.客戶端接收MediaSession創建回調,獲取MediaSession對應的MediaController;
???????3.MediaSession服務端控制媒體播放;
???????4.客戶端控制MediaSession服務端媒體播放;
???????主要涉及的類有:MediaSession、MediaController、MediaSessionManager、MediaSessionService、MediaSessionRecord等,先看一下類關系圖:
1. 創建MediaSession
???????音樂應用需要創建MediaSession,簡單看一下實現:
private void initMediaSession() {
MediaSession mediaSession = new MediaSession(this, getPackageName());
mediaSession.setCallback(new MediaSession.Callback() {
@Override
public void onCommand(@NonNull String command, @Nullable Bundle args, @Nullable ResultReceiver cb) {
super.onCommand(command, args, cb);
}
@Override
public void onPlay() {
super.onPlay();
}
@Override
public void onPause() {
super.onPause();
}
});
Intent intent = new Intent();
intent.setComponent(new ComponentName(getPackageName(), "com.hly.testActivity"));
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
mediaSession.setSessionActivity(PendingIntent.getActivity(this, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT));
mediaSession.setActive(true);
}
???????在創建MediaSession時,主要做了三件事:
???????1.setCallBack():客戶端控制媒體播放時,會進行通知回調,執行對應的播放控制動作;
???????2.setSessionActivity():傳入媒體播放界面對應的PendingIntent,客戶端通過該PendingIntent可以快速切換到播放界面;
???????3.setActive():設置該MediaSession為active即活躍狀態,每次設置都會觸發客戶端onActiveSessionChanged(),獲取新的MediaController列表;
???????接下來根據源碼來看一下具體的邏輯實現:
1.1.MediaSession.java
???????首先看一下構造方法:
public MediaSession(@NonNull Context context, @NonNull String tag,
@Nullable Bundle sessionInfo) {
........
mMaxBitmapSize = context.getResources().getDimensionPixelSize(
com.android.internal.R.dimen.config_mediaMetadataBitmapMaxSize);
mCbStub = new CallbackStub(this);
MediaSessionManager manager = (MediaSessionManager) context.getSystemService(Context.MEDIA_SESSION_SERVICE);
try {
mBinder = manager.createSession(mCbStub, tag, sessionInfo);
mSessionToken = new Token(Process.myUid(), mBinder.getController());
mController = new MediaController(context, mSessionToken);
} catch (RemoteException e) {
throw new RuntimeException("Remote error creating session.", e);
}
}
???????在構造方法內,進行一些變量的初始化及實例化:
???????1.mMaxBitmapSize:對應Metadata中Bitmap的最大寬度或高度,此處對應的是320dp;
???????2.創建CallbackStub實例mCbStub,CallbackStub繼承ISessionCallback.Stub,用來接收客戶端發生的控制命令,后面章節再進行分析;
???????3.通過MediaSessionManager的createSession()來創建ISession實例mBinder;接下來進行分析;
???????4.創建Token實例mSessionToken;
???????5.創建MediaSession對應的MediaController;
???????接下來先分析createSession():
1.2.MediaSessionManager.java
public ISession createSession(@NonNull MediaSession.CallbackStub cbStub, @NonNull String tag,
@Nullable Bundle sessionInfo) {
try {
return mService.createSession(mContext.getPackageName(), cbStub, tag, sessionInfo,
UserHandle.myUserId());
} catch (RemoteException e) {
throw new RuntimeException(e);
}
}
???????mService是ISessionManager.Stub實例,ISessionManager.Stub是在MediaSessionService內部進行實現的;
1.3.MediaSessionService.java
class SessionManagerImpl extends ISessionManager.Stub {
.....................
.....................
Override
public ISession createSession(String packageName, ISessionCallback cb, String tag,
Bundle sessionInfo, int userId) throws RemoteException {
.....................
try {
enforcePackageName(packageName, uid);
int resolvedUserId = ActivityManager.handleIncomingUser(pid, uid, userId,
false /* allowAll */, true /* requireFull */, "createSession", packageName);
if (cb == null) {
throw new IllegalArgumentException("Controller callback cannot be null");
}
MediaSessionRecord session = createSessionInternal(
pid, uid, resolvedUserId, packageName, cb, tag, sessionInfo);
if (session == null) {
hrow new IllegalStateException("Failed to create a new session record");
}
ISession sessionBinder = session.getSessionBinder();
if (sessionBinder == null) {
throw new IllegalStateException("Invalid session record");
}
return sessionBinder;
} catch (Exception e) {
Slog.w(TAG, "Exception in creating a new session", e);
throw e;
} finally {
Binder.restoreCallingIdentity(token);
}
}
}
???????在該方法內主要兩項工作:
???????1.通過createSessionInternal()創建MediaSessionRecord實例session;
???????2.通過session的getSessionBinder()返回ISession實例;
private MediaSessionRecord createSessionInternal(int callerPid, int callerUid, int userId,
String callerPackageName, ISessionCallback cb, String tag, Bundle sessionInfo) {
synchronized (mLock) {
int policies = 0;
if (mCustomSessionPolicyProvider != null) {
policies = mCustomSessionPolicyProvider.getSessionPoliciesForApplication(
callerUid, callerPackageName);
}
FullUserRecord user = getFullUserRecordLocked(userId);
if (user == null) {
Log.w(TAG, "Request from invalid user: " + userId + ", pkg=" + callerPackageName);
throw new RuntimeException("Session request from invalid user.");
}
final int sessionCount = user.mUidToSessionCount.get(callerUid, 0);
if (sessionCount >= SESSION_CREATION_LIMIT_PER_UID
&& !hasMediaControlPermission(callerPid, callerUid)) {
throw new RuntimeException("Created too many sessions. count="
+ sessionCount + ")");
}
final MediaSessionRecord session;
try {
session = new MediaSessionRecord(callerPid, callerUid, userId,
callerPackageName, cb, tag, sessionInfo, this,
mRecordThread.getLooper(), policies);
} catch (RemoteException e) {
throw new RuntimeException("Media Session owner died prematurely.", e);
}
user.mUidToSessionCount.put(callerUid, sessionCount + 1);
user.mPriorityStack.addSession(session);
mHandler.postSessionsChanged(session);
if (DEBUG) {
Log.d(TAG, "Created session for " + callerPackageName + " with tag " + tag);
}
return session;
}
}
???????通過以上代碼可以看到:
???????1.先進行一些判斷,FullUserRecord不能為空,sessionCount不能超過100;
???????2.創建MediaSessionRecord實例;
???????3.將進程的sessionCount交給mUidToSessionCount進行管理,將先創建的session交給mPriorityStack進行管理,后續在客戶端回調時會用到;
???????4.執行mHandler.postSessionsChanged(session)來通知客戶端activeSessions回調,后面再講;
1.4.MediaSessionRecord.java
public MediaSessionRecord(int ownerPid, int ownerUid, int userId, String ownerPackageName,
ISessionCallback cb, String tag, Bundle sessionInfo,
MediaSessionService service, Looper handlerLooper, int policies)
throws RemoteException {
mOwnerPid = ownerPid;
mOwnerUid = ownerUid;
mUserId = userId;
mPackageName = ownerPackageName;
mTag = tag;
mSessionInfo = sessionInfo;
mController = new ControllerStub();
mSessionToken = new MediaSession.Token(ownerUid, mController);
mSession = new SessionStub();
mSessionCb = new SessionCb(cb);
mService = service;
mContext = mService.getContext();
mHandler = new MessageHandler(handlerLooper);
mAudioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
mAudioManagerInternal = LocalServices.getService(AudioManagerInternal.class);
mAudioAttrs = DEFAULT_ATTRIBUTES;
mPolicies = policies;
// May throw RemoteException if the session app is killed.
mSessionCb.mCb.asBinder().linkToDeath(this, 0);
}
???????在構造方法內,進行了實例初始化:
???????1.創建ControllerStub實例mController,ControllerStub繼承了ISessionController.Stub,主要用來接收來自客戶端的控制;
???????2.創建MediaSession.Token實例mSessionToken;
???????3.創建SessionStub實例mSession,SessionStub繼承ISession.Stub,主要用來初始化MediaSession實例變量,比如:setSessionActivity()等;
???????4.創建SessionCb實例mSessionCb,mSessionCb接收到來自客戶端的控制調用mSessionCb,然后在其內部調用cb最終調用到MediaSession構造方法內部的CallbackStub實例mCbStub;
???????前面在MediaSessionService內部的createSession()創建MediaSessionRecord實例,然后調用getSessionBinder()返回ISession實例,即對應SessionStub實例mSession;
1.5.總結
???????MediaSession構造方法內部的createSession()最終返回的是MediaSessionRecord的實例mSession,用一張圖總結一下執行過程:
2.MediaSession創建完成回調
???????在MediaSession創建完成后,會回調給客戶端進行實時監聽,關于回調在上面已經分析到,具體實現是在MediaSessionService的createSessionInternal()內部創建完MediaSessionRecord實例后會執行mHandler.postSessionsChanged(session),一起看一下:
2.1.MediaSessionService.java
public void postSessionsChanged(MediaSessionRecordImpl record) {
// Use object instead of the arguments when posting message to remove pending requests.
Integer userIdInteger = mIntegerCache.get(record.getUserId());
if (userIdInteger == null) {
userIdInteger = Integer.valueOf(record.getUserId());
mIntegerCache.put(record.getUserId(), userIdInteger);
}
int msg = (record instanceof MediaSessionRecord)
? MSG_SESSIONS_1_CHANGED : MSG_SESSIONS_2_CHANGED;
removeMessages(msg, userIdInteger);
obtainMessage(msg, userIdInteger).sendToTarget();
}
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case MSG_SESSIONS_1_CHANGED:
pushSession1Changed((int) msg.obj);
break;
case MSG_SESSIONS_2_CHANGED:
pushSession2Changed((int) msg.obj);
break;
}
}
???????跟隨調用關系:
private void pushSession1Changed(int userId) {
synchronized (mLock) {
FullUserRecord user = getFullUserRecordLocked(userId);
if (user == null) {
Log.w(TAG, "pushSession1ChangedOnHandler failed. No user with id=" + userId);
return;
}
List<MediaSessionRecord> records = getActiveSessionsLocked(userId);
int size = records.size();
ArrayList<MediaSession.Token> tokens = new ArrayList<>();
for (int i = 0; i < size; i++) {
tokens.add(records.get(i).getSessionToken());
}
pushRemoteVolumeUpdateLocked(userId);
for (int i = mSessionsListeners.size() - 1; i >= 0; i--) {
SessionsListenerRecord record = mSessionsListeners.get(i);
if (record.userId == USER_ALL || record.userId == userId) {
try {
record.listener.onActiveSessionsChanged(tokens);
} catch (RemoteException e) {
Log.w(TAG, "Dead ActiveSessionsListener in pushSessionsChanged, removing",
e);
mSessionsListeners.remove(i);
}
}
}
}
}
???????1.先通過getActiveSessionsLocked()獲取到MediaSessionRecord列表,是通過mPriorityStack中獲取的;
???????2.創建MediaSession.Token列表,遍歷執行MediaSessionRecord的getSessionToken()方法來獲取對應的MediaSession.Token;
???????3.遍歷mSessionsListeners執行record.listener.onActiveSessionsChanged(tokens)回調給客戶端;
???????當然了,客戶端必須先注冊,才能接收到回調,先看一下客戶端注冊過程;
2.2.MediaSessionManager.java
public void addOnActiveSessionsChangedListener(
@NonNull OnActiveSessionsChangedListener sessionListener,
@Nullable ComponentName notificationListener, int userId, @Nullable Handler handler) {
if (sessionListener == null) {
throw new IllegalArgumentException("listener may not be null");
}
if (handler == null) {
handler = new Handler();
}
synchronized (mLock) {
if (mListeners.get(sessionListener) != null) {
Log.w(TAG, "Attempted to add session listener twice, ignoring.");
return;
}
SessionsChangedWrapper wrapper = new SessionsChangedWrapper(mContext, sessionListener,
handler);
try {
mService.addSessionsListener(wrapper.mStub, notificationListener, userId);
mListeners.put(sessionListener, wrapper);
} catch (RemoteException e) {
Log.e(TAG, "Error in addOnActiveSessionsChangedListener.", e);
}
}
}
???????先創建內部類SessionsChangedWrapper實例wrapper,然后將其內部變量mStub作為參數傳遞給MediaSessionService的addSessionsListener方法;
@Override
public void addSessionsListener(IActiveSessionsListener listener,
ComponentName componentName, int userId) throws RemoteException {
final int pid = Binder.getCallingPid();
final int uid = Binder.getCallingUid();
final long token = Binder.clearCallingIdentity();
try {
int resolvedUserId = verifySessionsRequest(componentName, userId, pid, uid);
synchronized (mLock) {
int index = findIndexOfSessionsListenerLocked(listener);
if (index != -1) {
Log.w(TAG, "ActiveSessionsListener is already added, ignoring");
return;
}
SessionsListenerRecord record = new SessionsListenerRecord(listener,
componentName, resolvedUserId, pid, uid);
try {
listener.asBinder().linkToDeath(record, 0);
} catch (RemoteException e) {
Log.e(TAG, "ActiveSessionsListener is dead, ignoring it", e);
return;
}
mSessionsListeners.add(record);
}
} finally {
Binder.restoreCallingIdentity(token);
}
}
???????將listener封裝成SessionsListenerRecord對象,最終存入mSessionsListeners進行管理,用來后續通知回調(前面可以看到);
???????再看一下SessionsChangedWrapper的實現:
2.2.1.SessionsChangedWrapper
private static final class SessionsChangedWrapper {
private Context mContext;
private OnActiveSessionsChangedListener mListener;
private Handler mHandler;
public SessionsChangedWrapper(Context context, OnActiveSessionsChangedListener listener,
Handler handler) {
mContext = context;
mListener = listener;
mHandler = handler;
}
private final IActiveSessionsListener.Stub mStub = new IActiveSessionsListener.Stub() {
@Override
public void onActiveSessionsChanged(final List<MediaSession.Token> tokens) {
final Handler handler = mHandler;
if (handler != null) {
handler.post(new Runnable() {
@Override
public void run() {
final Context context = mContext;
if (context != null) {
ArrayList<MediaController> controllers = new ArrayList<>();
int size = tokens.size();
for (int i = 0; i < size; i++) {
controllers.add(new MediaController(context, tokens.get(i)));
}
final OnActiveSessionsChangedListener listener = mListener;
if (listener != null) {
listener.onActiveSessionsChanged(controllers);
}
}
}
});
}
}
};
private void release() {
mListener = null;
mContext = null;
mHandler = null;
}
}
???????跟隨調用關系,最終會調用到該類的onActiveSessionsChanged()方法,在該方法內會遍歷tokens來獲取MediaSessionRecord對應的MediaSession.Token,創建MediaController[注意一下:每次有ActivieSession變化,都會返回不同的MediaController列表],最終回調到客戶端的是與MediaSession相關聯的MediaController列表;
3.客戶端控制
3.1.MediaController.java
public MediaController(@NonNull Context context, @NonNull MediaSession.Token token) {
if (context == null) {
throw new IllegalArgumentException("context shouldn't be null");
}
if (token == null) {
throw new IllegalArgumentException("token shouldn't be null");
}
if (token.getBinder() == null) {
throw new IllegalArgumentException("token.getBinder() shouldn't be null");
}
mSessionBinder = token.getBinder();
mTransportControls = new TransportControls();
mToken = token;
mContext = context;
}
???????在MediaController構造方法內部,主要執行了兩項工作:
???????1.通過getBinder()獲取ISessionController實例mSessionBinder,從名字可以看到是用來控制MediaSession的;
???????2.創建TransportControls()實例mTransportControls,客戶端用來執行控制;
3.1.1. TransportControls
public final class TransportControls {
..............
public void play() {
try {
mSessionBinder.play(mContext.getPackageName());
} catch (RemoteException e) {
Log.wtf(TAG, "Error calling play.", e);
}
}
.....................
public void pause() {
try {
mSessionBinder.pause(mContext.getPackageName());
} catch (RemoteException e) {
Log.wtf(TAG, "Error calling pause.", e);
}
}
.....................
}
???????TransportControls最終是通過mSessionBinder來進行命令控制,mSessionBinder是通過token.getBinder()來獲取,反過來看一下MediaSession.Token實現:
3.2.MediaSession.Token
public static final class Token implements Parcelable {
private final int mUid;
private final ISessionController mBinder;
public Token(int uid, ISessionController binder) {
mUid = uid;
mBinder = binder;
}
Token(Parcel in) {
mUid = in.readInt();
mBinder = ISessionController.Stub.asInterface(in.readStrongBinder());
}
public ISessionController getBinder() {
return mBinder;
}
}
???????再回到MediaSessionRecord內部看一下該mBinder的由來:
3.3.MediaSessionRecord.java
public MediaSessionRecord(int ownerPid, int ownerUid, int userId, String ownerPackageName,
ISessionCallback cb, String tag, Bundle sessionInfo,
MediaSessionService service, Looper handlerLooper, int policies)
throws RemoteException {
..............
mController = new ControllerStub();
mSessionToken = new MediaSession.Token(ownerUid, mController);
mSessionCb = new SessionCb(cb);
............
}
??????? ISessionController實例是由ControllerStub創建而來,所以當執行控制時,調用的是ControllerStub內部的方法:
3.3.1.ControllerStub
class ControllerStub extends ISessionController.Stub {
@Override
public void sendCommand(String packageName, String command, Bundle args,
ResultReceiver cb) {
mSessionCb.sendCommand(packageName, Binder.getCallingPid(), Binder.getCallingUid(),
command, args, cb);
}
..................
@Override
public void play(String packageName) {
mSessionCb.play(packageName, Binder.getCallingPid(), Binder.getCallingUid());
}
....................
@Override
public void pause(String packageName) {
mSessionCb.pause(packageName, Binder.getCallingPid(), Binder.getCallingUid());
}
}
??????? 在ControllerStub內部的方法又會調用到SessionCb的方法:
3.3.2.SessionCb
class SessionCb {
private final ISessionCallback mCb;
SessionCb(ISessionCallback cb) {
mCb = cb;
}
public void sendCommand(String packageName, int pid, int uid, String command, Bundle args,
ResultReceiver cb) {
try {
mCb.onCommand(packageName, pid, uid, command, args, cb);
} catch (RemoteException e) {
Slog.e(TAG, "Remote failure in sendCommand.", e);
}
}
public void play(String packageName, int pid, int uid) {
try {
mCb.onPlay(packageName, pid, uid);
} catch (RemoteException e) {
Slog.e(TAG, "Remote failure in play.", e);
}
}
public void pause(String packageName, int pid, int uid) {
try {
mCb.onPause(packageName, pid, uid);
} catch (RemoteException e) {
Slog.e(TAG, "Remote failure in pause.", e);
}
}
??????? SessionCb會調用到mCb,即MediaSession內部的CallbackStub實例;
3.4.MediaSession.CallbackStub
public static class CallbackStub extends ISessionCallback.Stub {
.............
@Override
public void onCommand(String packageName, int pid, int uid, String command, Bundle args,
ResultReceiver cb) {
MediaSession session = mMediaSession.get();
if (session != null) {
session.dispatchCommand(createRemoteUserInfo(packageName, pid, uid),
command, args, cb);
}
}
@Override
public void onPlay(String packageName, int pid, int uid) {
MediaSession session = mMediaSession.get();
if (session != null) {
session.dispatchPlay(createRemoteUserInfo(packageName, pid, uid));
}
}
@Override
public void onPause(String packageName, int pid, int uid) {
MediaSession session = mMediaSession.get();
if (session != null) {
session.dispatchPause(createRemoteUserInfo(packageName, pid, uid));
}
}
}
???????跟隨調用關系,最終會調用到mCallback的方法,前面已經講到,mCallBack是音樂應用在創建MediaSession時,傳入的本地實現;
case MSG_COMMAND:
Command cmd = (Command) obj;
mCallback.onCommand(cmd.command, cmd.extras, cmd.stub);
break;
case MSG_PLAY:
mCallback.onPlay();
break;
case MSG_PAUSE:
mCallback.onPause();
break;
???????音樂應用內部對應實現對應的方法,那么控制就生效了。
3.5.總結
???????客戶端MediaController通過TransportControls來進行控制,最終會對應到服務端MediaSession的Callback,對應關系如下:
TransportControls | MediaSession.Callback |
---|---|
play() | onPlay() |
pause() | onPause |
stop() | onStop |
skipToNext() | onSkipToNext() |
4.MediaSession端控制
4.1.MediaSession.java
???????直接看代碼,播放暫停相關控制通過setPlaybackState(),PlaybackState:STATE_PAUSED = 2、STATE_PLAYING = 3;
public void setPlaybackState(@Nullable PlaybackState state) {
mPlaybackState = state;
try {
mBinder.setPlaybackState(state);
} catch (RemoteException e) {
Log.wtf(TAG, "Dead object in setPlaybackState.", e);
}
}
???????媒體信息發生變化時,可以通過setMetadata來更新MediaMetadata信息就可以了;
public void setMetadata(@Nullable MediaMetadata metadata) {
long duration = -1;
int fields = 0;
MediaDescription description = null;
if (metadata != null) {
metadata = (new MediaMetadata.Builder(metadata, mMaxBitmapSize)).build();
if (metadata.containsKey(MediaMetadata.METADATA_KEY_DURATION)) {
duration = metadata.getLong(MediaMetadata.METADATA_KEY_DURATION);
}
fields = metadata.size();
description = metadata.getDescription();
}
String metadataDescription = "size=" + fields + ", description=" + description;
try {
mBinder.setMetadata(metadata, duration, metadataDescription);
} catch (RemoteException e) {
Log.wtf(TAG, "Dead object in setPlaybackState.", e);
}
}
???????通過上面的分析,mBinder是通過MediaSessionRecord的getSessionBinder()來返回的,接下來一起看一下執行過程:
4.2.MediaSessionRecord.java
???????getSessionBinder()返回的是SessionStub實例:
private final class SessionStub extends ISession.Stub {
.............
@Override
public void setPlaybackState(PlaybackState state) throws RemoteException {
int oldState = mPlaybackState == null
? PlaybackState.STATE_NONE : mPlaybackState.getState();
int newState = state == null
? PlaybackState.STATE_NONE : state.getState();
boolean shouldUpdatePriority = ALWAYS_PRIORITY_STATES.contains(newState)
|| (!TRANSITION_PRIORITY_STATES.contains(oldState)
&& TRANSITION_PRIORITY_STATES.contains(newState));
synchronized (mLock) {
mPlaybackState = state;
}
final long token = Binder.clearCallingIdentity();
try {
mService.onSessionPlaybackStateChanged(
MediaSessionRecord.this, shouldUpdatePriority);
} finally {
Binder.restoreCallingIdentity(token);
}
mHandler.post(MessageHandler.MSG_UPDATE_PLAYBACK_STATE);
}
.................
}
???????通過該方法可以看到,主要有四項工作:
???????1.首先判斷當前的playState及新的playState,然后來確定shouldUpdatePriority的值,該值表示當前的MediaSession處于活躍狀態(比如:由暫停到播放);
???????2.更新mPlaybackState值為最新的playbackstate;
???????3.執行mService.onSessionPlaybackStateChanged()來通知MediaSessionSession來根據shouldUpdatePriority來確定是否需要更新相關狀態;
void onSessionPlaybackStateChanged(MediaSessionRecordImpl record,
boolean shouldUpdatePriority) {
synchronized (mLock) {
FullUserRecord user = getFullUserRecordLocked(record.getUserId());
if (user == null || !user.mPriorityStack.contains(record)) {
Log.d(TAG, "Unknown session changed playback state. Ignoring.");
return;
}
user.mPriorityStack.onPlaybackStateChanged(record, shouldUpdatePriority);
}
}
???????如果shouldUpdatePriority為true,則將record放在mSessions的首位,反之將mCachedVolumeDefault置空;
public void onPlaybackStateChanged(
MediaSessionRecordImpl record, boolean shouldUpdatePriority) {
if (shouldUpdatePriority) {
mSessions.remove(record);
mSessions.add(0, record);
clearCache(record.getUserId());
} else if (record.checkPlaybackActiveState(false)) {
// Just clear the volume cache when a state goes inactive
mCachedVolumeDefault = null;
}
..........
}
???????4.執行mHandler.post(MessageHandler.MSG_UPDATE_PLAYBACK_STATE)來通知客戶端進行狀態更新;
private void pushPlaybackStateUpdate() {
synchronized (mLock) {
if (mDestroyed) {
return;
}
for (int i = mControllerCallbackHolders.size() - 1; i >= 0; i--) {
ISessionControllerCallbackHolder holder = mControllerCallbackHolders.get(i);
try {
holder.mCallback.onPlaybackStateChanged(mPlaybackState);
} catch (DeadObjectException e) {
mControllerCallbackHolders.remove(i);
logCallbackException("Removing dead callback in pushPlaybackStateUpdate",
holder, e);
} catch (RemoteException e) {
logCallbackException("unexpected exception in pushPlaybackStateUpdate",
holder, e);
}
}
}
}
???????可以看到,發生消息來通知回調,當然了,客戶端要想接收回調,肯定需要先進行注冊,通過MediaController來進行注冊,一起看一下:
4.3.MediaController.java
public void registerCallback(@NonNull Callback callback) {
registerCallback(callback, null);
}
public void registerCallback(@NonNull Callback callback, @Nullable Handler handler) {
.........
synchronized (mLock) {
addCallbackLocked(callback, handler);
}
}
private void addCallbackLocked(Callback cb, Handler handler) {
if (getHandlerForCallbackLocked(cb) != null) {
Log.w(TAG, "Callback is already added, ignoring");
return;
}
MessageHandler holder = new MessageHandler(handler.getLooper(), cb);
mCallbacks.add(holder);
holder.mRegistered = true;
if (!mCbRegistered) {
try {
mSessionBinder.registerCallback(mContext.getPackageName(), mCbStub);
mCbRegistered = true;
} catch (RemoteException e) {
Log.e(TAG, "Dead object in registerCallback", e);
}
}
}
???????跟隨調用關系,可以看到:
???????1.首先將cb封裝到MessageHandler內部,然后放入mCallbacks進行管理;
???????2.執行mSessionBinder的registerCallback()方法,mCbStub對應的是CallbackStub實例,用來接收回調:
private static final class CallbackStub extends ISessionControllerCallback.Stub {
private final WeakReference<MediaController> mController;
CallbackStub(MediaController controller) {
mController = new WeakReference<MediaController>(controller);
}
.................
@Override
public void onPlaybackStateChanged(PlaybackState state) {
MediaController controller = mController.get();
if (controller != null) {
controller.postMessage(MSG_UPDATE_PLAYBACK_STATE, state, null);
}
}
@Override
public void onMetadataChanged(MediaMetadata metadata) {
MediaController controller = mController.get();
if (controller != null) {
controller.postMessage(MSG_UPDATE_METADATA, metadata, null);
}
}
..................
}
前面分析到,mSessionBinders是通過token.getBinder()來獲得的,最終返回的是MediaSessionRecord的ControllerStub實例mController,看一下對應實現:
4.4.MediaSessionRecord.ControllerStub
class ControllerStub extends ISessionController.Stub {
................
@Override
public void registerCallback(String packageName, ISessionControllerCallback cb) {
synchronized (mLock) {
// If this session is already destroyed tell the caller and
// don't add them.
if (mDestroyed) {
try {
cb.onSessionDestroyed();
} catch (Exception e) {
// ignored
}
return;
}
if (getControllerHolderIndexForCb(cb) < 0) {
mControllerCallbackHolders.add(new ISessionControllerCallbackHolder(cb,
packageName, Binder.getCallingUid()));
}
}
}
}
???????將ISessionControllerCallback封裝到ISessionControllerCallbackHolder中,然后加入到mControllerCallbackHolders進行管理,根據前面講到的,有狀態變化時通知回調就是對mControllerCallbackHolders進行holder.mCallback.onPlaybackStateChanged(mPlaybackState)遍歷通知,再返回到MediaController內部的CallbackStub:
public void onPlaybackStateChanged(PlaybackState state) {
MediaController controller = mController.get();
if (controller != null) {
controller.postMessage(MSG_UPDATE_PLAYBACK_STATE, state, null);
}
}
case MSG_UPDATE_PLAYBACK_STATE:
mCallback.onPlaybackStateChanged((PlaybackState) msg.obj);
break;
4.5.總結
???????服務端MediaSession進行控制,最終會對應到客戶端MediaController的Callback,對應關系如下:
MediaSession | MediaController.Callback |
---|---|
setMetadata(MediaMetadata) | onMetadataChanged(MediaMetadata) |
setPlaybackState(PlaybackState) | onPlaybackStateChanged(PlaybackState) |
???????客戶端MediaController與服務端MediaSession雙向控制圖如下:
???????以上就是MediaSession工作的相關流程,詳細邏輯還需要通過源碼進一步了解!