天天看點

media and camera 架構之一: media playback

Android多媒體架構包括支援播放多種通用的媒體類型,是以開發者可以很容易地整合音頻、視訊和圖檔到你的應用中。來自你的應用程式資源(如raw檔案夾中資源)、android檔案系統中獨立檔案或者來自基于網絡連接配接資料流的音頻和視訊檔案,都可以通過調用MediaPlayer接口播放。

這篇文檔向你展示了如何開發一個具有良好的系統性能和良好的使用者體驗媒體播放類應用。

注意:你隻能在标準的媒體輸出裝置上播放音頻資料。一般來說,這些裝置是移動裝置的揚聲器或者藍牙耳機。當在打電話會話狀态時,不可以播放音頻檔案。

基本要素:

在android架構中以下兩個類被用于播放音頻和視訊:

MediaPlayer: 該類包含播放音頻和視訊的主要的接口。

AudioManager:該類管理裝置上的音頻來源和音頻輸出。

manifest聲明:

在開始在應用程式上開發媒體播放之前,務必確定你的manifest包含合适的相關特性的使用者許可聲明:

internet許可:如果你使用MediaPlayer播放基于網絡的資料流,你的應用程式必須請求連接配接網絡的權限。

<uses-permission android:name = "android.permission.INTERNET"/>
           

wake lock許可:如果你的播放類應用需要阻止螢幕變暗或者在休眠中保持運作,或者需要使用MediaPlayer.setScreenOnWhilePlaying()或MediaPlayer.setWakeMode()這些方法,你必須請求下面的許可:

<uses-permission android:name ="android.permission.WAKE_LOCK"/>
           

使用MediaPlayer:

媒體架構的其中一個重要元件就是MediaPlayer類.該類的一個執行個體對象以最簡單的設定就可以擷取、解碼和播放音頻和媒體資源。它還支援幾種不同的媒體資源,比如:

本地資源

内部的URIs 比如從content resolver裡擷取的一個uri

外部URIs(如stream)

android支援的媒體格式類型可以參考 Android Supported Media Formats文檔。

下面是一個示例,展示播放作為一個本地raw資源的音頻檔案(即儲存在你的應用中的"res/raw"目錄中):

MediaPlayer mediaPlayer = MediaPlayer.create(context, R.raw.sound_file_1);
mediaPlayer.start(); // no need to call prepare(); create() does that for you
           
在這個例子中,raw資源是一個檔案,系統不會嘗試使用特殊方法解析的它。 但是,該資源的内容不能是原始的音頻資料。它必須是一種系統支援的經過合适編碼和格式化的媒體檔案。 下面展示如何播放來自系統本地可用的URI資源(舉例如從content resolver擷取資源):
Uri myUri = ....; // initialize Uri here
MediaPlayer mediaPlayer = new MediaPlayer();
mediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
mediaPlayer.setDataSource(getApplicationContext(), myUri);
mediaPlayer.prepare();
mediaPlayer.start();
           
通過http流播放來自遠端的URL資源例子如下:
Uri myUri = ....; // initialize Uri here
MediaPlayer mediaPlayer = new MediaPlayer();
mediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
mediaPlayer.setDataSource(getApplicationContext(), myUri);
mediaPlayer.prepare();
mediaPlayer.start();
           
通過http流播放來自遠端的URL資源例子如下:



            
String url = "http://........"; // your URL here
MediaPlayer mediaPlayer = new MediaPlayer();
mediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
mediaPlayer.setDataSource(url);
mediaPlayer.prepare(); // might take long! (for buffering, etc)
mediaPlayer.start();
           
注意:如果你傳遞一個線上的媒體檔案URL資料流,那麼該檔案必須能夠漸進式下載下傳。 警告:當你使用setDataSource()方法的時候必須捕獲或者傳遞 IllegalArgumentException 和 IOException異常,因為你應用的檔案可以不存在。 異步preparation: 原理上可以直接使用MediaPlayer。但是,記住對于典型的android 應用來說有幾個方法需要正确集合在一起使用這一點非常重要。比如,調用prepare()就需要在一段時間後才能執行,因為它可能包含擷取和解碼媒體資料。是以,與任何其他方法一樣,它需要一段時間來執行,你不能從應用程式的主線程中調用它。如果那樣做了會導緻主線程挂起直到它執行完畢,那将會是非常糟糕的使用者體驗,而且可能會導緻ANR錯誤。即使預期你的資源加載非常快,但記住任何在主線程中響應時間超過十分之一毫秒的事情都會産生明顯的停頓,以至于會給使用者産生一種你的應用運作很慢的印象。 為了避免主線程被挂起,需要建立新的線程直線MediaPlayer的prepare并在該線程結束時通知主線程。而且,當你自己去寫線程邏輯時,你會發現 通過使用架構提供的repareAsyn()這個友善的方法去完成該線程任務是一種如此常見的使用MediPlayer的模式。該方法在背景開始準備媒體,結束時立即傳回。當媒體準備完畢,通過調用setOnPreparedListerner()方法來配置MediaPlayer.OnPreparedListerner的onPrepared()方法。 狀态管理: MediaPlayer的另一面你需要記住的是它是基于狀态的。也就是說,MediaPlayer包含一個内部狀态,在你寫代碼的時候必須意識到這點。因為一些操作隻在一些特殊的狀态才有效。如果在錯誤的狀态執行了一個操作,系統可能會抛出異常或者導緻一些意想不到的行為。 在MediaPlayer類中的文檔展示了一張完整的狀态圖,它闡明了哪些方法可以從一個狀态轉移到另外一個狀态。比如,當你建立一個新的MediaPlayer對象,它是在IDLE狀态。在那個狀态,你可以通過調用setDataSource()來初始化該對象,進而使對象變為初始化狀态。初始化之後,必須通過調用prepare()方法或者prepareAsync()方法來準備該對象。當MediaPlayer準備完畢之後,該對象将進入到Prepared狀态,那就意味着你可以調用start()方法來播放媒體了。在那時,如表中舉例說明,你可以通過來回調用start()、pause()、seekTo()方法及其他方法,實作在Started,Paused和PlaybackCompleted狀态之間切換。當你調用stop()方法時,請注意你不能在調用start()方法直到你再次準備好MediaPlayer。 當你在寫MediaPlayer對象的互動代碼時要一直把該狀态圖記在心裡,因為在一些不該調用該方法的狀态中調用了該方法是引起bugs的常見原因。 釋放MediaPlayer: 一個MedaiPlayer對象是非常消耗珍貴的系統資源的。是以,你需要而外注意確定沒有挂起不再需要的MediaPlayer對象。當确定這樣做時,你應該經常調用release()方法確定系統配置設定給它的資源完全地釋放。例如,當你正在使用MediaPlayer而你的活動頁面收到調用stop()的方法,你必須要釋放MediaPlayer,因為當你的活動頁面不再和使用者互動時(除非你在背景播放媒體,這中情況将在下面一部分講述),繼續保留該媒體對象沒有一點意義。當你的活動頁面恢複或者重新開始時,你需要建立一個新的MediaPlayer且在繼續播放之前再次準備好媒體。 下面代碼展示了如何釋放和登出你的MediaPlayer對象:
mediaPlayer.release();
mediaPlayer=null;
           
作為一個示例,它考慮到了當活動頁面再次開始建立了一個新對象但在活動頁面停止時你忘記釋放該對象可能會導緻的一些問題。也許你可能知道,當使用者切換螢幕方向(或者通過另外的方式改變裝置設定)時,系統是通過重新啟動活動頁面(通常做法)來處理這種變化,是以當使用者在裝置前後的橫向與縱向來回切換時,将可能會很快消耗完系統資源,因為在每一次方向變化時,你都建立了新的MediaPlayer對象且都沒有被釋放(關于更多運作時重新重新開機的資訊請檢視Handling Runtime Changes)。 你可能會疑惑如果你還想繼續播放"背景媒體"即使使用者離開了活動頁面會怎麼樣呢,嵌入的音樂類應用的運轉就類似這種方式。在這種情況下,你需要通過Service來控制MediaPlayer對象,正如下面将要論述的通過Service方式使用MediaPlayer. 通過Service方式使用MediaPlayer: 如果你想讓你的媒體在背景播放甚至當你的應用程式沒有在打開在螢幕上----也就是,你想它繼續播放它,當使用者在與其他應用互動時,那麼你需要啟動一個Service來控制你的MediaPlayer執行個體。你應該謹慎地做這樣的設定,因為使用者和系統對于一個應用程式背景運作的服務該如何在系剩餘的資源中進行互動有一些預期。如果你的應用程式沒有達到這些預期,使用者可能會有些不好的體驗。這部分将描述些你需要注意到的主要的問題,且提供一些如何着手處理他們的意見。 異步運作: 首先,像一個活動頁面,一般來說一個Service中所有的工作都可以在一個單獨的線程中完成,事實上如果你正在運作在同一個應用程式中的一個活動頁面和一個Service,一般來說它們是使用同一個線程(即主線程)。是以,服務程序需要即時處理請求接入的用戶端且當相應他們時不能執行漫長的計算操作。如果任何可預見的含有大量的工作或者阻塞方法的任務被執行,你必須異步執行這些任務:可以通過自定義新的線程來實作,也可以通過直接調用架構中現有的異步處理工具來實作。 從執行個體中看到,當在主線程中使用MediaPlayer對象時,你應該調用prepareAsync()方法而不是prepare()方法,并且需要實作MediaPlayer.OnPreparedListener()方法來靜聽媒體準備完畢時的通知,然後你可以開始播放媒體。例如:
public class MyService extends Service implements MediaPlayer.OnPreparedListener {
    private static final String ACTION_PLAY = "com.example.action.PLAY";
    MediaPlayer mMediaPlayer = null;

    public int onStartCommand(Intent intent, int flags, int startId) {
        ...
        if (intent.getAction().equals(ACTION_PLAY)) {
            mMediaPlayer = ... // initialize it here
            mMediaPlayer.setOnPreparedListener(this);
            mMediaPlayer.prepareAsync(); // prepare async to not block main thread
        }
    }

    /** Called when MediaPlayer is ready */
    public void onPrepared(MediaPlayer player) {
        player.start();
    }
}
           
處理異步加載的錯誤: 當使用異步加載操作時,由于異常或錯誤的代碼導緻的錯誤可能會經常出現,當時當你使用異步加載資源時,你應該確定你的應用程式對于一些錯誤及時給出提示。在一個MediaPlayer執行個體中,你可以通過實作MediaPlayer.OnErrorListener接口來實作提示的功能,并把該接口的實作類設定到執行個體上:
public class MyService extends Service implements MediaPlayer.OnErrorListener {
    MediaPlayer mMediaPlayer;

    public void initMediaPlayer() {
        // ...initialize the MediaPlayer here...

        mMediaPlayer.setOnErrorListener(this);
    }

    @Override
    public boolean onError(MediaPlayer mp, int what, int extra) {
        // ... react appropriately ...
        // The MediaPlayer has moved to the Error state, must be reset!
    }
}
           
記住錯誤在何時發生很重要,如果MediaPlayer狀态改變錯誤(請看MediaPlayer類中的全狀态圖的文檔),你必須要重置該對象之後才能重新使用該對象。 使用喚醒鎖: 當設計在背景播放媒體的應用程式時,當你的服務程式正在運作時裝置有可能進入休眠狀态。因為在裝置休眠時android系統會盡可能地節省耗電量,系統試圖關閉所有不需要的手機特性,包括cpu和wifi的硬體資源。然而,如果你的服務程序正在播放或輸出音樂,你需要阻止系統幹涉你的播放。 在這種情況下為了確定你的服務程序繼續運作,你必須使用“喚醒鎖”,喚醒鎖是一種向系統發送信号的一種方式,告訴系統你的應用使用了一些需要在手機休眠的時儲存有效的特性。 注意:你應該總是保守地使用"喚醒鎖",且在确實需要的時候保留他們,因為他們會明顯地減少裝置的電池壽命。 為了確定當你的MediaPlayer播放時cpu一直運作,應該調用setWakeMode()方法當你初始化MediaPlayer對象時。一旦你這樣做了,MediaPlayer對象在播放時保持這個特殊的鎖在停頓或者停止時會釋放該鎖:
mMediaPlayer = new MediaPlayer();
// ... other initialization here ...
mMediaPlayer.setWakeMode(getApplicationContext(), PowerManager.PARTIAL_WAKE_LOCK);
           
然而,在上面的例子中喚醒鎖隻在CPU運作狀态時得到保證。如果你正在通過網絡傳輸媒體而wifi此時也被占用,你可能也想持有這個wifi鎖,但這個鎖你必須手動捕獲和釋放它。是以,當你使用遠端URL來開始準備MediaPlayere對象時,你應該建立和捕獲WIFI鎖。例如:
WifiLock wifiLock = ((WifiManager) getSystemService(Context.WIFI_SERVICE))
    .createWifiLock(WifiManager.WIFI_MODE_FULL, "mylock");

wifiLock.acquire();
           
當你暫停或停止媒體,或者不再需要網絡時,你應該釋放該鎖對象:
wifiLock.release();
           

像前台服務一樣運作

服務程序通常被用于執行背景人後,如捕獲郵件,同步加載資料,下載下傳内容及其他可能的事情。在這些情況中,使用者不主動了解程序服務的執行,而且可能根本不會注意一些程序服務被中斷或者重新開機。 但是仔細想一下到服務程序播放音樂的情況。很清楚地發現,這是一個使用者會主動注意的且會受到其他中斷嚴重幹擾的服務程序。此外,使用者可能希望在服務程序運作期間與它進行互動。在這種情況下,服務程序應該像“前台服務”。一個前台服務在系統中擁有較高的重要性——一個是系統可能永遠不會殺死的程序,因為它對使用者來說有着直接的重要性。當在前台運作時,服務都必須提供狀态通知欄確定使用者明确看到運作的服務程序且允許他們打開活動頁面與這些服務程序進行互動。 為了把你的服務程序變為其前台服務程序,你必須向狀态欄提供一個新的Notification對象,再調用service中的startForeground().例如:
String songName;
// assign the song name to songName
PendingIntent pi = PendingIntent.getActivity(getApplicationContext(), 0,
                new Intent(getApplicationContext(), MainActivity.class),
                PendingIntent.FLAG_UPDATE_CURRENT);
Notification notification = new Notification();
notification.tickerText = text;
notification.icon = R.drawable.play0;
notification.flags |= Notification.FLAG_ONGOING_EVENT;
notification.setLatestEventInfo(getApplicationContext(), "MusicPlayerSample",
                "Playing: " + songName, pi);
startForeground(NOTIFICATION_ID, notification);
           
當你的服務程序運作在前台時,你設定的的通知就會出現在裝置的通,知欄區域中。如果使用者選中這個通知,系統就會調用你提供的PendingIntent。在上面的例子中,它就打開了一個活動頁面(如MainActivity) 圖1 展示了你的通知如何呈現給使用者
media and camera 架構之一: media playback
media and camera 架構之一: media playback
圖1 前台服務程序通知的螢幕截圖,展示了狀态欄中的通知圖示(左圖)和展開後的圖示(右圖) 你應該隻在當你的服務程序确實執行一些使用者主動關注的動作時保持在“前台服務”狀态。一旦不再需要,你應該調用stopForeground()來釋放掉它。 處理音頻焦點 即使隻能有一個活動頁面可以在任意給定的時間運作,但android任然是多任務同時運作的環境。這裡對應用程式使用音頻提出一個特殊的挑戰,因為隻有一個音頻輸出并且同時可能有幾個媒體服務程序來競相使用它。在Android2.2之前,沒有内置的機制來提出這個問題,進而在一些情況下導緻了糟糕的使用者體驗。例如,當使用者正在聽音樂時此時别的應用需要通知使用者一些重要的事情,使用者可能會沒有聽到通知的提示音因為大聲的音樂聲。從Android2.2開始,平台提供為應用程式提供一種方式去協商使用裝置的音頻輸出。這種機制被叫做音頻焦點。 當你的應用程式需要輸出音頻時,如音頻或者通知體提示音,你應該一直請求音頻焦點。一旦它擷取到焦點,它就可以自如地使用音頻輸出,但是它應該一直監聽焦點變化。如果收到了失去音頻焦點的通知,它應該立即殺死音頻或者減低音頻的分貝到安靜水準(被稱為“淹沒”——有一個标志暗示哪個水準是合适的)且隻能在它重新獲得焦點之後才能恢複大聲播放。 音頻焦點的本質是協同運作。也就是說,應用程式被期望(和強烈地鼓勵)遵守音頻焦點的規則,但是這些規則不被系統所執行。如果一個應用程式想大聲播放音樂即時它失去了音頻焦點,系統也不會做任何事情去阻止。但是,使用者體驗越糟糕,使用者則更容易解除安裝行為不友好的應用程式。 為了請求音頻焦點,你必須調用AudioManager中的requestAudioFocus()方法,如下面例子展示:
AudioManager audioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
int result = audioManager.requestAudioFocus(this, AudioManager.STREAM_MUSIC,
    AudioManager.AUDIOFOCUS_GAIN);

if (result != AudioManager.AUDIOFOCUS_REQUEST_GRANTED) {
    // could not get audio focus.
}
           
requestAudioFocus()的第一個參數是一個 AudioManager.OnAudioFocusChangeListener方法,當音頻焦點變化時它的 onAudioFocusChange()  方法将會被調用。是以,你應該實作這個接口在你的服務程序和活動頁面中。例如:
class MyService extends Service
                implements AudioManager.OnAudioFocusChangeListener {
    // ....
    public void onAudioFocusChange(int focusChange) {
        // Do something based on focus change...
    }
}
           
foucsChange參數告訴你音頻焦點如何變化,可以是下面任意一個值(他們都是AudioManager中定義的常量):
  • AUDIOFOCUS_GAIN

    : 已經擷取到音頻焦點
  • AUDIOFOCUS_LOSS

    :失去了音頻焦點有段時間了。必須停止音頻播放。因為你可能長時間内不用期望重新擷取到音頻焦點了,該狀态下你可以盡可能地清理資源,例如 你應該釋放

    MediaPlayer對象。

  • AUDIOFOCUS_LOSS_TRANSIENT

    : 暫時失去音頻焦點,但是短時間内可能重新擷取。你必須停止音頻播放,但是可以繼續保持資源,因為你将會在短時間内重新擷取焦點.
  • AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK

    : 暫時失去音頻焦點,但是可以允許你繼續安靜地播放音頻(以較小的音量)而不是完全殺死該音頻.

這裡有一個實作的例子:

public void onAudioFocusChange(int focusChange) {
    switch (focusChange) {
        case AudioManager.AUDIOFOCUS_GAIN:
            // resume playback
            if (mMediaPlayer == null) initMediaPlayer();
            else if (!mMediaPlayer.isPlaying()) mMediaPlayer.start();
            mMediaPlayer.setVolume(1.0f, 1.0f);
            break;

        case AudioManager.AUDIOFOCUS_LOSS:
            // Lost focus for an unbounded amount of time: stop playback and release media player
            if (mMediaPlayer.isPlaying()) mMediaPlayer.stop();
            mMediaPlayer.release();
            mMediaPlayer = null;
            break;

        case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT:
            // Lost focus for a short time, but we have to stop
            // playback. We don't release the media player because playback
            // is likely to resume
            if (mMediaPlayer.isPlaying()) mMediaPlayer.pause();
            break;

        case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK:
            // Lost focus for a short time, but it's ok to keep playing
            // at an attenuated level
            if (mMediaPlayer.isPlaying()) mMediaPlayer.setVolume(0.1f, 0.1f);
            break;
    }
}
           

記住音頻焦點的接口隻能在API 8版本(Android2.2)或者之上可用,是以如果你想在較早的版本上支援該它,你應該采可用的支援該特性的向後相容的政策,反之隻能采用之前的方式。

你可通過反射調用音頻焦點的方法或者建立單獨的類實作音頻焦點的特性(也就是AudioFocusHelper類)來實作後向相容性,這裡是關于這種類的一個示例:

public class AudioFocusHelper implements AudioManager.OnAudioFocusChangeListener {
    AudioManager mAudioManager;

    // other fields here, you'll probably hold a reference to an interface
    // that you can use to communicate the focus changes to your Service

    public AudioFocusHelper(Context ctx, /* other arguments here */) {
        mAudioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
        // ...
    }

    public boolean requestFocus() {
        return AudioManager.AUDIOFOCUS_REQUEST_GRANTED ==
            mAudioManager.requestAudioFocus(mContext, AudioManager.STREAM_MUSIC,
            AudioManager.AUDIOFOCUS_GAIN);
    }

    public boolean abandonFocus() {
        return AudioManager.AUDIOFOCUS_REQUEST_GRANTED ==
            mAudioManager.abandonAudioFocus(this);
    }

    @Override
    public void onAudioFocusChange(int focusChange) {
        // let your service know about the focus change
    }
}
           

如果檢測到你的系統運作在API 8或者之上,你隻要建立一個AudioFocusHelper類的執行個體。例如:

if (android.os.Build.VERSION.SDK_INT >= 8) {
    mAudioFocusHelper = new AudioFocusHelper(getApplicationContext(), this);
} else {
    mAudioFocusHelper = null;
}
           

執行清理:

如之前提到的,一個MediaPlayer對象可以消耗大量的系統資源,是以你應該在需要的時候保留它,在執行完畢後調用release()方法釋放掉它。明确地調用清理方法比依靠系統的垃圾回收機制重要,因為在垃圾回收機制回收MediaPlayer對象之前需要花費些時間,因為該機制隻是對記憶體需要敏感而不是對媒體相關資源敏感。是以在你使用服務進時,你應該重寫onDestroy()方法來取保釋放MediaPlayer對象:

public class MyService extends Service {
   MediaPlayer mMediaPlayer;
   // ...

   @Override
   public void onDestroy() {
       if (mMediaPlayer != null) mMediaPlayer.release();
   }
}
           

除了關閉媒體時區釋放MediaPlayer對象不說, 你也應該一直尋求其他機會去釋放該對象。例如你預計相當長的時間内不會在播放媒體(例如失去媒體焦點之後),你應該明确地釋放存在的MediaPlayer對象,稍後再去重新建立。從另一方面來說,如果你預計隻是短暫的停止播放,你應該盡可能地保留MediaPlayer對象避免重新建立和準備對象的開銷。

處理AUDIO_BECOMING_NOISY intent

很多寫的非常好的播放音頻的應用程式可以自動停止播放當一個事件發生導緻音頻檔案變為噪音時(通過外部的揚聲器輸出)。例如,當使用者正在使用耳機聽音樂此時裝置偶然地斷開與耳機的聯系可能會導緻上述情況。然而,這種情況是無意識地發生的。如果你沒有實作裝置的外部揚聲器輸出音頻這一特性,這可能不是使用者所期望的。

你務必確定你的app在這些情況下通過操作ACTION_AUDIO_BECOMING_NOISY意圖來停止播放音樂,你可以通過添加一個Manifest注冊一個接收器來實作:

<receiver android:name=".MusicIntentReceiver">
   <intent-filter>
      <action android:name="android.media.AUDIO_BECOMING_NOISY" />
   </intent-filter>
</receiver>
           

MusicIntentReceiver類注冊為 ACTION_AUDIO_BECOMING_NOISY 意圖的廣播接收器。接着你應該實作這個類:

public class MusicIntentReceiver extends android.content.BroadcastReceiver {
   @Override
   public void onReceive(Context ctx, Intent intent) {
      if (intent.getAction().equals(
                    android.media.AudioManager.ACTION_AUDIO_BECOMING_NOISY)) {
          // signal your service to stop playback
          // (via an Intent, for instance)
      }
   }
}
           

從内容解析器取回媒體檔案

在媒體播放應用中另外一個有用的特性是有能力擷取裝置上使用者已有的音樂。你可以通過查詢外部的内容解析器來實作:

ContentResolver contentResolver = getContentResolver();
Uri uri = android.provider.MediaStore.Audio.Media.EXTERNAL_CONTENT_URI;
Cursor cursor = contentResolver.query(uri, null, null, null, null);
if (cursor == null) {
    // query failed, handle error.
} else if (!cursor.moveToFirst()) {
    // no media on the device
} else {
    int titleColumn = cursor.getColumnIndex(android.provider.MediaStore.Audio.Media.TITLE);
    int idColumn = cursor.getColumnIndex(android.provider.MediaStore.Audio.Media._ID);
    do {
       long thisId = cursor.getLong(idColumn);
       String thisTitle = cursor.getString(titleColumn);
       // ...process entry...
    } while (cursor.moveToNext());
}
           

為了在MediaPlayer使用該特性,你可以這樣做:

long id = /* retrieve it from somewhere */;
Uri contentUri = ContentUris.withAppendedId(
        android.provider.MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, id);

mMediaPlayer = new MediaPlayer();
mMediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
mMediaPlayer.setDataSource(getApplicationContext(), contentUri);

// ...prepare and start...