Android Car音乐播放器分析
概述
Android Car系统音乐路径是packages/apps/Car/Media,应用名称是CarMediaApp,包名是com.android.car.media。系统音乐的数据来自同目录的LocalMediaPlayer,应用名称是LocalMediaPlayer,包名是com.android.car.media.localmediaplayer。这两个应用共用了sharedUserId,这样可以互相访问数据了。注意共用sharedUserId,必需签名也要相同,否则应用无法安装。
MediaSession框架
MediaBrowser
媒体浏览器,用来连接MediaBrowserService和订阅数据,通过它的回调接口我们可以获取和Service的连接状态以及获取在Service中异步获取的音乐库数据。媒体浏览器一般创建于客户端。
MediaBrowserService
浏览器服务,提供onGetRoot(控制客户端媒体浏览器的连接请求,通过返回值决定是否允许该客户端连接服务)和onLoadChildren(媒体浏览器向Service发送数据订阅时调用,一般在这执行异步获取数据的操作,最后将数据发送至媒体浏览器的回调接口中)这两个抽象方法 。客户端通过MediaBrowser.subscribe()方法发起数据请求,每次subscribe都会调用到onLoadChildren。
MediaSession
媒体会话,即受控端,通过设置MediaSessionCompat.Callback回调来接收媒体控制器MediaController发送的指令,当收到指令时会触发Callback中各个指令对应的回调方法(回调方法中会执行播放器相应的操作,如播放、暂停等)。Session一般在Service.onCreate方法中创建,最后需调用setSessionToken方法设置用于和控制器配对的令牌并通知浏览器连接服务成功。一个seesion可以接收来自一个或多个媒体播放器的callback。这使得通过其它设备来控制成为可能。
Media controller
我们的UI只是和Media controller交互,而不是Player 本身,Media controller会将一些控制信息传递给Media Session,它也会在seesion发生变化的时候,得到来自session的回调,一个media controller一次只可以连接一个session。当使用一个media contoller和Session的时候,我们可以在运行期部署多个播放器,在其执行的时候根据设备去修改app的外观。
本地音乐播放器
LocalMediaPlayer服务端
LocalMediaPlayer主要功能是从数据库获取音乐数据及一个后台播放器Player。获取音乐是在LocalMediaBrowserService中实现。LocalMediaBrowserService又继承MediaBrowserService,这个目录下还有IMediaBrowserService.aidl,IMediaBrowserServiceCallbacks.aidl两个文件。IMediaBrowserService.aidl中定义了getMediaItem方法,这个方法会在MediaBrowserService中重写,作为binder服务端实现,在MediaBrowser中调用 。MediaBrowserService作为一个播放后台服务,是通过MediaSession实现的,在使用过程中MediaSession会与该服务及音乐客户端关联,所有的播放操作都是由MediaSession实现的。
Media客户端
MediaActivity
音乐客户端的主Activity是MediaActivity,这个类继承自CarDrawerActivity,我们只看MediaActivity。在其onCreate()方法中创建了MediaDrawerController。MediaDrawerController实现了MediaItemOnClickListener接口的onMediaItemClicked(MediaBrowser.MediaItem mediaItem)方法,我们在播放列表点击某个列表就会走到这里来,参数是MediaBrowser.MediaItem,根据它可以获取到音乐的标题、ID等信息,但是获取IconUri为null,获取音乐图片的信息我们稍后再讲。在这里,先调用MediaController的pause方法,暂停上个音乐的播放,然后调用MediaController的playFromMediaId方法,播放当前点击的音乐。MediaController内部是通过binder和服务端通讯的,最终还是通过MediaSession控制音乐的状态。
MediaPlaybackFragment
在MediaActivity中会创建MediaPlaybackFragment,这个Fragment作为主界面显示。在MediaPlaybackFragment的onCreate()方法中会创建MediaPlaybackModel类。然后调用其onStart()方法。
MediaManager
音乐客户端封装了MediaManager类,客户端和服务端的连接和状态控制都是在该类中处理。MediaManager类中的一些回调。MediaBrowser.ConnectionCallback。客户端向服务端发起连接的回调。包括连接成功onConnected(),连接挂起onConnectionSuspended(),连接失败onConnectionFailed()方法。当收到连接成功回调后,会通过MediaSession生成MediaController,然后MediaController注册回调mController.registerCallback(mMediaControllerCallback)。MediaController的回调方法主要包括状态改变回调onPlaybackStateChanged
(),会话销毁回调onSessionDestroyed()。音乐客户端的所有状态控制都在onPlaybackStateChanged方法中处理。
获取音乐数据的流程
服务连接
起始是由媒体浏览客户端来发起媒体数据的获取请求,即MediaBrowser客户端。MediaBrowser首先要连接上媒体服务。
接着分析MediaActivity,在其onCreate()方法中,还会调用MediaManager的addListener()方法:MediaManager.getInstance(this).addListener(mListener),整个流程由此启动。在MediaActivity中还会调用到MediaManager的setMediaClientComponent方法,潘多拉的魔盒由此打开。在setMediaClientComponent方法中创建和服务端的连接mBrowser = new MediaBrowser(mContext, component, mMediaBrowserConnectionCallback, null);其中mMediaBrowserConnectionCallback就是上文提到的MediaBrowser.ConnectionCallback接口。至此音乐客户端已经和服务端连接上了,接下来就是如何获取音乐数据了。
获取数据
接下来分析入口在哪里。从界面可以看到,主界面显示几个文件夹,比如Folders,Albums,Artists,Genres...这些数据是从LocalMediaBrowserService类中创建的,在其onLoadChildren回调中,当传入的parentId是ROOT_ID时,就返回这些文件夹列表。从这里我们知道,音乐数据并不是一下子全部返回的,而是根据parentId,返回其下相应的数据。无论是文件夹还是音乐数据,都是通过MediaItem集合的方式返回。通过前面我们知道每当客户端调用subscribe方法的时候,服务端会回调onLoadChildren方法。那第一个subscribe方法是在什么地方调用的呢?这里直接给出答案。
流程顺序图
MediaPlaybackFragment.onResume()->MediaPlaybackModel.start()->MediaManager.addListener()添加了onMediaAppChanged监听;MediaActivity.onResumeFragments()->MediaManager.setMediaClientComponent()->调用所有的onMediaAppChanged回调;
MediaPlaybackModel.mMediaManagerListener.onMediaAppChanged()->new MediaBrowser(mContext, name, mConnectionCallback, mBrowserExtras).connect()->MediaPlaybackModel.mConnectionCallback.onConnected()->notifyListeners(onMediaConnected)->MediaDrawerController.mModelListener.onMediaConnected)()->MediaDrawerAdapter.setFetcher(createRootMediaItemsFetcher());
MediaDrawerController.createRootMediaItemsFetcher(getMediaBrowser().getRoot())[在这里调用了MediaBrowser的getRoot方法,就是上面提到的LocalMediaBrowserService的ROOT_ID字段]->MediaDrawerController.createMediaBrowserItemFetcher();
继续看MediaDrawerAdapter.setFetcher()方法
->MediaItemsFetcher.start()->getMediaBrowser().subscribe(mMediaId),这里的mMediaId就是ROOT_ID。到此客户端调用subscribe方法获取数据,服务端回调onLoadChildren方法返回数据的流程就清楚了。