MediaSession框架的源码分析
一:MediaSession的框架图
1:整体框架
2:音频应用的框架图
音频应用框架在MediaSession通用框架中加了MediaBroswer和MediaBrowserService,这篇主要根据音频应用的逻辑简单了解一下MediaSession的源码。
二:源码分析
1:创建媒体服务MediaBrowserService
MyMusicService,它继承MediaBrowserServiceCompat,
class MyMusicService : MediaBrowserServiceCompat() { ... }复制代码
MediaBrowserServiceCompat
MediaBrowserServiceCompat继承service,它是个后台服务,
看onCreate中,根据不同的版本做了兼容,发现他们都是实现了MediaBrowserServiceImpl接口。 以及onBind方法返回的也是mImpl的onbind()。
public abstract class MediaBrowserServiceCompat extends Service { private MediaBrowserServiceImpl mImpl; @Override public void onCreate() { super.onCreate(); if (Build.VERSION.SDK_INT >= 28) { mImpl = new MediaBrowserServiceImplApi28(); } else if (Build.VERSION.SDK_INT >= 26) { mImpl = new MediaBrowserServiceImplApi26(); } else if (Build.VERSION.SDK_INT >= 23) { mImpl = new MediaBrowserServiceImplApi23(); } else if (Build.VERSION.SDK_INT >= 21) { mImpl = new MediaBrowserServiceImplApi21(); } else { mImpl = new MediaBrowserServiceImplBase(); } mImpl.onCreate(); } @Override public IBinder onBind(Intent intent) { return mImpl.onBind(intent); } }复制代码
MediaBrowserServiceImpl接口的内容
interface MediaBrowserServiceImpl { void onCreate(); IBinder onBind(Intent intent); void setSessionToken(MediaSessionCompat.Token token); void notifyChildrenChanged(String parentId, Bundle options); void notifyChildrenChanged(RemoteUserInfo remoteUserInfo, String parentId, Bundle options); Bundle getBrowserRootHints(); RemoteUserInfo getCurrentBrowserInfo(); }复制代码
再看MediaBrowserServiceImplApi28类的实现,发现一步步到21才是最终的,
看onCreate中MediaBrowserServiceApi21类创建了MediaBrowserService,并调用它的onCreate,onbind一样,以及setSessionToken。
@RequiresApi(21) class MediaBrowserServiceImplApi21 implements MediaBrowserServiceImpl { final List<Bundle> mRootExtrasList = new ArrayList<>(); MediaBrowserService mServiceFwk; Messenger mMessenger; @Override public void onCreate() { mServiceFwk = new MediaBrowserServiceApi21(MediaBrowserServiceCompat.this); mServiceFwk.onCreate(); } @Override public IBinder onBind(Intent intent) { return mServiceFwk.onBind(intent); } @Override public void setSessionToken(final MediaSessionCompat.Token token) { mHandler.postOrRun(new Runnable() { @Override public void run() { if (!mRootExtrasList.isEmpty()) { IMediaSession extraBinder = token.getExtraBinder(); if (extraBinder != null) { for (Bundle rootExtras : mRootExtrasList) { BundleCompat.putBinder(rootExtras, EXTRA_SESSION_BINDER, extraBinder.asBinder()); } } mRootExtrasList.clear(); } mServiceFwk.setSessionToken((MediaSession.Token) token.getToken()); } }); }复制代码
MediaBrowserServiceApi21
MediaBrowserServiceApi21继承了MediaBrowserService,MediaBrowserService才是真正干活的,完成媒体服务的功能。
MediaBrowserService
看onCreate方法,它主要有ServiceBinder来工作,
@Override public void onCreate() { super.onCreate(); mBinder = new ServiceBinder(); } @Override public IBinder onBind(Intent intent) { if (SERVICE_INTERFACE.equals(intent.getAction())) { return mBinder; } return null; }复制代码
看onBind,判断了aciton,也就是manifest注册service时需要加入的action
<service android:name="com.ttjjttjj.mymediasession.shared.MyMusicService" android:exported="true"> <intent-filter> <action android:name="android.media.browse.MediaBrowserService" /> </intent-filter> </service>复制代码
ServiceBinder实现了IMediaBrowserService的aidl接口
private class ServiceBinder extends IMediaBrowserService.Stub { }复制代码
IMediaBrowserService接口
16oneway interface IMediaBrowserService { 17 void connect(String pkg, in Bundle rootHints, IMediaBrowserServiceCallbacks callbacks); 18 void disconnect(IMediaBrowserServiceCallbacks callbacks); 19 20 void addSubscriptionDeprecated(String uri, IMediaBrowserServiceCallbacks callbacks); 21 void removeSubscriptionDeprecated(String uri, IMediaBrowserServiceCallbacks callbacks); 22 23 void getMediaItem(String uri, in ResultReceiver cb, IMediaBrowserServiceCallbacks callbacks); 24 void addSubscription(String uri, in IBinder token, in Bundle options, 25 IMediaBrowserServiceCallbacks callbacks); 26 void removeSubscription(String uri, in IBinder token, IMediaBrowserServiceCallbacks callbacks); 27}复制代码
其中主要方法是由MediaBrowserCompat来请求通信的,service的具体内容先不看,来看一下客户端浏览器的连接。
2:MediaBrowserCompat 客户端浏览器
看下图就是MediaBrowserCompat的创建和连接,还绑定服务,设置连接回调
MediaBrowser.connect 媒体浏览器连接
正在干活的是MediaBrowser.class, 可以看到其中启动和绑定了服务MediaBrowserService
MediaBrowserService.connect() 媒体服务触发连接
connect()调用后,就到了MediaBrowserService中的connect方法
private class ServiceBinder extends IMediaBrowserService.Stub { @Override public void connect(final String pkg, final Bundle rootHints, final IMediaBrowserServiceCallbacks callbacks) { mHandler.post(new Runnable() { @Override public void run() { final IBinder b = callbacks.asBinder(); // Clear out the old subscriptions. We are getting new ones. mConnections.remove(b); final ConnectionRecord connection = new ConnectionRecord(); connection.pkg = pkg; connection.pid = pid; connection.uid = uid; connection.rootHints = rootHints; connection.callbacks = callbacks; mCurConnection = connection; connection.root = MediaBrowserService.this.onGetRoot(pkg, uid, rootHints); mCurConnection = null; // If they didn't return something, don't allow this client. if (connection.root == null) { Log.i(TAG, "No root for client " + pkg + " from service " + getClass().getName()); try { callbacks.onConnectFailed(); } catch (RemoteException ex) { Log.w(TAG, "Calling onConnectFailed() failed. Ignoring. " + "pkg=" + pkg); } } else { try { mConnections.put(b, connection); b.linkToDeath(connection, 0); if (mSession != null) { callbacks.onConnect(connection.root.getRootId(), mSession, connection.root.getExtras()); } } catch (RemoteException ex) { Log.w(TAG, "Calling onConnect() failed. Dropping client. " + "pkg=" + pkg); mConnections.remove(b); } } } }); } }复制代码
方法中主要是创建记录ConnectionRecord,记录MediaBrowserCompat的信息,调用了onGetRoot生成root,,然后回调给 MediaBrowserCompat,root是MediaBrowserCompat和MediaBrowserSrevice连接成功的信物,如果root为null就表示连接失败。
回调MediaBrowserCompat.ConnectionCallback()
这样就到了MediaBrowserCompat的回调接口connect中,
看到返回了服务端的root,然后进行订阅,以及订阅回调,其中可以返回初始的数据给controller
然后创建MediaControllerCompat,可以看到参数中,传入了mMediaBrowserCompat.sessionToken
MediaBrowserCompat返回的seesionToken,是连接上MediaBrowserServiceCompat,然后MediaBrowserServiceCompat中创建了MediaSession,返回给了客户端的控制器MediaControllerCompat,sessionToken是MediaController和MediaSession关联在一起的。然后看看MediaSession。
3:MediaSessionCompat 媒体会话
下图可以看到,MediaSession是在MediaBrowserServiceCompat中创建,然后传出token给service,(上面MediaBrowserCompat的token就是service返回的,然后MediaController通过token和MediaSeesion连接,如果是视频应用token就不需要service返回了,直接传给MediaController就可以了)。
另外设置了MediaSession的回调接口,它触发是由MediaController的请求触发的。
MediaSessionCompat
进入MediaSessionCompat -》MediaSessionImplApi28-》MediaSessionImplApi21,发现实现的是 MediaSeesion和Token的对象。
MediaSession
MediaSessionManager 管理,创建Session的对象, 参数mCbSub是CallBack
MediaSessionManager#MediaSessionService
MediaSessionManager 的实现是 MediaSessionService 的系统服务
/** * System implementation of MediaSessionManager */ public class MediaSessionService extends SystemService implements Monitor {复制代码
看onCreate, SessionManagerImpl具体
public MediaSessionService(Context context) { super(context); mContext = context; mSessionManagerImpl = new SessionManagerImpl(); PowerManager pm = (PowerManager) context.getSystemService(Context.POWER_SERVICE); mMediaEventWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "handleMediaEvent"); mNotificationManager = INotificationManager.Stub.asInterface( ServiceManager.getService(Context.NOTIFICATION_SERVICE)); }复制代码
SessionManagerImpl
#createSession#createSessionInternal#MediaSessionRecord
SessionManagerImpl中创建Session,以及创建了MediaSessionRecord
MediaSessionRecord
MediaSessionRecord中创建了ControllerStub,它是ISessionController的具体实现,完成MediaController的控制
MediaessionRecord中创建了SessionStub,它胡处理控制传来的操作,callback给之前的参数mCbSub(CallbackStub)
CallbackStub
CallbackStub中通过MediaSession分发之前的操作,
@Override public void onPlay(String packageName, int pid, int uid) { MediaSession session = mMediaSession.get(); if (session != null) { session.dispatchPlay(createRemoteUserInfo(packageName, pid, uid)); } }复制代码
MediaSession#dispatchPlay#postToCallback#postToCallbackDelayed#mCallback
void dispatchPlay(RemoteUserInfo caller) { postToCallback(caller, CallbackMessageHandler.MSG_PLAY, null, null); }复制代码
mCallback#CallbackMessageHandler.post 这里就是handler发送消息
void postToCallbackDelayed(RemoteUserInfo caller, int what, Object obj, Bundle data, long delay) { synchronized (mLock) { if (mCallback != null) { mCallback.post(caller, what, obj, data, delay); } } }复制代码
#CallbackMessageHandler.post处理消息,通过mCallback来回调,就是一开始MediaSessio设置的回调MediaSession.Callback。
ISession
mBinder为ISession的对象,可以看到源码,实现MediaSessionRecord#SessionStub
上面的接口,可以看到熟悉的,用于回调给mediaController。
void setMetadata(in MediaMetadata metadata); void setPlaybackState(in PlaybackState state);复制代码
Token
创建的Token中传入了ISessionController,对应的是ISessionController
public static final class Token implements Parcelable { private final int mUid; private final ISessionController mBinder; /** * @hide */ public Token(int uid, ISessionController binder) { mUid = uid; mBinder = binder; } ... ... }复制代码
ISessionController
其中包含了熟悉的控制器的paly,stop等操作, 实现是MediaSessionRecord#ControllerStub
而这个token真是给客户端的MediaControllerCompat来操作的,下面看MediaControllerCompat。
4:MediaControllerCompat 创建客户端控制器
MediaControllerCompat创建
回调MediaControllerCompat.Callback()
其中的onPlaybackStateChanged,和onMetadataChanged, 会在上面的ISession调用setPlaybackState和setMetadata会触发他们。
mSession.setPlaybackState(mPlaybackStateCompat) mSession.setMetadata(LocalDataHelper.transformPlayBeanByDuration(getPlayBean(), mMediaPlayer.duration.toLong()))复制代码
transportControls执行操作
当UI上点击播放,暂停,下一个等操作是,直接通过MediaControllerCompat.transportControls的接口,看一下怎麽实现的。
/** * MediaControllerCompat 操作UI按钮,执行pause, play等 */ private fun handlerPlayEvent() { when (viewModel.mMediaControllerCompat.playbackState.state) { PlaybackStateCompat.STATE_PLAYING -> { "onClick pause".logd() viewModel.mMediaControllerCompat.transportControls.pause() } PlaybackStateCompat.STATE_PAUSED -> { "onClick play".logd() viewModel.mMediaControllerCompat.transportControls.play() } else -> { "onClick other".logd() viewModel.mMediaControllerCompat.transportControls.playFromSearch("", null) } } }复制代码
TransportControls
看到其中的实现是mSessionBinder对象,对应的是ISessionController,就是Token中持有的,所以MediaController操作就调用了ISessionController的接口,然后看上面MediaSession创建了过程中一步一步到回调中MediaSession.Callback(), 这样就完成从UI到MediaController到MediaSession到CallBack的过程。
private final ISessionController mSessionBinder; public final class TransportControls { private static final String TAG = "TransportController"; private TransportControls() { } ... ... /** * Request that the player start its playback at its current position. */ public void play() { try { mSessionBinder.play(mContext.getPackageName()); } catch (RemoteException e) { Log.wtf(TAG, "Error calling play.", e); } } public void playFromUri(Uri uri, Bundle extras) { if (uri == null || Uri.EMPTY.equals(uri)) { throw new IllegalArgumentException( "You must specify a non-empty Uri for playFromUri."); } try { mSessionBinder.playFromUri(mContext.getPackageName(), uri, extras); } catch (RemoteException e) { Log.wtf(TAG, "Error calling play(" + uri + ").", e); } } /** * Request that the player pause its playback and stay at its current * position. */ public void pause() { try { mSessionBinder.pause(mContext.getPackageName()); } catch (RemoteException e) { Log.wtf(TAG, "Error calling pause.", e); } } public void stop() { try { mSessionBinder.stop(mContext.getPackageName()); } catch (RemoteException e) { Log.wtf(TAG, "Error calling stop.", e); } } public void seekTo(long pos) { try { mSessionBinder.seekTo(mContext.getPackageName(), pos); } catch (RemoteException e) { Log.wtf(TAG, "Error calling seekTo.", e); } } /** * Start fast forwarding. If playback is already fast forwarding this * may increase the rate. */ public void fastForward() { try { mSessionBinder.fastForward(mContext.getPackageName()); } catch (RemoteException e) { Log.wtf(TAG, "Error calling fastForward.", e); } } /** * Skip to the next item. */ public void skipToNext() { try { mSessionBinder.next(mContext.getPackageName()); } catch (RemoteException e) { Log.wtf(TAG, "Error calling next.", e); } } ... ... }复制代码
到这里音频应用的框架源码基本走了一遍,由于时间和知识有限,欢迎大家指正和留言。
作者:大力水手666
链接:https://juejin.cn/post/7018837198455717919