MediaSession框架源码分析

这篇是MediaSession框架使用、源码分析以及实战第二篇。文章目录前言源码分析客户端MediaBrowser分析MediaBrowserService分析服务端前言主要从源码的角度来说一下MediaSession框架。在分析源码之前一定要熟悉MediaSession框架的使用,这样就可以直接看源码进行分析。首先,我们在第一篇说到,MediaSession框架由客户端+中间framework+服务端组成。App开发可以由客户端+服务端组成就可以。客户端和服务端代码使用方式大概如下所示:客户端:
摘要由CSDN通过智能技术生成

这篇是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回调,干了几件事。

  1. 通过IMediaBrowserService.Stub.asInterface(binder)拿到IMediaBrowserService句柄mServiceBinder。
  2. 通过getNewServiceCallbacks构造IMediaBrowserServiceCallbacks的回调ServiceCallbacks
  3. 执行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方法主要干了几件事:

  1. 判断调用的uid和传入的pkg是否能对应上。
  2. 将新构建ConnectionRecord的放入mConnections,因为是多对一的关系,所以需要维护一个Map。每个ConnectionRecord都会记录连接客户端的uid、pid、pkg、rootHints、callback等信息。
  3. 如果服务端此时存在,就会回调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
  • 2
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值