OPhone程序開發入門之音樂播放器
OPhone平臺提供了完整的多媒體解決方案。爲開發者提供了統一的,簡單易用的開發接口。本文首先介紹了OPhone平臺的多媒體框架,然後詳細介紹了 在OPhone平臺上開發音樂播放程序所需的基本知識。通過一步一步構建一個簡單的音樂播放器示例程序,來幫助讀者瞭解具體的開發過程。該示例涵蓋了 Application,Activity,Service,Intent,BroadCast Receiver等基本概念,使讀者對OPhone程序的開發有一個全面的瞭解,進一步鞏固和熟悉這些基本概念。最後介紹瞭如何利用MAT工具分析 OPhone 程序。
本文適合OPhone平臺開發的初學者閱讀。(作者:CMRI 孟釗)
OPhone平臺的多媒體架構
在開始構建我們的示例程序前,先讓我們大概瞭解一下OPhone平臺的多媒體框架。
圖 一
圖一是OPhone平臺的整體框架結構,從圖上我們可以看出OPhone平臺大致可以分成以下幾個層次:
- 最上層是Application層。它包含了主屏,電話,瀏覽器,地址本等核心的應用程序。我們將開發的音樂播放器也屬於這一層。
- 第二層是Application Framework層。這一層爲開發者提供了完整的編程接口。多媒體部分提供了MediaPlayer, MediaRecorder等接口。同時MediaProvider,MediaScanner等系統服務也對媒體文件的管理提供了支持。本文將重點介紹 它們的使用。
- 第三層是Library層, 它由一系列的c/c++庫組成,這些庫的能力通過JNI封裝成java接口,由Application Framework層提供給開發者。多媒體系統庫OpenCore,它是OPhone多媒體的核心,來源於PacketVideo。它非常複雜,提供了完 整的多媒體解決方案。
- 最底層爲Linux Kernel和驅動,負責與硬件的數據交互等。
圖二說明了在OPhone平臺中播放音樂文件時的調用關係。
對於應用程序開發者來說,需要重點學習和關注的是如何使用Appliation Framework層提供給開發者的接口。
音樂媒體信息的管理
在開始構架程序之前,我們需要準備一下必須的基本知識。首先來了解一下在OPhone平臺中應該如何獲取音樂文件的信息以及如何管理這些信息。
OPhone系統提供了 MediaScanner,MediaProvider,MediaStore等接口,並且提供了一套數據庫表格,通過Content Provider的方式提供給用戶。當手機開機或者有SD卡插拔等事件發生時,系統將會自動掃描SD卡和手機內存上的媒體文件,如 audio,video,圖片等,將相應的信息放到定義好的數據庫表格中。在這個程序中,我們不需要關心如何去掃描手機中的文件,只要瞭解如何查詢和使用 這些信息就可以了。
MediaStore中定義了一系列的數據表格,通過ContentResolver提供的查詢接口,我們可以得到各種需要的信息。下面我們重點介紹如何管理SD卡上的音樂文件信息。
先來了解一下ContentResolver的查詢接口:
- Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder);
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);
該命令將返回所有在外部存儲卡上的音樂文件的信息,其中常用的信息如下:
- 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 , 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, null , null , null ,
- 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, null ,
- MediaStore.Audio.Media.ARTIST_ID + "=" + aid, null ,
- MediaStore.Audio.Media.TITLE);
查詢屬於指定專輯(專輯id 爲 aid)的歌曲:
- return query(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, null ,
- MediaStore.Audio.Media.ALBUM_ID + "=" + aid, null ,
- MediaStore.Audio.Media.TITLE);
以上我們重點介紹了音樂媒體信息的查詢方法,對於媒體信息的增刪改等操作主要集中在對播放列表的管理上,也是通過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:
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 ;
- }
- }
- @Override
- public IBinder onBind(Intent intent) {
- // TODO Auto-generated method stub
- return mBinder;
- }
我們繼續完成MusicPlaybackService的基本構架,添加一個MediaPlayer成員,並在onCreate()函數中對其進行初始化,它將負責音樂播放的主要功能。
- private MediaPlayer mMediaPlayer = null ;
- public void onCreate() {
- super .onCreate();
- mMediaPlayer = new MediaPlayer();
- }
構架完成MusicPlaybackService的基本架構後,我們要定義一些常用的控制接口了,其他模塊通過這些接口,可以控制音樂的播放,暫停,停止等功能。
- public void setDataSource(String path) {
- try {
- mMediaPlayer.reset();
- mMediaPlayer.setDataSource(path);
- mMediaPlayer.prepare();
- } catch (IOException e) {
- return ;
- } catch (IllegalArgumentException e) {
- return ;
- }
- }
- 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" >
- <intent-filter>
- <action android:name="com.ophone.musicplaybackservice" />
- </intent-filter>
- </service>
六.開始播放歌曲
MusicPlaybackService準備就緒,我們可以利用它來播放歌曲了。修改MusicListActivity,在 onCreate() 中通過startService()函數啓動MusicPlaybackService,並通過bindService()函數與之綁定。當綁定完成 時,ServiceConnection的 onServiceConnected()接口將被調用。
- private MusicPlaybackService mPlaybackService = null ;
- 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) {
- super .onCreate(savedInstanceState);
- setContentView(R.layout.list_layout);
- mDBController = ((MusicPlayerDemoApp)getApplication()).getMusicDBController();
- // 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) {
- // TODO Auto-generated method stub
- super .onListItemClick(l, v, position, id);
- if (mCursor == null ||mCursor.getCount() == 0 ) {
- return ;
- }
- 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如下:
- <?xml version= "1.0" encoding= "UTF-8" ?>
- <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:layout_width="fill_parent"
- android:layout_height="wrap_content"
- 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_width="100px"
- android:layout_height="wrap_content"
- android:layout_alignParentRight="true"
- android:visibility="invisible"
- android:text="@string/stop" />
- </RelativeLayout>
- <ListView android:id="@id/android:list"
- android:layout_width="fill_parent"
- android:layout_height="fill_parent"
- android:cacheColorHint="#00000000" />
- <TextView android:id="@id/android:empty"
- android:layout_width="fill_parent"
- android:layout_height="fill_parent"
- android:textSize="20sp"
- android:text="@string/no_music" />
- </LinearLayout>
在MusicListActivity中,添加兩個按鈕點擊事件的處理程序,通過Button的setOnClickListener()函數,爲 button添加一個Button.OnClickListener,當有點擊事件發生時,Button.OnClickListener的 onClick()接口將被調用。
- private TextView mTextView = null ;
- private Button mPlayPauseButton = null ;
- private Button mStopButton = null ;
在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) {
- // Perform action on click
- if (mPlaybackService != 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() {
- super .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 {
- public void onReceive(Context context, Intent intent) {
- // 添加處理程序,啓動播放。
- }
- }
在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, "1" );
- values.put(MediaStore.Audio.Media.IS_NOTIFICATION, "1" );
- resolver.update(ringUri, values, null , null );
- } catch (UnsupportedOperationException ex) {
- return ;
- }
第二步,通過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信息,同時還指出了可疑的內存泄漏的對象。
大家可以根據MAT提供的詳細Heap信息,查找漏洞了。