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