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


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

        OPhone平臺提供了完整的多媒體解決方案。爲開發者提供了統一的,簡單易用的開發接口。本文首先介紹了OPhone平臺的多媒體框架,然後詳細介紹了 在OPhone平臺上開發音樂播放程序所需的基本知識。通過一步一步構建一個簡單的音樂播放器示例程序,來幫助讀者瞭解具體的開發過程。該示例涵蓋了 Application,Activity,Service,Intent,BroadCast Receiver等基本概念,使讀者對OPhone程序的開發有一個全面的瞭解,進一步鞏固和熟悉這些基本概念。最後介紹瞭如何利用MAT工具分析 OPhone 程序。

        本文適合OPhone平臺開發的初學者閱讀。(作者:CMRI 孟釗)

OPhone平臺的多媒體架構

        在開始構建我們的示例程序前,先讓我們大概瞭解一下OPhone平臺的多媒體框架。

 

圖   一

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

  1. 最上層是Application層。它包含了主屏,電話,瀏覽器,地址本等核心的應用程序。我們將開發的音樂播放器也屬於這一層。
  2. 第二層是Application Framework層。這一層爲開發者提供了完整的編程接口。多媒體部分提供了MediaPlayer, MediaRecorder等接口。同時MediaProvider,MediaScanner等系統服務也對媒體文件的管理提供了支持。本文將重點介紹 它們的使用。
  3. 第三層是Library層, 它由一系列的c/c++庫組成,這些庫的能力通過JNI封裝成java接口,由Application Framework層提供給開發者。多媒體系統庫OpenCore,它是OPhone多媒體的核心,來源於PacketVideo。它非常複雜,提供了完 整的多媒體解決方案。
  4. 最底層爲Linux Kernel和驅動,負責與硬件的數據交互等。

 

  圖二說明了在OPhone平臺中播放音樂文件時的調用關係。

  對於應用程序開發者來說,需要重點學習和關注的是如何使用Appliation Framework層提供給開發者的接口。

音樂媒體信息的管理

  在開始構架程序之前,我們需要準備一下必須的基本知識。首先來了解一下在OPhone平臺中應該如何獲取音樂文件的信息以及如何管理這些信息。

  OPhone系統提供了 MediaScanner,MediaProvider,MediaStore等接口,並且提供了一套數據庫表格,通過Content Provider的方式提供給用戶。當手機開機或者有SD卡插拔等事件發生時,系統將會自動掃描SD卡和手機內存上的媒體文件,如 audio,video,圖片等,將相應的信息放到定義好的數據庫表格中。在這個程序中,我們不需要關心如何去掃描手機中的文件,只要瞭解如何查詢和使用 這些信息就可以了。

  MediaStore中定義了一系列的數據表格,通過ContentResolver提供的查詢接口,我們可以得到各種需要的信息。下面我們重點介紹如何管理SD卡上的音樂文件信息。

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

 

  1. Cursor  query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder);  

        Uri:指明要查詢的數據庫名稱加上表的名稱,從MediaStore中我們可以找到相應信息的參數,具體請參考開發文檔。 
        Projection: 指定查詢數據庫表中的哪幾列,返回的遊標中將包括相應的信息。Null則返回所有信息。
        selection: 指定查詢條件
        selectionArgs:參數selection裏有 ?這個符號是,這裏可以以實際值代替這個問號。如果selection這個沒有?的話,那麼這個String數組可以爲null。
        SortOrder:指定查詢結果的排列順序

  查詢所有歌曲:

  1. Cursor cursor = query(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,  null ,  
  2.                 null  null , MediaStore.Audio.Media.DEFAULT_SORT_ORDER);  

  該命令將返回所有在外部存儲卡上的音樂文件的信息,其中常用的信息如下:

  1. MediaStore.Audio.Media._ID:歌曲ID  
  2. Int id = cursor.getInt(cursor.getColumnIndexOrThrow(MediaStore.Audio.Media._ID));  
  3.   
  4. MediaStore.Audio.Media.TITLE:歌曲的名稱  
  5. String tilte = cursor.getString(cursor.getColumnIndexOrThrow(MediaStore.Audio.Media.TITLE));  
  6.   
  7. MediaStore.Audio.Media.ALBUM :歌曲的專輯名  
  8. String album = cursor.getString(cursor.getColumnIndexOrThrow(MediaStore.Audio.Media.ALBUM));  
  9.   
  10.   
  11. MediaStore.Audio.Media.ARTIST:歌曲的歌手名  
  12. String artist = cursor.getString(cursor.getColumnIndexOrThrow(MediaStore.Audio.Media.ARTIST));  
  13.   
  14. MediaStore.Audio.Media.DATA:歌曲文件的路徑  
  15. String url = cursor.getString(cursor.getColumnIndexOrThrow(MediaStore.Audio.Media.DATA));  
  16.   
  17. MediaStore.Audio.Media.DURATION:歌曲的總播放時長  
  18. Int duration = cursor.getInt(cursor.getColumnIndexOrThrow(MediaStore.Audio.Media.DURATION));  
  19.   
  20. MediaStore.Audio.Media.SIZE: 歌曲文件的大小  
  21. Int size = cursor.getLong(cursor.getColumnIndexOrThrow(MediaStore.Audio.Media.SIZE));  


        查詢歌手信息:

  1. Cursor cursor = query(MediaStore.Audio.Artists.EXTERNAL_CONTENT_URI,  null  null  null ,  
  2.  MediaStore.Audio.Artists.DEFAULT_SORT_ORDER);  

  該命令將返回所有在外部存儲卡上的歌手信息,其中常用的信息如下:

  1. MediaStore.Audio.Artists._ID:歌手id  
  2. Int id = cursor.getInt(cursor.getColumnIndexOrThrow(MediaStore.Audio.Artists._ID));  
  3.   
  4. MediaStore.Audio.Artists.ARTIST :歌手姓名  
  5. String name = cursor.getString(cursor.getColumnIndexOrThrow(MediaStore.Audio.Artists.ARTIST));  
  6.   
  7. MediaStore.Audio.Artists.NUMBER_OF_ALBUMS: 共有多少該歌手的專輯  
  8. Int numOfAlbum = cursor.getInt(cursor.getColumnIndexOrThrow(MediaStore.Audio.Artists.NUMBER_OF_ALBUMS));  
  9.   
  10. MediaStore.Audio.Artists.NUMBER_OF_TRACKS: 共有多少該歌手的歌曲  
  11. Int numOfSong = cursor.getInt(cursor.getColumnIndexOrThrow(MediaStore.Audio.Artists.NUMBER_OF_TRACKS));  

  查詢專輯信息:

  1. Cursor cursor = query(MediaStore.Audio.Albums.EXTERNAL_CONTENT_URI,  null  null , null ,  
  2.  MediaStore.Audio.Albums.DEFAULT_SORT_ORDER);  

    該命令將返回所有在外部存儲卡上的專輯信息,其中常用的信息如下:

  1. MediaStore.Audio.Albums._ID :專輯id  
  2. Int id = cursor.getInt(cursor.getColumnIndexOrThrow(MediaStore.Audio.Albums._ID));  
  3.   
  4. MediaStore.Audio.Albums.ALBUM:專輯名稱  
  5. String name = cursor.getString(cursor.getColumnIndexOrThrow(MediaStore.Audio.Albums.ALBUM));  
  6.   
  7. MediaStore.Audio.Albums.NUMBER_OF_SONGS:共用多少歌曲屬於該專輯  
  8. Int numOfSong = cursor.getInt(cursor.getColumnIndexOrThrow(MediaStore.Audio.Albums.NUMBER_OF_SONGS));  

  查詢播放列表

  1. Cursor cursor = query(MediaStore.Audio.Playlists.EXTERNAL_CONTENT_URI,  null  null  null ,  
  2.  MediaStore.Audio.Playlists.DATE_ADDED + " asc" );  

      該命令將返回所有在外部存儲卡上的專輯信息,其中常用的信息如下:

  1. MediaStore.Audio.Playlists._ID :播放列表id  
  2. Int id = cursor.getInt(cursor.getColumnIndexOrThrow(MediaStore.Audio.Playlists._ID));  
  3.   
  4. MediaStore.Audio.Playlists.NAME:播放列表名稱  
  5. String name = cursor.getString(cursor.getColumnIndexOrThrow(MediaStore.Audio.Playlists.NAME));  
  6.   
  7. MediaStore.Audio.Playlists.DATE_ADDED :添加時間  
  8. long  dateAdded = cursor.getLong(cursor.getColumnIndexOrThrow(MediaStore.Audio.Playlists.DATE_ADDED));  
  9.   
  10. MediaStore.Audio.Playlists.DATE_MODIFIED :修改時間  
  11. long  dateModified = cursor.getLong(cursor.getColumnIndexOrThrow(MediaStore.Audio.Playlists.DATE_MODIFIED));  

      通過組合這些查詢結果,指定查詢條件,用戶可以很方便的查詢指定的媒體信息,比如:查詢屬於指定歌手(歌手id 爲 aid)的歌曲:

  1. query(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,  null ,  
  2.                 MediaStore.Audio.Media.ARTIST_ID + "="  + aid,  null ,  
  3.                 MediaStore.Audio.Media.TITLE);  


      查詢屬於指定專輯(專輯id 爲 aid)的歌曲:

  1. return  query(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,  null ,  
  2.                 MediaStore.Audio.Media.ALBUM_ID + "="  + aid,  null ,  
  3.                 MediaStore.Audio.Media.TITLE);  


      以上我們重點介紹了音樂媒體信息的查詢方法,對於媒體信息的增刪改等操作主要集中在對播放列表的管理上,也是通過Content Resolver的insert,update,delete等接口來實現的。只要搞清楚了各個參數的含義,相應URI以及各個字段的義,很容易實現。由 於篇幅原因,我們不再詳細介紹,有興趣的朋友可以查看OPhone開發文檔。

音樂播放

  音樂文件的播放功能是由MediaPlayer類實現的,MediaPlayer提供了常用的接口,比如播放,暫停,停止,快速定位等。

       播放音樂文件的基本調用流程:

  1. 生成MediaPlayer實例。
  2. 設置播放源(文件)
  3. 準備播放
  4. 開始播放  
  1. MediaPlayer mp =  new  MediaPlayer();    
  2. mp.setDataSource(file_to_play);    
  3. mp.prepare();    
  4. 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.

  1. <?xml version= "1.0"  encoding= "utf-8" ?>  
  2. <manifest xmlns:android="http://schemas.android.com/apk/res/android"   
  3.       package = "com.ophone"   
  4.       android:versionCode="1"   
  5.       android:versionName="1.0" >  
  6.     <application android:name="MusicPlayerDemoApp"   
  7.      android:icon="@drawable/icon"    
  8.      android:label="@string/app_name" >  
  9.     </application>  
  10. </manifest>  

     我們需要注意Application的兩個函數: onCreate() 和 onTerminate(). 當程序開始運行時,onCreate()函數會首先被調用,此時沒有任何其他的對象在運行,在這裏我們可以進行一些初始化的工作。當程序結束時, onTerminate()函數會被調用,程序進程將會退出,我們可以在此做一些最終的清理工作。需要注意的是,當因爲系統資源緊張等問題,程序被系統 kill的時候,onTerminate()不會被調用到,程序將直接退出。
  稍後我們再來修改MusicPlayerDemoApp,先往下繼續。

三.管理音樂信息的類MusicDBController
  爲了使接口整潔,便於管理和使用,我們將在第三章介紹的         查詢管理音樂信息的方法統一封裝在MusicDBController類中。

  1. public   static  MusicDBController getInstance(MusicPlayerDemoApp app) {  
  2.         if (sInstance ==  null ) {  
  3.             sInstance = new  MusicDBController(app);  
  4.         }  
  5.         return  sInstance;  
  6.     }  
  7.       
  8.     private  MusicDBController(MusicPlayerDemoApp app) {  
  9.         mApp = app;  
  10.   }  
  11.   
  12. private  Cursor query(Uri _uri, String[] prjs, String selections,  
  13.             String[] selectArgs, String order) {  
  14.         ContentResolver resolver = mApp.getContentResolver();  
  15.         if  (resolver ==  null ) {  
  16.             return   null ;  
  17.         }  
  18.           
  19.         return  resolver.query(_uri, prjs, selections, selectArgs,  
  20.                 order);  

  MusicDBController採用單例模式,使程序中只有唯一的實例。我們傳入MusicPlayerDemoApp 作爲Context生成Content Resolver,用來查詢媒體庫。

  現在,我們修改MusicPlayerDemoApp,添加一個MusicDBController的成員,並在onCreate()中初始化它。

  1. private  MusicDBController mDBContorller =  null ;      
  2.     public   void  onCreate() {  
  3.         // TODO Auto-generated method stub   
  4.         super .onCreate();  
  5.           
  6.         // init MusicDBController   
  7.         mDBContorller = MusicDBController.getInstance(this );  
  8.   }  
  9.     並且提供一個獲取MusicDBController的接口:  
  10.       
  11. public  MusicDBController getMusicDBController(){  
  12.         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:

        android.R.id.listicon1 圖片
        android.R.id.text1 左上文字
        android.R.id.text2 左下文字
        android.R.id.text3 右下文字

 

  1. public   void  bindView(View view, Context context, Cursor cursor) {     
  2.           
  3.     super .bindView(view, context, cursor);  
  4.           
  5.     TextView titleView = (TextView) view.findViewById(android.R.id.text1);  
  6.         TextView artistView = (TextView) view.findViewById(android.R.id.text2);  
  7.         TextView durationView = (TextView) view.findViewById(android.R.id.text3);  
  8.         ImageView imageView = (ImageView) view.findViewById(android.R.id.listicon1);  
  9.           
  10.         // Set icon   
  11.         imageView.setImageResource(R.drawable.cmcc_list_music);  
  12.           
  13.         // Set track name   
  14.         titleView.setText(cursor.getString(cursor.getColumnIndexOrThrow(MediaStore.Audio.Media.TITLE)));  
  15.           
  16.         // Set artist name   
  17.         artistView.setText(cursor.getString(cursor.getColumnIndexOrThrow(MediaStore.Audio.Media.ARTIST)));  
  18.           
  19.         // Set duration   
  20.         int  duration = cursor.getInt(cursor.getColumnIndexOrThrow(MediaStore.Audio.Media.DURATION));  
  21.         durationView.setText(makeTimeString(duration));  
  22.           
  23.     }  

 注意,上面這段代碼中的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的實例,爲獲取歌曲信息做準備。 

  1. private  MusicDBController mDBController =  null ;  
  2.       
  3.     /** Called when the activity is first created. */   
  4.     @Override   
  5.     public   void  onCreate(Bundle savedInstanceState) {  
  6.         super .onCreate(savedInstanceState);  
  7.         setContentView(R.layout.main);  
  8.           
  9.         mDBController = ((MusicPlayerDemoApp)getApplication()).getMusicDBController();  
  10.     }  
  11. 通過MusicListAdapter,我們將從MusicDBController中拿到的媒體庫信息,綁定到ListView,我們在onResume()完成這個工作。  
  12. protected   void  onResume() {  
  13.         super .onResume();  
  14.         mCursor = mDBController.getAllSongs();  
  15.         MusicListAdapter adapter = new  MusicListAdapter( this , android.R.layout.cmcc_list_5, mCursor,  new  String[]{},  new   int []{});  
  16.         setListAdapter(adapter);  
  17.  }  
  18. 將MusicListActivity添加到AndroidManifest.xml中  
  19.   
  20. <activity android:name=".MusicListActivity" >  
  21.             <intent-filter>  
  22.                 <action android:name="android.intent.action.MAIN"  />  
  23.                 <category android:name="android.intent.category.LAUNCHER"  />  
  24.             </intent-filter>  
  25.         </activity>  

  現在運行一下我們的程序,它已經可以展現給你媒體庫的音樂列表了。

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

五.後臺播放-使用Service

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

  首先,創建一個MusicPlaybackService類,它繼承自android.app.Service,重載onBind方法,返回自 定義的LocalBinder,通過LocalBinder的getService()方法就可以獲得MusicPlaybackService的句柄 了。 

  1. private   final  IBinder mBinder =  new  LocalBinder();  
  2.       
  3.     public   class  LocalBinder  extends  Binder {  
  4.         public  MusicPlaybackService getService() {  
  5.             return  MusicPlaybackService. this ;  
  6.         }  
  7.     }  
  8.       
  9.     @Override   
  10.     public  IBinder onBind(Intent intent) {  
  11.         // TODO Auto-generated method stub   
  12.         return  mBinder;  
  13. }  

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

  1. private  MediaPlayer mMediaPlayer =  null ;  
  2.           
  3.     public   void  onCreate() {  
  4.         super .onCreate();  
  5. mMediaPlayer = new  MediaPlayer();  
  6. }  

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

  1. public   void  setDataSource(String path) {  
  2.         try  {  
  3.             mMediaPlayer.reset();  
  4.             mMediaPlayer.setDataSource(path);  
  5.             mMediaPlayer.prepare();  
  6.         } catch  (IOException e) {  
  7.             return ;  
  8.         } catch  (IllegalArgumentException e) {  
  9.             return ;  
  10.         }  
  11.     }  
  12.       
  13.     public   void  start() {  
  14.         mMediaPlayer.start();  
  15.     }  
  16.       
  17.     public   void  stop() {  
  18.         mMediaPlayer.stop();  
  19.     }  
  20.   
  21.     public   void  pause() {  
  22.         mMediaPlayer.pause();  
  23.     }  
  24.   
  25.     public   boolean  isPlaying() {  
  26.         return  mMediaPlayer.isPlaying();  
  27.     }  
  28.       
  29.     public   int  getDuration() {  
  30.         return  mMediaPlayer.getDuration();  
  31.     }  
  32.   
  33.     public   int  getPosition() {  
  34.         return  mMediaPlayer.getCurrentPosition();  
  35.     }  
  36.   
  37.     public   long  seek( long  whereto) {  
  38.         mMediaPlayer.seekTo((int ) whereto);  
  39.         return  whereto;  
  40. }  

  最後,修改AndroidManifest.xml,添加MusicPlaybackService的定義。

  1. <service android:name= ".MusicPlaybackService"  android:exported= "true"  >  
  2.             <intent-filter>  
  3.                  <action android:name="com.ophone.musicplaybackservice"  />  
  4.             </intent-filter>      
  5. </service>  

 六.開始播放歌曲

      MusicPlaybackService準備就緒,我們可以利用它來播放歌曲了。修改MusicListActivity,在 onCreate() 中通過startService()函數啓動MusicPlaybackService,並通過bindService()函數與之綁定。當綁定完成 時,ServiceConnection的 onServiceConnected()接口將被調用。

  1. private  MusicPlaybackService mPlaybackService =  null ;      
  2.       
  3.    private  ServiceConnection mPlaybackConnection =  new  ServiceConnection() {  
  4.         public   void  onServiceConnected(ComponentName className, IBinder service) {    
  5.             mPlaybackService = ((MusicPlaybackService.LocalBinder)service).getService();  
  6.         }  
  7.   
  8.         public   void  onServiceDisconnected(ComponentName className) {  
  9.             mPlaybackService = null ;  
  10.         }  
  11. };  
  12.   
  13. public   void  onCreate(Bundle savedInstanceState) {  
  14.         super .onCreate(savedInstanceState);  
  15.         setContentView(R.layout.list_layout);  
  16.           
  17.         mDBController = ((MusicPlayerDemoApp)getApplication()).getMusicDBController();  
  18.           
  19.         // bind playback service   
  20.         startService(new  Intent( this ,MusicPlaybackService. class ));          
  21.         bindService(new  Intent( this ,MusicPlaybackService. class ), mPlaybackConnection, Context.BIND_AUTO_CREATE);  

      爲MusicListActivity添加點擊事件處理,當用戶點擊一個音樂item時,會開始自動播放該歌曲,當用戶點擊一個item時,onListItemClick()函數會被調用。

  1. protected   void  onListItemClick(ListView l, View v,  int  position,  long  id) {  
  2.         // TODO Auto-generated method stub   
  3.         super .onListItemClick(l, v, position, id);  
  4.           
  5.         if  (mCursor ==  null  ||mCursor.getCount() ==  0 ) {  
  6.             return ;  
  7.         }  
  8.         mCursor.moveToPosition(position);  
  9.         String url = mCursor  
  10.                        .getString(mCursor  
  11.                             .getColumnIndexOrThrow(MediaStore.Audio.Media.DATA));  
  12.         mPlaybackService.setDataSource(url);  
  13.         mPlaybackService.start();  
  14.     }  

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

七. 播放控制-使用Intent和Broadcast Receiver

      目前我們只能播放音樂,還無法控制音樂的播放,暫停,停止,等等,讓我們進一步來完善這個播放程序,給它添加兩個控制按鈕。
修改MusicListActivity的layout文件list_layout.xml如下:

  1. <?xml version= "1.0"  encoding= "UTF-8" ?>  
  2. <LinearLayout  
  3. android:id="@+id/widget1"   
  4. android:layout_width="fill_parent"   
  5. android:layout_height="fill_parent"   
  6. xmlns:android="http://schemas.android.com/apk/res/android"   
  7. android:orientation="vertical"   
  8. >  
  9. <RelativeLayout android:id="@+id/control_panel"   
  10.         android:orientation="horizontal"    
  11.         android:layout_width="fill_parent"   
  12.         android:layout_height="wrap_content"   
  13.         >  
  14.           
  15. <TextView android:id="@+id/show_text"   
  16.         android:layout_width="fill_parent"   
  17.         android:layout_height="wrap_content"   
  18.         android:textSize="20sp"   
  19.         android:text="@string/click_to_play" />  
  20.   
  21. <Button android:id="@+id/play_pause_btn"   
  22.     android:layout_width="100px"   
  23.     android:layout_height="wrap_content"    
  24.     android:layout_alignParentLeft="true"   
  25.     android:visibility="invisible"   
  26.     android:text="@string/play" />      
  27.         
  28. <Button android:id="@+id/stop_btn"   
  29.     android:layout_width="100px"   
  30.     android:layout_height="wrap_content"    
  31.     android:layout_alignParentRight="true"   
  32.     android:visibility="invisible"   
  33.     android:text="@string/stop" />              
  34. </RelativeLayout>  
  35.   
  36.   
  37. <ListView android:id="@id/android:list"   
  38.         android:layout_width="fill_parent"   
  39.         android:layout_height="fill_parent"   
  40.         android:cacheColorHint="#00000000" />  
  41.           
  42. <TextView android:id="@id/android:empty"   
  43.         android:layout_width="fill_parent"   
  44.         android:layout_height="fill_parent"   
  45.         android:textSize="20sp"   
  46.         android:text="@string/no_music" />  
  47. </LinearLayout>  

       在MusicListActivity中,添加兩個按鈕點擊事件的處理程序,通過Button的setOnClickListener()函數,爲 button添加一個Button.OnClickListener,當有點擊事件發生時,Button.OnClickListener的 onClick()接口將被調用。

  1. private  TextView mTextView =  null ;  
  2. private  Button mPlayPauseButton =  null ;  
  3. private  Button mStopButton =  null ;  

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

  1. mTextView = (TextView)findViewById(R.id.show_text);  
  2.         mPlayPauseButton = (Button) findViewById(R.id.play_pause_btn);  
  3.         mStopButton = (Button) findViewById(R.id.stop_btn);  
  4.           
  5.         mPlayPauseButton.setOnClickListener(new  Button.OnClickListener() {  
  6.             public   void  onClick(View v) {  
  7.                 // Perform action on click   
  8.                 if  (mPlaybackService !=  null  && mPlaybackService.isPlaying()) {  
  9.                     mPlaybackService.pause();  
  10.                     mPlayPauseButton.setText(R.string.play);  
  11.                 } else   if  (mPlaybackService !=  null ){  
  12.                     mPlaybackService.start();  
  13.                     mPlayPauseButton.setText(R.string.pause);  
  14.                 }  
  15.             }  
  16.         });  
  17.           
  18.         mStopButton.setOnClickListener(new  Button.OnClickListener() {  
  19.             public   void  onClick(View v) {  
  20.                 // Perform action on click   
  21.                 if  (mPlaybackService !=  null  ) {  
  22.                     mTextView.setVisibility(View.VISIBLE);  
  23.                     mPlayPauseButton.setVisibility(View.INVISIBLE);  
  24.                     mStopButton.setVisibility(View.INVISIBLE);  
  25.                     mPlaybackService.stop();  
  26.                 }  
  27.             }  
  28.         });  

       現在運行程序,我們還看不到這兩個控制按鈕,默認狀態下他們是不可見狀態。程序剛啓動時,默認顯示提示信息。當播放器狀態發生改變,有歌曲進行播放時,我 們顯示控制按鈕,隱藏提示信息。我們使用Intent和BroadCast Receiver來實現這個功能。

      定義準備完畢和播放完畢的Action String 

  1. public   static   final  String PLAYER_PREPARE_END =  "com.ophone.musicplaybackservice.prepared" ;  
  2.     public   static   final  String PLAY_COMPLETED =  "com.ophone.musicplaybackservice.playcompleted" ;  

      播放器狀態發生改變的時候,通過Intent的形式,將消息廣播出去,給mediaplayer添加 MediaPlayer.OnPreparedListener和MediaPlayer.OnCompletionListener,監聽準備完畢和播 放結束的消息。

  1. MediaPlayer.OnCompletionListener mCompleteListener =  new  MediaPlayer.OnCompletionListener() {  
  2.         public   void  onCompletion(MediaPlayer mp) {  
  3.             broadcastEvent(PLAY_COMPLETED);  
  4.         }  
  5.     };  
  6.       
  7.     MediaPlayer.OnPreparedListener mPrepareListener = new  MediaPlayer.OnPreparedListener() {  
  8.         public   void  onPrepared(MediaPlayer mp) {     
  9.             broadcastEvent(PLAYER_PREPARE_END);  
  10.         }  
  11.     };  
  12.       
  13.     private   void  broadcastEvent(String what) {  
  14.         Intent i = new  Intent(what);  
  15.         sendBroadcast(i);  
  16. }  
  17.   
  18. 修改MusicPlaybackService,在mediaplayer中註冊這個兩個Listener:  
  19.   
  20. public   void  onCreate() {  
  21.         super .onCreate();  
  22.           
  23.         mMediaPlayer = new  MediaPlayer();  
  24.         mMediaPlayer.setOnPreparedListener(mPrepareListener);  
  25.         mMediaPlayer.setOnCompletionListener(mCompleteListener);  

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

  1. protected  BroadcastReceiver mPlayerEvtReceiver =  new  BroadcastReceiver() {  
  2.         @Override   
  3.         public   void  onReceive(Context context, Intent intent) {  
  4.             String action = intent.getAction();  
  5.             if  (action.equals(MusicPlaybackService.PLAYER_PREPARE_END)) {  
  6.                 // will begin to play   
  7.                 mTextView.setVisibility(View.INVISIBLE);  
  8.                 mPlayPauseButton.setVisibility(View.VISIBLE);  
  9.                 mStopButton.setVisibility(View.VISIBLE);  
  10.                   
  11.                 mPlayPauseButton.setText(R.string.pause);  
  12.             } else   if (action.equals(MusicPlaybackService.PLAY_COMPLETED)) {  
  13.                 mPlayPauseButton.setText(R.string.play);  
  14.             }  
  15.         }  
  16.     };  
  17.   
  18. 在onCreate()函數中,註冊這個BroadcastReceiver來監聽PLAYER_PREPARE_END 和PLAY_COMPLETED 這兩個信息 ,在onCreate函數中添加下面的代碼:  
  19. IntentFilter filter = new  IntentFilter();  
  20.         filter.addAction(MusicPlaybackService.PLAYER_PREPARE_END);  
  21.         filter.addAction(MusicPlaybackService.PLAY_COMPLETED);  
  22.     registerReceiver(mPlayerEvtReceiver, filter);  

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

給程序加點新功能

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

1.利用Alarm service實現簡單的鬧鈴功能。
  Alarm Service是OPhone平臺提供的一個系統服務。程序可以向Alarm Service註冊一個PendingIntent,當到達註冊時間的時候,Alarm Service會發出這個事先註冊的intent,程序監聽這個intent就可以達到定時的效果。

 1)添加一個BroadcastReceiver

  1. public   class  StartAlarm  extends  BroadcastReceiver {  
  2.         public   void  onReceive(Context context, Intent intent) {  
  3.             // 添加處理程序,啓動播放。   
  4.         }  
  5. }  

  在AndroidManifest.xml中添加定義:

  1. <receiver android:name= ".StartAlarm"  />  

  2)註冊Alarm Service

  1. Intent startIntent =  new  Intent(Context, StartAlarm. class );  
  2.   
  3. PendingIntent startSender = PendingIntent.getBroadcast(  
  4.                 Context, 0 , startIntent,  0 );  
  5.   
  6. // Schedule the alarm!  startTimeMillis 是定時時間   
  7.         AlarmManager am = (AlarmManager)getSystemService(ALARM_SERVICE);  
  8.         am.setRepeating(AlarmManager.RTC_WAKEUP, startTimeMillis,  
  9.                 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:

  1. ContentResolver resolver = ctx.getContentResolver();  
  2.         // Set the flag in the database to mark this as a ringtone   
  3.         Uri ringUri = ContentUris.withAppendedId(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, songId);     
  4.        
  5.         try  {                         
  6.             ContentValues values = new  ContentValues( 2 );  
  7.             values.put(MediaStore.Audio.Media.IS_RINGTONE, "1" );  
  8.             values.put(MediaStore.Audio.Media.IS_ALARM, "1" );  
  9.             values.put(MediaStore.Audio.Media.IS_NOTIFICATION, "1" );              
  10.             resolver.update(ringUri, values, null  null );              
  11.               
  12.         } catch  (UnsupportedOperationException ex) {  
  13.             return ;  
  14.     }  

  第二步,通過android.provider.Settings.Profile的setRingTone接口,設置歌曲爲振鈴:

  1. 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信息。  

  1. Adb shell 登陸到手機或模擬器
  2. Su – 切換到root權限
  3. Chmod 777 /data/misc, 使/data/misc目錄具有讀寫權限
  4. 通過ps命令,找到要調試的程序的pid
  5. Kill -10 pid
  6. 在/data/misc 目錄下,會生成文件名類似heap-dump-xxxxx-pidxxx.hprof的文件。
  7. 通過adb pull 命令將.hprof文件拽到pc端
  8. 使用OPhone SDK提供的hprof-conv工具將OPhone生成的hprof文件轉換成MAT識別的標準格式。例如:
  1. Hprof-conv  heap-dump-xxxxx-pidxxx.hprof  standard-dump-file.hprof  

   9. 使用MAT工具打開 standard-dump-file.hprof, 你將看到類似下圖的分析報告。
     分析報告提供了詳盡的heap信息,同時還指出了可疑的內存泄漏的對象。

       大家可以根據MAT提供的詳細Heap信息,查找漏洞了。

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章