这篇是MediaSession框架使用、源码分析以及实战第二篇。
前言
主要从源码的角度来说一下MediaSession框架。在分析源码之前一定要熟悉MediaSession框架的使用,这样就可以直接看源码进行分析。
首先,我们在第一篇说到,MediaSession框架由客户端+中间framework+服务端组成。App开发可以由客户端+服务端组成就可以。客户端和服务端代码使用方式大概如下所示:
客户端:
public class MainActivity extends AppCompatActivity {
...
//媒体浏览器
private MediaBrowser mMediaBrowser;
//媒体控制器
private MediaController mMediaController;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//新建MediaBrowser,第一个参数是context
//第二个参数是CompoentName,有多种构造方法,指向要连接的服务
//第三个参数是连接结果的回调connectionCallback,第四个参数为Bundle
mMediaBrowser = new MediaBrowser(this,
new ComponentName(this, MediaService.class), connectionCallback, null);
mMediaBrowser.connect();
...
}
//连接结果的回调
private MediaBrowser.ConnectionCallback connectionCallback
= new MediaBrowser.ConnectionCallback() {
public void onConnected() {
//如果服务端接受连接,就会调此方法表示连接成功,否则回调onConnectionFailed();
Log.d(TAG, "onConnected: ");
//获取配对令牌
MediaSession.Token token = mMediaBrowser.getSessionToken();
//通过token,获取MediaController,第一个参数是context,第二个参数为token
mMediaController = new MediaController(getBaseContext(), token);
//mediaController注册回调,callback就是媒体信息改变后,服务给客户端的回调
mMediaController.registerCallback(mMediaCallBack);
}
public void onConnectionSuspended() {
//与服务断开回调(可选)
}
public void onConnectionFailed() {
//连接失败回调(可选)
}
}
//服务对客户端的信息回调。
private MediaController.Callback mMediaCallBack = new MediaController.Callback() {
@Override
public void onMetadataChanged(MediaMetadata metadata) {
}
@Override
public void onPlaybackStateChanged(PlaybackState state) {
}
@Override
public void onQueueChanged(List<MediaSession.QueueItem> queue) {
}
@Override
public void onSessionEvent(String event, Bundle extras) {
}
@Override
public void onExtrasChanged(Bundle extras) {
}
}
}
源码分析
客户端
MediaBrowser分析
我们先从客户端进行分析,首先客户端构造一个MediaBrowser,然后进行 mMediaBrowser.connect(),等待连接结果的回调就可以了。
- MediaBrowser在第一篇说过,就是媒体浏览器。我们看一下它的构造方法。
主要就是包含一个Callback回调(用于获取连接信息,拿到控制句柄,以及注册mediaControl回调),一个媒体浏览服务的组件(用于bind媒体浏览服务),一个检索根ID(用于浏览访问)。
/**
* Creates a media browser for the specified media browser service.
*
* @param context The context.
* @param serviceComponent The component name of the media browser service.
* @param callback The connection callback.
* 可选的特定于服务的参数捆绑发送,在连接和检索根ID时访问媒体浏览器服务用于浏览,如果没有则为
* null。该捆绑包的内容可能会影响浏览时返回的信息,参考BrowserRoot#EXTRA_RECENT(最近)、
* BrowserRoot#EXTRA_OFFLINE(本地)、BrowserRoot#EXTRA_SUGGESTED(推荐)
* @param rootHints An optional bundle of service-specific arguments to send
* to the media browser service when connecting and retrieving the root id
* for browsing, or null if none. The contents of this bundle may affect
* the information returned when browsing.
* @see android.service.media.MediaBrowserService.BrowserRoot#EXTRA_RECENT
* @see android.service.media.MediaBrowserService.BrowserRoot#EXTRA_OFFLINE
* @see android.service.media.MediaBrowserService.BrowserRoot#EXTRA_SUGGESTED
*/
public MediaBrowser(Context context, ComponentName serviceComponent,
ConnectionCallback callback, Bundle rootHints) {
...
mContext = context;
mServiceComponent = serviceComponent;
mCallback = callback;
mRootHints = rootHints == null ? null : new Bundle(rootHints);
}
- mMediaBrowser.connect()分析
我们看到起始就是调用bindService绑定我们传进来的组件服务,bindService大家都比较熟了,就不说了。如果绑定失败,就走没绑定成功的逻辑。
public void connect() {
...
mState = CONNECT_STATE_CONNECTING;
mHandler.post(new Runnable() {
@Override
public void run() {
...
//SERVICE_INTERFACE = "android.media.browse.MediaBrowserService";
final Intent intent = new Intent(MediaBrowserService.SERVICE_INTERFACE);
intent.setComponent(mServiceComponent);
mServiceConnection = new MediaServiceConnection();
boolean bound = false;
//就是绑定我们传进来的组件服务。
try {
bound = mContext.bindService(intent, mServiceConnection,
Context.BIND_AUTO_CREATE);
}
...
}
});
}
bindService后肯定会有回调,我们看一下回调代码。绑定成功会有onServiceConnected回调,干了几件事。
- 通过IMediaBrowserService.Stub.asInterface(binder)拿到IMediaBrowserService句柄mServiceBinder。
- 通过getNewServiceCallbacks构造IMediaBrowserServiceCallbacks的回调ServiceCallbacks
- 执行mServiceBinder.connect(mContext.getPackageName(), mRootHints,
mServiceCallbacks);此方法是异步的。
private class MediaServiceConnection implements ServiceConnection {
@Override
public void onServiceConnected(final ComponentName name, final IBinder binder) {
postOrRun(new Runnable() {
@Override
public void run() {
mServiceBinder = IMediaBrowserService.Stub.asInterface(binder);
// We make a new mServiceCallbacks each time we connect so that we can drop
// responses from previous connections.
mServiceCallbacks = getNewServiceCallbacks();
mState = CONNECT_STATE_CONNECTING;
try {
mServiceBinder.connect(mContext.getPackageName(), mRootHints,
mServiceCallbacks);
} catch (RemoteException ex) {
...
}
}
});
}
...
}
}
private ServiceCallbacks getNewServiceCallbacks() {
return new ServiceCallbacks(this);
}
private static class ServiceCallbacks extends IMediaBrowserServiceCallbacks.Stub {
private WeakReference<MediaBrowser> mMediaBrowser;
public ServiceCallbacks(MediaBrowser mediaBrowser) {
mMediaBrowser = new WeakReference<MediaBrowser>(mediaBrowser);
}
...
}
分析代码我们发现ServiceBinder是MediaBrowserService类的内部类,继承了IMediaBrowserService.Stub。
下面就要进入MediaBrowserService$ServiceBinder类分析connect方法。由于MediaBrowserService类没有构造函数,我们暂时不需要分析。
MediaBrowserService分析
首先看代码,ServiceBinder的connect方法主要干了几件事:
- 判断调用的uid和传入的pkg是否能对应上。
- 将新构建ConnectionRecord的放入mConnections,因为是多对一的关系,所以需要维护一个Map。每个ConnectionRecord都会记录连接客户端的uid、pid、pkg、rootHints、callback等信息。
- 如果服务端此时存在,就会回调callbacks.onConnect(connection.root.getRootId(), mSession, connection.root.getExtras()),其中callbacks就是上面说的getNewServiceCallbacks方法获取的ServiceCallbacks。
//MediaBrowserService.java
private class ServiceBinder extends IMediaBrowserService.Stub {
@Override
public void connect(final String pkg, final Bundle rootHints,
final IMediaBrowserServiceCallbacks callbacks) {
final int pid = Binder.getCallingPid();
final int uid = Binder.getCallingUid();
if (!isValidPackage(pkg, uid)) {
throw new IllegalArgumentException("Package/uid mismatch: uid=" + uid
+ " package=" + pkg);
}
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;
//在这里会调用用户实现的onGetRoot接口,这就是为什么用户要根据客户端传入的rootHints去返回不同的值
connection.root = MediaBrowserService.this.onGetRoot(pkg, uid, rootHints);
mCurConnection = null;
// If they didn't return something, don't allow this client.服务端没实现的话直接报failed
if (connection.root == null) {
Log.i(TAG, "No root for client " + pkg + " from service "
+ getClass().getName