天天看點

OPhone程式開發入門之音樂播放器

ophone平台提供了完整的多媒體解決方案。為開發者提供了統一的,簡單易用的開發接口。本文首先介紹了ophone平台的多媒體架構,然後詳細介紹了 在ophone平台上開發音樂播放程式所需的基本知識。通過一步一步建構一個簡單的音樂播放器示例程式,來幫助讀者了解具體的開發過程。該示例涵蓋了application,activity,service,intent,broadcast  receiver等基本概念,使讀者對ophone程式的開發有一個全面的了解,進一步鞏固和熟悉這些基本概念。最後介紹了如何利用mat工具分析ophone

程式。

        本文适合ophone平台開發的初學者閱讀。(作者:cmri 孟钊)

ophone平台的多媒體架構

        在開始建構我們的示例程式前,先讓我們大概了解一下ophone平台的多媒體架構。

OPhone程式開發入門之音樂播放器

圖   一

       圖一是ophone平台的整體架構結構,從圖上我們可以看出ophone平台大緻可以分成以下幾個層次:

最上層是application層。它包含了主屏,電話,浏覽器,位址本等核心的應用程式。我們将開發的音樂播放器也屬于這一層。

第二層是application  framework層。這一層為開發者提供了完整的程式設計接口。多媒體部分提供了mediaplayer,  mediarecorder等接口。同時mediaprovider,mediascanner等系統服務也對媒體檔案的管理提供了支援。本文将重點介紹 它們的使用。

第三層是library層,  它由一系列的c/c++庫組成,這些庫的能力通過jni封裝成java接口,由application  framework層提供給開發者。多媒體系統庫opencore,它是ophone多媒體的核心,來源于packetvideo。它非常複雜,提供了完 整的多媒體解決方案。

最底層為linux kernel和驅動,負責與硬體的資料互動等。

OPhone程式開發入門之音樂播放器

  圖二說明了在ophone平台中播放音樂檔案時的調用關系。

  對于應用程式開發者來說,需要重點學習和關注的是如何使用appliation framework層提供給開發者的接口。

音樂媒體資訊的管理

  在開始構架程式之前,我們需要準備一下必須的基本知識。首先來了解一下在ophone平台中應該如何擷取音樂檔案的資訊以及如何管理這些資訊。

  ophone系統提供了mediascanner,mediaprovider,mediastore等接口,并且提供了一套資料庫表格,通過content  provider的方式提供給使用者。當手機開機或者有sd卡插拔等事件發生時,系統将會自動掃描sd卡和手機記憶體上的媒體檔案,如audio,video,圖檔等,将相應的資訊放到定義好的資料庫表格中。在這個程式中,我們不需要關心如何去掃描手機中的檔案,隻要了解如何查詢和使用 這些資訊就可以了。

  mediastore中定義了一系列的資料表格,通過contentresolver提供的查詢接口,我們可以得到各種需要的資訊。下面我們重點介紹如何管理sd卡上的音樂檔案資訊。

  先來了解一下contentresolver的查詢接口:

view plaincopy to clipboardprint?

cursor  query(uri uri, string[] projection, string selection, string[] selectionargs, string sortorder); 

[java]

view plaincopyprint?

        uri:指明要查詢的資料庫名稱加上表的名稱,從mediastore中我們可以找到相應資訊的參數,具體請參考開發文檔。

        projection: 指定查詢資料庫表中的哪幾列,傳回的遊标中将包括相應的資訊。null則傳回所有資訊。

        selection: 指定查詢條件

        selectionargs:參數selection裡有 ?這個符号是,這裡可以以實際值代替這個問号。如果selection這個沒有?的話,那麼這個string數組可以為null。

        sortorder:指定查詢結果的排列順序

  查詢所有歌曲:

cursor cursor = query(mediastore.audio.media.external_content_uri,

null, 

                null,

null, mediastore.audio.media.default_sort_order); 

                null, null, mediastore.audio.media.default_sort_order); 

  該指令将傳回所有在外部存儲卡上的音樂檔案的資訊,其中常用的資訊如下:

mediastore.audio.media._id:歌曲id 

int id = cursor.getint(cursor.getcolumnindexorthrow(mediastore.audio.media._id)); 

mediastore.audio.media.title:歌曲的名稱 

string tilte = cursor.getstring(cursor.getcolumnindexorthrow(mediastore.audio.media.title)); 

mediastore.audio.media.album :歌曲的專輯名 

string album = cursor.getstring(cursor.getcolumnindexorthrow(mediastore.audio.media.album)); 

mediastore.audio.media.artist:歌曲的歌手名 

string artist = cursor.getstring(cursor.getcolumnindexorthrow(mediastore.audio.media.artist)); 

mediastore.audio.media.data:歌曲檔案的路徑 

string url = cursor.getstring(cursor.getcolumnindexorthrow(mediastore.audio.media.data)); 

mediastore.audio.media.duration:歌曲的總播放時長 

int duration = cursor.getint(cursor.getcolumnindexorthrow(mediastore.audio.media.duration)); 

mediastore.audio.media.size: 歌曲檔案的大小 

int size = cursor.getlong(cursor.getcolumnindexorthrow(mediastore.audio.media.size)); 

        查詢歌手資訊:

cursor cursor = query(mediastore.audio.artists.external_content_uri,

null, null,

mediastore.audio.artists.default_sort_order); 

  該指令将傳回所有在外部存儲卡上的歌手資訊,其中常用的資訊如下:

mediastore.audio.artists._id:歌手id 

int id = cursor.getint(cursor.getcolumnindexorthrow(mediastore.audio.artists._id)); 

mediastore.audio.artists.artist :歌手姓名 

string name = cursor.getstring(cursor.getcolumnindexorthrow(mediastore.audio.artists.artist)); 

mediastore.audio.artists.number_of_albums: 共有多少該歌手的專輯 

int numofalbum = cursor.getint(cursor.getcolumnindexorthrow(mediastore.audio.artists.number_of_albums)); 

mediastore.audio.artists.number_of_tracks: 共有多少該歌手的歌曲 

int numofsong = cursor.getint(cursor.getcolumnindexorthrow(mediastore.audio.artists.number_of_tracks)); 

  查詢專輯資訊:

cursor cursor = query(mediastore.audio.albums.external_content_uri,

null, null,null, 

mediastore.audio.albums.default_sort_order); 

    該指令将傳回所有在外部存儲卡上的專輯資訊,其中常用的資訊如下:

mediastore.audio.albums._id :專輯id 

int id = cursor.getint(cursor.getcolumnindexorthrow(mediastore.audio.albums._id)); 

mediastore.audio.albums.album:專輯名稱 

string name = cursor.getstring(cursor.getcolumnindexorthrow(mediastore.audio.albums.album)); 

mediastore.audio.albums.number_of_songs:共用多少歌曲屬于該專輯 

int numofsong = cursor.getint(cursor.getcolumnindexorthrow(mediastore.audio.albums.number_of_songs)); 

  查詢播放清單

cursor cursor = query(mediastore.audio.playlists.external_content_uri,

mediastore.audio.playlists.date_added +

" asc"); 

mediastore.audio.playlists.date_added + " asc"); 

      該指令将傳回所有在外部存儲卡上的專輯資訊,其中常用的資訊如下:

mediastore.audio.playlists._id :播放清單id 

int id = cursor.getint(cursor.getcolumnindexorthrow(mediastore.audio.playlists._id)); 

mediastore.audio.playlists.name:播放清單名稱 

string name = cursor.getstring(cursor.getcolumnindexorthrow(mediastore.audio.playlists.name)); 

mediastore.audio.playlists.date_added :添加時間 

long dateadded = cursor.getlong(cursor.getcolumnindexorthrow(mediastore.audio.playlists.date_added)); 

mediastore.audio.playlists.date_modified :修改時間 

long datemodified = cursor.getlong(cursor.getcolumnindexorthrow(mediastore.audio.playlists.date_modified)); 

      通過組合這些查詢結果,指定查詢條件,使用者可以很友善的查詢指定的媒體資訊,比如:查詢屬于指定歌手(歌手id 為 aid)的歌曲:

query(mediastore.audio.media.external_content_uri,

                mediastore.audio.media.artist_id +

"=" + aid, null, 

                mediastore.audio.media.title); 

      查詢屬于指定專輯(專輯id 為 aid)的歌曲:

return query(mediastore.audio.media.external_content_uri,

                mediastore.audio.media.album_id +

      以上我們重點介紹了音樂媒體資訊的查詢方法,對于媒體資訊的增删改等操作主要集中在對播放清單的管理上,也是通過content  resolver的insert,update,delete等接口來實作的。隻要搞清楚了各個參數的含義,相應uri以及各個字段的義,很容易實作。由 于篇幅原因,我們不再詳細介紹,有興趣的朋友可以檢視ophone開發文檔。

音樂播放

  音樂檔案的播放功能是由mediaplayer類實作的,mediaplayer提供了常用的接口,比如播放,暫停,停止,快速定位等。

       播放音樂檔案的基本調用流程:

生成mediaplayer執行個體。

設定播放源(檔案)

準備播放

開始播放    

mediaplayer mp = new mediaplayer();   

mp.setdatasource(file_to_play);   

mp.prepare();   

mp.start();   

       以上代碼即可以完成最簡單的音樂播放功能。

       除了mediaplayer類,我們還需要注意幾個播放器件listener的使用,它們提供了播放器的更多的狀态資訊。

       1.mediaplayer.onbufferingupdatelistener

       當播放網絡上的媒體檔案或者流媒體時   mediaplayer.onbufferingupdatelistener 的onbufferingupdate(mediaplayer mp, int percent)接口函數會被回調,通知目前的緩沖進度資訊。

       通過setonbufferingupdatelistener(mediaplayer.onbufferingupdatelistener listener) 函數來注冊該listener

       2.mediaplayer.oncompletionlistener

       目前歌曲播放結束後,mediaplayer.oncompletionlistener的 oncompletion(mediaplayer mp) 接口會被回調,通知歌曲結束事件。

     通過setoncompletionlistener(mediaplayer.oncompletionlistener listener) 函數來注冊該監聽器

       3.mediaplayer.onerrorlistener

       當由于某種原因,mediaplayer進入錯誤狀态時,mediaplayer.onbufferingupdatelistener的onerror(mediaplayer mp, int what, int extra)接口會被回調,通知錯誤資訊。此時mediaplayer 應該調用reset()函數,将mediaplayer重新置于idle狀态。如果發生無法回複的錯誤,需要重新擷取mediaplayer的執行個體。

       4.mediaplayer.onpreparedlistener

       當播放網絡媒體檔案或流媒體時,播放器的準備時間較長,播放器準備完畢可以開始播放時,mediaplayer.onpreparedlistener的onprepared(mediaplayer mp)接口會被回調,通知該資訊。

當播放器需要支援播放流媒體或者網絡媒體檔案時,建議使用prepareasync()接口調用來準備播放器,同時通過mediaplayer.onpreparedlistener來監聽prepared資訊。這樣可以避免因為網絡等因素造成的mediaplayer準 備時間過長進而導緻程式長時間無響應。    

建構音樂播放器程式

      在學習了媒體資訊管理和媒體播放的基本内容後,我們現在可以開始動手建構我們的簡單點傳播放器示例程式了。

一.建立工程

       在eclipse開發環境中建立一個新的android project.

file > new > android project.

  設定工程名為musicplayerdemo, 設定packages名為   com.ophone

二.指定程式的application,添加musicplayerdemoapp

        添加musicplayerdemoapp類,它繼承自 android.app.application。

         application類用來存儲程式的狀态,它存在于整個程式的生命周期之中。

         修改androidmanifest.xml如下,指定musicplayerdemoapp為示例程式的application.

<?xml version="1.0" encoding="utf-8"?> 

<manifest xmlns:android="http://schemas.android.com/apk/res/android" 

      package="com.ophone" 

      android:versioncode="1" 

      android:versionname="1.0"> 

    <application android:name="musicplayerdemoapp" 

     android:icon="@drawable/icon"  

     android:label="@string/app_name"> 

    </application> 

</manifest> 

     我們需要注意application的兩個函數: oncreate() 和 onterminate(). 當程式開始運作時,oncreate()函數會首先被調用,此時沒有任何其他的對象在運作,在這裡我們可以進行一些初始化的工作。當程式結束時, onterminate()函數會被調用,程式程序将會退出,我們可以在此做一些最終的清理工作。需要注意的是,當因為系統資源緊張等問題,程式被系統kill的時候,onterminate()不會被調用到,程式将直接退出。

  稍後我們再來修改musicplayerdemoapp,先往下繼續。

三.管理音樂資訊的類musicdbcontroller

  為了使接口整潔,便于管理和使用,我們将在第三章介紹的         查詢管理音樂資訊的方法統一封裝在musicdbcontroller類中。

public static musicdbcontroller getinstance(musicplayerdemoapp app) { 

        if(sinstance == null) { 

            sinstance = new musicdbcontroller(app); 

        } 

        return sinstance; 

    } 

    private musicdbcontroller(musicplayerdemoapp app) { 

        mapp = app; 

  } 

private cursor query(uri _uri, string[] prjs, string selections, 

            string[] selectargs, string order) { 

        contentresolver resolver = mapp.getcontentresolver(); 

        if (resolver ==

null) { 

            return null; 

        return resolver.query(_uri, prjs, selections, selectargs, 

                order); 

  musicdbcontroller采用單例模式,使程式中隻有唯一的執行個體。我們傳入musicplayerdemoapp 作為context生成content resolver,用來查詢媒體庫。

  現在,我們修改musicplayerdemoapp,添加一個musicdbcontroller的成員,并在oncreate()中初始化它。

private musicdbcontroller mdbcontorller =

null;     

    public void oncreate() { 

        // todo auto-generated method stub 

        super.oncreate(); 

        // init musicdbcontroller 

        mdbcontorller = musicdbcontroller.getinstance(this); 

    并且提供一個擷取musicdbcontroller的接口: 

public musicdbcontroller getmusicdbcontroller(){ 

        return mdbcontorller; 

 這樣程式中的任何activity和serivce都可以通過getapplicatio()函數得到musicplayerdemoapp, 再通過getmusicdbcontroller()接口擷取musicdbcontroller,進而擷取所需要的媒體資訊。

四.展示媒體庫-musiclistactivity 和 musiclistadapter。

      首先添加musiclistadapter,它繼承自simplecursoradapter。通過重載bindview()函數, 把媒體庫資訊綁定到指定的listview上。

  我們使用android.r.layout.cmcc_list_5作為listview的layout,它的布局定義如下:

   android.r.layout.cmcc_list_5:

OPhone程式開發入門之音樂播放器

        android.r.id.listicon1 圖檔

        android.r.id.text1 左上文字

        android.r.id.text2 左下文字

        android.r.id.text3 右下文字

public void bindview(view view, context context, cursor cursor) {    

    super.bindview(view, context, cursor); 

    textview titleview = (textview) view.findviewbyid(android.r.id.text1); 

        textview artistview = (textview) view.findviewbyid(android.r.id.text2); 

        textview durationview = (textview) view.findviewbyid(android.r.id.text3); 

        imageview imageview = (imageview) view.findviewbyid(android.r.id.listicon1); 

        // set icon 

        imageview.setimageresource(r.drawable.cmcc_list_music); 

        // set track name 

        titleview.settext(cursor.getstring(cursor.getcolumnindexorthrow(mediastore.audio.media.title))); 

        // set artist name 

        artistview.settext(cursor.getstring(cursor.getcolumnindexorthrow(mediastore.audio.media.artist))); 

        // set duration 

        int duration = cursor.getint(cursor.getcolumnindexorthrow(mediastore.audio.media.duration)); 

        durationview.settext(maketimestring(duration)); 

 注意,上面這段代碼中的android.r.id.text1,android.r.id.text2,android.r.id.text3 和 android.r.id.listicon1是在我們傳入中的listview(android.r.layout.cmcc_list_5)的layout中定義的。如果你使用了自己定義的layout,請把它們替換成你自己定義的widget id。

  現在可以來添加我們的第一個activity -musiclistactivity,它以list的形式展示了所有歌曲。musiclistactivity繼承自listactivity。

  在oncreate()中擷取musicdbcontroller的執行個體,為擷取歌曲資訊做準備。

private musicdbcontroller mdbcontroller =

null; 

    /** called when the activity is first created. */ 

    @override 

    public void oncreate(bundle savedinstancestate) { 

        super.oncreate(savedinstancestate); 

        setcontentview(r.layout.main); 

        mdbcontroller = ((musicplayerdemoapp)getapplication()).getmusicdbcontroller(); 

通過musiclistadapter,我們将從musicdbcontroller中拿到的媒體庫資訊,綁定到listview,我們在onresume()完成這個工作。 

protected void onresume() { 

        super.onresume(); 

        mcursor = mdbcontroller.getallsongs(); 

        musiclistadapter adapter = new musiclistadapter(this, android.r.layout.cmcc_list_5, mcursor,

new string[]{}, new

int[]{}); 

        setlistadapter(adapter); 

将musiclistactivity添加到androidmanifest.xml中 

<activity android:name=".musiclistactivity"> 

            <intent-filter> 

                <action android:name="android.intent.action.main" /> 

                <category android:name="android.intent.category.launcher" /> 

            </intent-filter> 

        </activity> 

  現在運作一下我們的程式,它已經可以展現給你媒體庫的音樂清單了。

  同樣的,仿照上面的過程,我們還可以添加展示專輯清單,藝術家清單等等activity,我們就不再一一介紹了。

五.背景播放-使用service

      現在我們需要考慮如何來播放這些媒體庫中的檔案了。我們希望當使用者退出這個程式界面後,我們的程式仍然能夠繼續播放歌曲,比如使用者在讀郵件時,可以聽聽音 樂。為了達到背景播放的效果,需要使用service。當程式的所有activity都退出後,service仍然可以在背景運作。在這個示例中我們使用local service,它與應用程式運作在同一個程序中。(我們甚至可以不使用bind  service就直接獲得它的句柄,調用它所提供的函數。)

  首先,建立一個musicplaybackservice類,它繼承自android.app.service,重載onbind方法,傳回自 定義的localbinder,通過localbinder的getservice()方法就可以獲得musicplaybackservice的句柄 了。

private final ibinder mbinder =

new localbinder(); 

    public class localbinder

extends binder { 

        public musicplaybackservice getservice() { 

            return musicplaybackservice.this; 

    public ibinder onbind(intent intent) { 

        return mbinder; 

  我們繼續完成musicplaybackservice的基本構架,添加一個mediaplayer成員,并在oncreate()函數中對其進行初始化,它将負責音樂播放的主要功能。

private mediaplayer mmediaplayer =

mmediaplayer = new mediaplayer(); 

  構架完成musicplaybackservice的基本架構後,我們要定義一些常用的控制接口了,其他子產品通過這些接口,可以控制音樂的播放,暫停,停止等功能。

public void setdatasource(string path) { 

        try { 

            mmediaplayer.reset(); 

            mmediaplayer.setdatasource(path); 

            mmediaplayer.prepare(); 

        } catch (ioexception e) { 

            return; 

        } catch (illegalargumentexception e) { 

    public void start() { 

        mmediaplayer.start(); 

    public void stop() { 

        mmediaplayer.stop(); 

    public void pause() { 

        mmediaplayer.pause(); 

    public boolean isplaying() { 

        return mmediaplayer.isplaying(); 

    public int getduration() { 

        return mmediaplayer.getduration(); 

    public int getposition() { 

        return mmediaplayer.getcurrentposition(); 

    public long seek(long whereto) { 

        mmediaplayer.seekto((int) whereto); 

        return whereto; 

  最後,修改androidmanifest.xml,添加musicplaybackservice的定義。

<service android:name=".musicplaybackservice" android:exported="true" > 

                 <action android:name="com.ophone.musicplaybackservice" /> 

            </intent-filter>     

</service> 

六.開始播放歌曲

      musicplaybackservice準備就緒,我們可以利用它來播放歌曲了。修改musiclistactivity,在 oncreate() 中通過startservice()函數啟動musicplaybackservice,并通過bindservice()函數與之綁定。當綁定完成 時,serviceconnection的 onserviceconnected()接口将被調用。

private musicplaybackservice mplaybackservice =

   private serviceconnection mplaybackconnection =

new serviceconnection() { 

        public void onserviceconnected(componentname classname, ibinder service) {   

            mplaybackservice = ((musicplaybackservice.localbinder)service).getservice(); 

        public void onservicedisconnected(componentname classname) { 

            mplaybackservice = null; 

}; 

public void oncreate(bundle savedinstancestate) { 

        setcontentview(r.layout.list_layout); 

        // bind playback service 

        startservice(new intent(this,musicplaybackservice.class));         

        bindservice(new intent(this,musicplaybackservice.class), mplaybackconnection, context.bind_auto_create); 

      為musiclistactivity添加點選事件處理,當使用者點選一個音樂item時,會開始自動播放該歌曲,當使用者點選一個item時,onlistitemclick()函數會被調用。

protected void onlistitemclick(listview l, view v,

int position, long id) { 

        super.onlistitemclick(l, v, position, id); 

        if (mcursor ==

null ||mcursor.getcount() == 0) { 

        mcursor.movetoposition(position); 

        string url = mcursor 

                       .getstring(mcursor 

                            .getcolumnindexorthrow(mediastore.audio.media.data)); 

        mplaybackservice.setdatasource(url); 

        mplaybackservice.start(); 

      現在趕緊運作一下程式吧,看看是不是已經可以播放音樂了呢。

七. 播放控制-使用intent和broadcast receiver

      目前我們隻能播放音樂,還無法控制音樂的播放,暫停,停止,等等,讓我們進一步來完善這個播放程式,給它添加兩個控制按鈕。

修改musiclistactivity的layout檔案list_layout.xml如下:

<linearlayout 

android:id="@+id/widget1" 

android:layout_width="fill_parent" 

android:layout_height="fill_parent" 

xmlns:android="http://schemas.android.com/apk/res/android" 

android:orientation="vertical" 

> 

<relativelayout android:id="@+id/control_panel" 

        android:orientation="horizontal"  

        android:layout_width="fill_parent" 

        android:layout_height="wrap_content" 

        > 

<textview android:id="@+id/show_text" 

        android:textsize="20sp" 

        android:text="@string/click_to_play"/> 

<button android:id="@+id/play_pause_btn" 

    android:layout_width="100px" 

    android:layout_height="wrap_content"  

    android:layout_alignparentleft="true" 

    android:visibility="invisible" 

    android:text="@string/play"/>     

<button android:id="@+id/stop_btn" 

    android:layout_alignparentright="true" 

    android:text="@string/stop"/>             

</relativelayout> 

<listview android:id="@id/android:list" 

        android:layout_height="fill_parent" 

        android:cachecolorhint="#00000000"/> 

<textview android:id="@id/android:empty" 

        android:text="@string/no_music"/> 

</linearlayout> 

       在musiclistactivity中,添加兩個按鈕點選事件的處理程式,通過button的setonclicklistener()函數,為button添加一個button.onclicklistener,當有點選事件發生時,button.onclicklistener的onclick()接口将被調用。

private textview mtextview =

private button mplaypausebutton =

private button mstopbutton =

       在oncreate函數中,增加如下的代碼:

mtextview = (textview)findviewbyid(r.id.show_text); 

        mplaypausebutton = (button) findviewbyid(r.id.play_pause_btn); 

        mstopbutton = (button) findviewbyid(r.id.stop_btn); 

        mplaypausebutton.setonclicklistener(new button.onclicklistener() { 

            public void onclick(view v) { 

                // perform action on click 

                if (mplaybackservice !=

null && mplaybackservice.isplaying()) { 

                    mplaybackservice.pause(); 

                    mplaypausebutton.settext(r.string.play); 

                } else

if (mplaybackservice != null){ 

                    mplaybackservice.start(); 

                    mplaypausebutton.settext(r.string.pause); 

                } 

            } 

        }); 

        mstopbutton.setonclicklistener(new button.onclicklistener() { 

            public

void onclick(view v) { 

null ) { 

                    mtextview.setvisibility(view.visible); 

                    mplaypausebutton.setvisibility(view.invisible); 

                    mstopbutton.setvisibility(view.invisible); 

                    mplaybackservice.stop(); 

       現在運作程式,我們還看不到這兩個控制按鈕,預設狀态下他們是不可見狀态。程式剛啟動時,預設顯示提示資訊。當播放器狀态發生改變,有歌曲進行播放時,我 們顯示控制按鈕,隐藏提示資訊。我們使用intent和broadcast receiver來實作這個功能。

      定義準備完畢和播放完畢的action string

public static

final string player_prepare_end =

"com.ophone.musicplaybackservice.prepared"; 

    public static

final string play_completed = "com.ophone.musicplaybackservice.playcompleted"; 

      播放器狀态發生改變的時候,通過intent的形式,将消息廣播出去,給mediaplayer添加mediaplayer.onpreparedlistener和mediaplayer.oncompletionlistener,監聽準備完畢和播 放結束的消息。

mediaplayer.oncompletionlistener mcompletelistener =

new mediaplayer.oncompletionlistener() { 

        public void oncompletion(mediaplayer mp) { 

            broadcastevent(play_completed); 

    }; 

    mediaplayer.onpreparedlistener mpreparelistener =

new mediaplayer.onpreparedlistener() { 

        public void onprepared(mediaplayer mp) {    

            broadcastevent(player_prepare_end); 

    private void broadcastevent(string what) { 

        intent i = new intent(what); 

        sendbroadcast(i); 

修改musicplaybackservice,在mediaplayer中注冊這個兩個listener: 

public void oncreate() { 

        mmediaplayer = new mediaplayer(); 

        mmediaplayer.setonpreparedlistener(mpreparelistener); 

        mmediaplayer.setoncompletionlistener(mcompletelistener); 

       在musiclistactivity中,我們定義一個broadcastreceiver來處理這兩個消息:

protected broadcastreceiver mplayerevtreceiver =

new broadcastreceiver() { 

        @override 

        public void onreceive(context context, intent intent) { 

            string action = intent.getaction(); 

            if (action.equals(musicplaybackservice.player_prepare_end)) { 

                // will begin to play 

                mtextview.setvisibility(view.invisible); 

                mplaypausebutton.setvisibility(view.visible); 

                mstopbutton.setvisibility(view.visible); 

                mplaypausebutton.settext(r.string.pause); 

            } else if(action.equals(musicplaybackservice.play_completed)) { 

                mplaypausebutton.settext(r.string.play); 

在oncreate()函數中,注冊這個broadcastreceiver來監聽player_prepare_end 和play_completed 這兩個資訊 ,在oncreate函數中添加下面的代碼: 

intentfilter filter = new intentfilter(); 

        filter.addaction(musicplaybackservice.player_prepare_end); 

        filter.addaction(musicplaybackservice.play_completed); 

    registerreceiver(mplayerevtreceiver, filter); 

      ok,現在我們的音樂播放器已經成型了,馬上運作一下吧。

給程式加點新功能

  下面介紹的功能,在我們的示例代碼中并沒有實作,如果您感興趣的話,可以按照下文介紹的大概步驟,添加到程式中,他們其實都很簡單。

1.利用alarm service實作簡單的鬧鈴功能。

  alarm service是ophone平台提供的一個系統服務。程式可以向alarm  service注冊一個pendingintent,當到達注冊時間的時候,alarm  service會發出這個事先注冊的intent,程式監聽這個intent就可以達到定時的效果。

 1)添加一個broadcastreceiver

public class startalarm

extends broadcastreceiver { 

            // 添加處理程式,啟動播放。 

  在androidmanifest.xml中添加定義:

<receiver android:name=".startalarm" /> 

  2)注冊alarm service

intent startintent = new intent(context, startalarm.class); 

pendingintent startsender = pendingintent.getbroadcast( 

                context, 0, startintent,

0); 

// schedule the alarm!  starttimemillis 是定時時間 

        alarmmanager am = (alarmmanager)getsystemservice(alarm_service); 

        am.setrepeating(alarmmanager.rtc_wakeup, starttimemillis, 

                24 *

60 * 60 *

1000, startsender); 

       ok,我們就完成了定時注冊,當注冊時間到達時,即使程式沒有運作,也會被喚醒,startalarm的onreceive()函數被調用,開始播放音樂。一個簡單的鬧鐘功能就實作了。感興趣的朋友可以馬上動手試驗試驗。  

  2.設定振鈴

  當我們發現了一首非常好聽的歌曲,想把它設定成來電振鈴,    如何實作呢?很簡單,隻需要如下兩個步驟。

  第一步,更新歌曲在media provider資料庫中的資訊,

将 mediastore.audio.media.is_ringtone,

mediastore.audio.media.is_alarm,

mediastore.audio.media.is_notification都置成 1。

  假設歌曲的id為 songid:

contentresolver resolver = ctx.getcontentresolver(); 

        // set the flag in the database to mark this as a ringtone 

        uri ringuri = contenturis.withappendedid(mediastore.audio.media.external_content_uri, songid);    

        try {                        

            contentvalues values = new contentvalues(2); 

            values.put(mediastore.audio.media.is_ringtone,

"1"); 

            values.put(mediastore.audio.media.is_alarm,

            values.put(mediastore.audio.media.is_notification,

"1");             

            resolver.update(ringuri, values, null,

null);             

        } catch (unsupportedoperationexception ex) { 

  第二步,通過android.provider.settings.profile的setringtone接口,設定歌曲為振鈴:

settings.profile.setringtone(resolver, ringuri); 

  現在給自己打個電話試試看,是不是振鈴已經起作用了

  使用mat分析ophone程式

我們的示例代碼已經完成了,大家可以按照上文的步驟自己一步一步來構造自己的音樂播放器,也可以使用附錄的源代碼包,将工程導入進eclipse直接體驗一下。最後和大家分享一下使用mat分析ophone程式的方法。

  通常來說我們調試ophone程式有兩個最常見的方法,一,利用ophone平台提供的android.util.log通過log資訊來分析 錯誤發生的原因。 二,通過設定斷點,一步一步的跟蹤程式發現問題。這兩個方法非常有效,介紹相關方法的文章也很多,大家google一下就找到了。

  還有一類常見的問題就是memory  leak。對記憶體洩漏這類問題,以上兩種方法不是很有效,在ddms工具裡面,我們也基本上隻能檢視到heap的使用情況,對分析問題幫助不大。我們可以 利用eclipse mat (memory analyzer tool)工具來分析此類問題。eclipse memory  analyzer是一個快速并且功能強大的java heap分析器,能夠幫助你查找記憶體洩漏和減少記憶體消耗。

  如何安裝使用mat工具,請到http://www.eclipse.org/mat/學習,我們主要來介紹一下如何在ophone上得到程式運作的heap dump資訊。 

adb shell 登陸到手機或模拟器

su – 切換到root權限

chmod 777 /data/misc, 使/data/misc目錄具有讀寫權限

通過ps指令,找到要調試的程式的pid

kill -10 pid

在/data/misc 目錄下,會生成檔案名類似heap-dump-xxxxx-pidxxx.hprof的檔案。

通過adb pull 指令将.hprof檔案拽到pc端

使用ophone sdk提供的hprof-conv工具将ophone生成的hprof檔案轉換成mat識别的标準格式。例如:

hprof-conv  heap-dump-xxxxx-pidxxx.hprof  standard-dump-file.hprof 

   9. 使用mat工具打開 standard-dump-file.hprof, 你将看到類似下圖的分析報告。

     分析報告提供了詳盡的heap資訊,同時還指出了可疑的記憶體洩漏的對象。

OPhone程式開發入門之音樂播放器

       大家可以根據mat提供的詳細heap資訊,查找漏洞了。

附錄:源代碼:

uploads/file/musicplayerdemo.zip