构建媒体浏览器服务
您的应用必须MediaBrowserService在其清单中声明带有intent-filter。您能够选择本身的服务名称; 在如下示例中,它是“MediaPlaybackService”。android
<service android:name=".MediaPlaybackService"> <intent-filter> <action android:name="android.media.browse.MediaBrowserService" /> </intent-filter> </service>
注意:推荐的实现MediaBrowserService 是MediaBrowserServiceCompat。这是在media-compat支持库中定义的 。在整个页面中,术语“MediaBrowserService”指的是of的一个实例MediaBrowserServiceCompat。浏览器
初始化媒体会话
当服务收到onCreate()生命周期回调方法时,它应该执行如下步骤:session
建立并初始化媒体会话
设置媒体会话回调
设置媒体会话令牌
onCreate()下面的代码演示了如下步骤:app
public class MediaPlaybackService extends MediaBrowserServiceCompat { private static final String MY_MEDIA_ROOT_ID = "media_root_id"; private static final String MY_EMPTY_MEDIA_ROOT_ID = "empty_root_id"; private MediaSessionCompat mMediaSession; private PlaybackStateCompat.Builder mStateBuilder; @Override public void onCreate() { super.onCreate(); // Create a MediaSessionCompat mMediaSession = new MediaSessionCompat(context, LOG_TAG); // Enable callbacks from MediaButtons and TransportControls mMediaSession.setFlags( MediaSessionCompat.FLAG_HANDLES_MEDIA_BUTTONS | MediaSessionCompat.FLAG_HANDLES_TRANSPORT_CONTROLS); // Set an initial PlaybackState with ACTION_PLAY, so media buttons can start the player mStateBuilder = new PlaybackStateCompat.Builder() .setActions( PlaybackStateCompat.ACTION_PLAY | PlaybackStateCompat.ACTION_PLAY_PAUSE); mMediaSession.setPlaybackState(mStateBuilder.build()); // MySessionCallback() has methods that handle callbacks from a media controller mMediaSession.setCallback(new MySessionCallback()); // Set the session's token so that client activities can communicate with it. setSessionToken(mMediaSession.getSessionToken()); } }
管理客户链接
MediaBrowserService有两种处理客户端链接的方法: onGetRoot()控制对服务的访问,并 onLoadChildren() 为客户端提供构建和显示MediaBrowserService内容层次结构菜单的能力。ide
使用控制客户端链接 onGetRoot()学习
该onGetRoot()方法返回内容层次结构的根节点。若是方法返回null,则拒绝链接。测试
要容许客户端链接到您的服务并浏览其媒体内容,onGetRoot()必须返回一个非空的BrowserRoot,它是一个表示您的内容层次结构的根ID。ui
要容许客户端在不浏览的状况下链接到MediaSession,onGetRoot()仍必须返回非null的BrowserRoot,但根ID应表示空的内容层次结构。this
典型的实现onGetRoot()可能以下所示:spa
@Override public BrowserRoot onGetRoot(String clientPackageName, int clientUid, Bundle rootHints) { // (Optional) Control the level of access for the specified package name. // You'll need to write your own logic to do this. if (allowBrowsing(clientPackageName, clientUid)) { // Returns a root ID that clients can use with onLoadChildren() to retrieve // the content hierarchy. return new BrowserRoot(MY_MEDIA_ROOT_ID, null); } else { // Clients can connect, but this BrowserRoot is an empty hierachy // so onLoadChildren returns nothing. This disables the ability to browse for content. return new BrowserRoot(MY_EMPTY_MEDIA_ROOT_ID, null); } }
在某些状况下,您可能但愿实施白/黑名单方案来控制链接。有关白名单的示例,请参阅通用Android音乐播放器示例应用程序中的PackageValidator类。
注意:您应该考虑提供不一样的内容层次结构,具体取决于进行查询的客户端类型。特别是,Android Auto会限制用户与音频应用的互动方式。有关更多信息,请参阅为自动播放音频。您能够查看clientPackageName链接时间以肯定客户端类型,并BrowserRoot根据客户端(或者rootHints 若是有)返回不一样的客户端类型。
与内容沟通内容 onLoadChildren()
在客户端链接以后,它能够经过重复调用MediaBrowserCompat.subscribe()来构建UI的本地表示来遍历内容层次结构。该subscribe()方法将回调发送onLoadChildren()到服务,该服务返回MediaBrowser.MediaItem对象列表。
每一个MediaItem都有一个惟一的ID字符串,它是一个不透明的标记。当客户想要打开子菜单或播放项目时,它会传递ID。您的服务负责将ID与相应的菜单节点或内容项相关联。
一个简单的实现onLoadChildren()可能以下所示:
@Override public void onLoadChildren(final String parentMediaId, final Result<List<MediaItem>> result) { // Browsing not allowed if (TextUtils.equals(MY_EMPTY_MEDIA_ROOT_ID, parentMediaId)) { result.sendResult(null); return; } // Assume for example that the music catalog is already loaded/cached. List<MediaItem> mediaItems = new ArrayList<>(); // Check if this is the root menu: if (MY_MEDIA_ROOT_ID.equals(parentMediaId)) { // Build the MediaItem objects for the top level, // and put them in the mediaItems list... } else { // Examine the passed parentMediaId to see which submenu we're at, // and put the children of that menu in the mediaItems list... } result.sendResult(mediaItems); }
注意:MediaItem MediaBrowserService传递的对象不该包含图标位图。使用Uri的,而不是调用 setIconUri() 在生成MediaDescription的每一个项目
有关如何实施的示例onLoadChildren(),请参阅MediaBrowserService和Universal Android Music Player示例应用程序。
媒体浏览器服务生命周期
Android 服务的行为取决于它是启动仍是绑定到一个或多个客户端。建立服务后,能够启动,绑定或同时启用它。在全部这些状态中,它功能齐全,能够执行其设计的工做。不一样之处在于服务存在多长时间。绑定的服务在其全部绑定的客户端解除绑定以前不会被销毁。能够显式中止和销毁已启动的服务(假设它再也不绑定到任何客户端)。
当MediaBrowser另外一个活动中的运行链接到a时MediaBrowserService,它会将活动绑定到服务,从而使服务绑定(但不启动)。此默认行为内置于MediaBrowserServiceCompat类中。
只有绑定(而且未启动)的服务在其全部客户端解除绑定时销毁。若是此时UI活动断开链接,则服务将被销毁。若是您尚未播听任何音乐,这不是问题。可是,当播放开始时,用户可能但愿即便在切换应用后也能继续收听。当您取消绑定UI以使用其余应用程序时,您不但愿销毁播放器。
所以,您须要确保在经过调用开始播放服务时启动该服务startService()。不管是否绑定,必须明确中止已启动的服务。这可确保即便控制UI活动解除绑定,您的播放器也会继续执行。
要中止已启动的服务,请致电Context.stopService()或stopSelf()。系统会尽快中止并销毁服务。可是,若是一个或多个客户端仍然绑定到该服务,则中止该服务的调用将延迟,直到其全部客户端解除绑定。
它的生命周期MediaBrowserService由建立方式,绑定到它的客户端数量以及从媒体会话回调接收的调用控制。总结一下:
该服务在响应媒体按钮或活动绑定到它(经过其链接后MediaBrowser)启动时建立。
媒体会话onPlay()回调应包括调用的代码startService()。这可确保服务启动并继续运行,即便MediaBrowser绑定到它的全部UI 活动都解除绑定。
该onStop()回调应该调用stopSelf()。若是服务已启动,则会中止该服务。此外,若是没有绑定的活动,服务将被销毁。不然,服务将保持绑定,直到其全部活动解除绑定。(若是startService()在销毁服务以前收到后续呼叫,则取消挂起中止。)
如下流程图演示了如何管理服务的生命周期。变量计数器跟踪绑定客户端的数量:
将MediaStyle通知与前台服务一块儿使用
当服务正在播放时,它应该在前台运行。这使系统知道服务正在执行有用的功能,若是系统内存不足,则不该该被杀死。前台服务必须显示通知,以便用户知道它并能够选择控制它。该onPlay()回调应该把服务的前景。(请注意,这是“前景”的特殊含义。虽然Android在前台考虑服务以进行流程管理,可是对于用户,播放器正在后台播放,而其余应用程序在“前景”中可见屏幕。)
当服务在前台运行时,它必须显示通知,理想状况下是一个或多个传输控件。通知还应包括会话元数据中的有用信息。
在播放器开始播放时构建并显示通知。这样作的最佳位置是MediaSessionCompat.Callback.onPlay()方法内部。
如下示例使用 NotificationCompat.MediaStyle专为媒体应用设计的。它显示了如何构建显示元数据和传输控件的通知。便捷方法 getController() 容许您直接从媒体会话建立媒体控制器。
// Given a media session and its context (usually the component containing the session) // Create a NotificationCompat.Builder // Get the session's metadata MediaControllerCompat controller = mediaSession.getController(); MediaMetadataCompat mediaMetadata = controller.getMetadata(); MediaDescriptionCompat description = mediaMetadata.getDescription(); NotificationCompat.Builder builder = new NotificationCompat.Builder(context, channelId); builder // Add the metadata for the currently playing track .setContentTitle(description.getTitle()) .setContentText(description.getSubtitle()) .setSubText(description.getDescription()) .setLargeIcon(description.getIconBitmap()) // Enable launching the player by clicking the notification .setContentIntent(controller.getSessionActivity()) // Stop the service when the notification is swiped away .setDeleteIntent(MediaButtonReceiver.buildMediaButtonPendingIntent(context, PlaybackStateCompat.ACTION_STOP)) // Make the transport controls visible on the lockscreen .setVisibility(NotificationCompat.VISIBILITY_PUBLIC) // Add an app icon and set its accent color // Be careful about the color .setSmallIcon(R.drawable.notification_icon) .setColor(ContextCompat.getColor(context, R.color.primaryDark)) // Add a pause button .addAction(new NotificationCompat.Action( R.drawable.pause, getString(R.string.pause), MediaButtonReceiver.buildMediaButtonPendingIntent(context, PlaybackStateCompat.ACTION_PLAY_PAUSE))) // Take advantage of MediaStyle features .setStyle(new MediaStyle() .setMediaSession(mediaSession.getSessionToken()) .setShowActionsInCompactView(0) // Add a cancel button .setShowCancelButton(true) .setCancelButtonIntent(MediaButtonReceiver.buildMediaButtonPendingIntent(context, PlaybackStateCompat.ACTION_STOP))); // Display the notification and place the service in the foreground startForeground(id, builder.build());
使用MediaStyle通知时,请注意这些NotificationCompat设置的行为:
用于 setMediaSession() 将通知与您的会话相关联。这容许第三方应用和配套设备访问和控制会话。
添加暂停和取消按钮时,您须要PendingIntent附加到播放操做。该方法 MediaButtonReceiver.buildMediaButtonPendingIntent() 执行将PlaybackState操做转换为PendingIntent的工做。
总结写的通常,欢迎留言、私信指出问题与不足之处!如回复不及时可加入Android技术交流群:150923287 一块儿学习探讨Android开发技术!