前段时间在做项目中接手了一项功能,具体就是有音乐播放时,需要在状态栏上进行实时展示,并进行双向控制,实现上使用到了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 records = getActiveSessionsLocked(userId);
int size = records.size();
ArrayList 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 tokens) {
final Handler handler = mHandler;
if (handler != null) {
handler.post(new Runnable() {
@Override
public void run() {
final Context context = mContext;
if (context != null) {
ArrayList 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 mController;
CallbackStub(MediaController controller) {
mController = new WeakReference(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工作的相关流程,详细逻辑还需要通过源码进一步了解!