构建媒体浏览器服务
您的应用必须MediaBrowserService在其清单中声明带有intent-filter。您可以选择自己的服务名称; 在以下示例中,它是“MediaPlaybackService”。


<service android:name=".MediaPlaybackService">
  <intent-filter>
    <action android:name="android.media.browse.MediaBrowserService" />
  </intent-filter>
</service>

注意:推荐的实现MediaBrowserService 是MediaBrowserServiceCompat。这是在media-compat支持库中定义的 。在整个页面中,术语“MediaBrowserService”指的是of的一个实例MediaBrowserServiceCompat。

初始化媒体会话
当服务收到onCreate()生命周期回调方法时,它应该执行以下步骤:

创建并初始化媒体会话
设置媒体会话回调
设置媒体会话令牌
onCreate()下面的代码演示了以下步骤:


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内容层次结构菜单的能力。

使用控制客户端连接 onGetRoot()

该onGetRoot()方法返回内容层次结构的根节点。如果方法返回null,则拒绝连接。

要允许客户端连接到您的服务并浏览其媒体内容,onGetRoot()必须返回一个非空的BrowserRoot,它是一个表示您的内容层次结构的根ID。

要允许客户端在不浏览的情况下连接到MediaSession,onGetRoot()仍必须返回非null的BrowserRoot,但根ID应表示空的内容层次结构。

典型的实现onGetRoot()可能如下所示:

@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设置的行为:

  • 使用时 setContentIntent(),您的服务会在单击通知时自动启动,这是一个方便的功能。
  • 在像锁屏这样的“不受信任”情况下,通知内容的默认可见性是 VISIBILITY_PRIVATE 。您可能希望在锁屏上看到传输控件,这样 VISIBILITY_PUBLIC 就可以了。
  • 设置背景颜色时要小心。在Android 5.0或更高版本的普通通知中,颜色仅应用于小应用程序图标的背景。但对于Android 7.0之前的MediaStyle通知,颜色用于整个通知背景。测试你的背景颜色。温柔的眼睛,避免极其明亮或荧光的颜色。

用于 setMediaSession() 将通知与您的会话相关联。这允许第三方应用和配套设备访问和控制会话。

  • 用于 setMediaSession() 将通知与您的会话相关联。这允许第三方应用和配套设备访问和控制会话。
  • 用于 setShowActionsInCompactView() 在通知的标准大小的contentView中添加最多3个操作。(此处指定了暂停按钮。)
  • 在Android 5.0(API级别21)及更高版本中,一旦服务不再在前台运行,您可以滑动通知以停止播放器。您不能在早期版本中执行此操作。要允许用户在Android 5.0(API级别21)之前删除通知并停止播放,您可以通过调用 setShowCancelButton(true) 和在通知的右上角添加取消按钮 setCancelButtonIntent() 。

添加暂停和取消按钮时,您需要PendingIntent附加到播放操作。该方法 MediaButtonReceiver.buildMediaButtonPendingIntent() 执行将PlaybackState操作转换为PendingIntent的工作。

总结
写的一般,欢迎留言、私信指出问题与不足之处!如回复不及时可加入Android技术交流群:150923287 一起学习探讨Android开发技术!


疯狂的程序员丶
435 声望22 粉丝