構建媒體瀏覽器服務
您的應用必須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開發技術!