OPhone程序开发入门之音乐播放器
2012-01-16 17:27
363 查看
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的查询接口:
view plaincopy to clipboardprint?
Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder);
[java]
view plaincopyprint?
Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder);
Uri:指明要查询的数据库名称加上表的名称,从MediaStore中我们可以找到相应信息的参数,具体请参考开发文档。
Projection: 指定查询数据库表中的哪几列,返回的游标中将包括相应的信息。Null则返回所有信息。
selection: 指定查询条件
selectionArgs:参数selection里有 ?这个符号是,这里可以以实际值代替这个问号。如果selection这个没有?的话,那么这个String数组可以为null。
SortOrder:指定查询结果的排列顺序
查询所有歌曲:
view plaincopy to clipboardprint?
Cursor cursor = query(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, null, null, null, MediaStore.Audio.Media.DEFAULT_SORT_ORDER);
[java]
view plaincopyprint?
Cursor cursor = query(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, null, null, null, MediaStore.Audio.Media.DEFAULT_SORT_ORDER);
该命令将返回所有在外部存储卡上的音乐文件的信息,其中常用的信息如下:
view plaincopy to clipboardprint?
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));
[java]
view plaincopyprint?
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));
查询歌手信息:
view plaincopy to clipboardprint?
Cursor cursor = query(MediaStore.Audio.Artists.EXTERNAL_CONTENT_URI, null, null, null, MediaStore.Audio.Artists.DEFAULT_SORT_ORDER);
[java]
view plaincopyprint?
Cursor cursor = query(MediaStore.Audio.Artists.EXTERNAL_CONTENT_URI, null, null, null, MediaStore.Audio.Artists.DEFAULT_SORT_ORDER);
该命令将返回所有在外部存储卡上的歌手信息,其中常用的信息如下:
view plaincopy to clipboardprint?
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));
[java]
view plaincopyprint?
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));
查询专辑信息:
view plaincopy to clipboardprint?
Cursor cursor = query(MediaStore.Audio.Albums.EXTERNAL_CONTENT_URI, null, null,null, MediaStore.Audio.Albums.DEFAULT_SORT_ORDER);
[java]
view plaincopyprint?
Cursor cursor = query(MediaStore.Audio.Albums.EXTERNAL_CONTENT_URI, null, null,null, MediaStore.Audio.Albums.DEFAULT_SORT_ORDER);
该命令将返回所有在外部存储卡上的专辑信息,其中常用的信息如下:
view plaincopy to clipboardprint?
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));
[java]
view plaincopyprint?
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));
查询播放列表
view plaincopy to clipboardprint?
Cursor cursor = query(MediaStore.Audio.Playlists.EXTERNAL_CONTENT_URI, null, null, null,
MediaStore.Audio.Playlists.DATE_ADDED + " asc");
[java]
view plaincopyprint?
Cursor cursor = query(MediaStore.Audio.Playlists.EXTERNAL_CONTENT_URI, null, null, null,
MediaStore.Audio.Playlists.DATE_ADDED + " asc");
该命令将返回所有在外部存储卡上的专辑信息,其中常用的信息如下:
view plaincopy to clipboardprint?
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));
[java]
view plaincopyprint?
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)的歌曲:
view plaincopy to clipboardprint?
query(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, null,
MediaStore.Audio.Media.ARTIST_ID + "=" + aid, null,
MediaStore.Audio.Media.TITLE);
[java]
view plaincopyprint?
query(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, null,
MediaStore.Audio.Media.ARTIST_ID + "=" + aid, null,
MediaStore.Audio.Media.TITLE);
查询属于指定专辑(专辑id 为 aid)的歌曲:
view plaincopy to clipboardprint?
return query(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, null,
MediaStore.Audio.Media.ALBUM_ID + "=" + aid, null,
MediaStore.Audio.Media.TITLE);
[java]
view plaincopyprint?
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实例。
设置播放源(文件)
准备播放
开始播放
view plaincopy to clipboardprint?
MediaPlayer mp = new MediaPlayer(); mp.setDataSource(file_to_play); mp.prepare(); mp.start();
[java]
view plaincopyprint?
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.
view plaincopy to clipboardprint?
<?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>
[java]
view plaincopyprint?
<?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类中。
view plaincopy to clipboardprint?
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);
[java]
view plaincopyprint?
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()中初始化它。
view plaincopy to clipboardprint?
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;
[java]
view plaincopyprint?
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 右下文字
view plaincopy to clipboardprint?
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)); }
[java]
view plaincopyprint?
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的实例,为获取歌曲信息做准备。
view plaincopy to clipboardprint?
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>
[java]
view plaincopyprint?
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的句柄了。
view plaincopy to clipboardprint?
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; }
[java]
view plaincopyprint?
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()函数中对其进行初始化,它将负责音乐播放的主要功能。
view plaincopy to clipboardprint?
private MediaPlayer mMediaPlayer = null; public void onCreate() { super.onCreate(); mMediaPlayer = new MediaPlayer(); }
[java]
view plaincopyprint?
private MediaPlayer mMediaPlayer = null; public void onCreate() { super.onCreate(); mMediaPlayer = new MediaPlayer(); }
构架完成MusicPlaybackService的基本架构后,我们要定义一些常用的控制接口了,其他模块通过这些接口,可以控制音乐的播放,暂停,停止等功能。
view plaincopy to clipboardprint?
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; }
[java]
view plaincopyprint?
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的定义。
view plaincopy to clipboardprint?
<service android:name=".MusicPlaybackService" android:exported="true" > <intent-filter> <action android:name="com.ophone.musicplaybackservice" /> </intent-filter> </service>
[java]
view plaincopyprint?
<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()接口将被调用。
view plaincopy to clipboardprint?
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);
[java]
view plaincopyprint?
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()函数会被调用。
view plaincopy to clipboardprint?
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(); }
[java]
view plaincopyprint?
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如下:
view plaincopy to clipboardprint?
<?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>
[java]
view plaincopyprint?
<?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()接口将被调用。
view plaincopy to clipboardprint?
private TextView mTextView = null; private Button mPlayPauseButton = null; private Button mStopButton = null;
[java]
view plaincopyprint?
private TextView mTextView = null; private Button mPlayPauseButton = null; private Button mStopButton = null;
在onCreate函数中,增加如下的代码:
view plaincopy to clipboardprint?
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(); } } });
[java]
view plaincopyprint?
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
view plaincopy to clipboardprint?
public static final String PLAYER_PREPARE_END = "com.ophone.musicplaybackservice.prepared"; public static final String PLAY_COMPLETED = "com.ophone.musicplaybackservice.playcompleted";
[java]
view plaincopyprint?
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,监听准备完毕和播放结束的消息。
view plaincopy to clipboardprint?
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);
[java]
view plaincopyprint?
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来处理这两个消息:
view plaincopy to clipboardprint?
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);
[java]
view plaincopyprint?
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
view plaincopy to clipboardprint?
public class StartAlarm extends BroadcastReceiver { public void onReceive(Context context, Intent intent) { // 添加处理程序,启动播放。 } }
[java]
view plaincopyprint?
public class StartAlarm extends BroadcastReceiver { public void onReceive(Context context, Intent intent) { // 添加处理程序,启动播放。 } }
在AndroidManifest.xml中添加定义:
view plaincopy to clipboardprint?
<receiver android:name=".StartAlarm" />
[java]
view plaincopyprint?
<receiver android:name=".StartAlarm" />
2)注册Alarm Service
view plaincopy to clipboardprint?
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);
[java]
view plaincopyprint?
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:
view plaincopy to clipboardprint?
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; }[java]
view plaincopyprint?
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接口,设置歌曲为振铃:
view plaincopy to clipboardprint?
Settings.Profile.setRingTone(resolver, ringUri);
[java]
view plaincopyprint?
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识别的标准格式。例如:
view plaincopy to clipboardprint?
Hprof-conv heap-dump-xxxxx-pidxxx.hprof standard-dump-file.hprof
[java]
view plaincopyprint?
Hprof-conv heap-dump-xxxxx-pidxxx.hprof standard-dump-file.hprof
9. 使用MAT工具打开 standard-dump-file.hprof, 你将看到类似下图的分析报告。
分析报告提供了详尽的heap信息,同时还指出了可疑的内存泄漏的对象。
大家可以根据MAT提供的详细Heap信息,查找漏洞了。
附录:源代码:
uploads/File/MusicPlayerDemo.zip
http://www.ophonesdn.com/article/show/35
程序。
本文适合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的查询接口:
view plaincopy to clipboardprint?
Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder);
[java]
view plaincopyprint?
Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder);
Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder);
Uri:指明要查询的数据库名称加上表的名称,从MediaStore中我们可以找到相应信息的参数,具体请参考开发文档。
Projection: 指定查询数据库表中的哪几列,返回的游标中将包括相应的信息。Null则返回所有信息。
selection: 指定查询条件
selectionArgs:参数selection里有 ?这个符号是,这里可以以实际值代替这个问号。如果selection这个没有?的话,那么这个String数组可以为null。
SortOrder:指定查询结果的排列顺序
查询所有歌曲:
view plaincopy to clipboardprint?
Cursor cursor = query(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, null, null, null, MediaStore.Audio.Media.DEFAULT_SORT_ORDER);
[java]
view plaincopyprint?
Cursor cursor = query(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, null, null, null, MediaStore.Audio.Media.DEFAULT_SORT_ORDER);
Cursor cursor = query(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, null, null, null, MediaStore.Audio.Media.DEFAULT_SORT_ORDER);
该命令将返回所有在外部存储卡上的音乐文件的信息,其中常用的信息如下:
view plaincopy to clipboardprint?
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));
[java]
view plaincopyprint?
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));
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));
查询歌手信息:
view plaincopy to clipboardprint?
Cursor cursor = query(MediaStore.Audio.Artists.EXTERNAL_CONTENT_URI, null, null, null, MediaStore.Audio.Artists.DEFAULT_SORT_ORDER);
[java]
view plaincopyprint?
Cursor cursor = query(MediaStore.Audio.Artists.EXTERNAL_CONTENT_URI, null, null, null, MediaStore.Audio.Artists.DEFAULT_SORT_ORDER);
Cursor cursor = query(MediaStore.Audio.Artists.EXTERNAL_CONTENT_URI, null, null, null, MediaStore.Audio.Artists.DEFAULT_SORT_ORDER);
该命令将返回所有在外部存储卡上的歌手信息,其中常用的信息如下:
view plaincopy to clipboardprint?
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));
[java]
view plaincopyprint?
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));
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));
查询专辑信息:
view plaincopy to clipboardprint?
Cursor cursor = query(MediaStore.Audio.Albums.EXTERNAL_CONTENT_URI, null, null,null, MediaStore.Audio.Albums.DEFAULT_SORT_ORDER);
[java]
view plaincopyprint?
Cursor cursor = query(MediaStore.Audio.Albums.EXTERNAL_CONTENT_URI, null, null,null, MediaStore.Audio.Albums.DEFAULT_SORT_ORDER);
Cursor cursor = query(MediaStore.Audio.Albums.EXTERNAL_CONTENT_URI, null, null,null, MediaStore.Audio.Albums.DEFAULT_SORT_ORDER);
该命令将返回所有在外部存储卡上的专辑信息,其中常用的信息如下:
view plaincopy to clipboardprint?
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));
[java]
view plaincopyprint?
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));
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));
查询播放列表
view plaincopy to clipboardprint?
Cursor cursor = query(MediaStore.Audio.Playlists.EXTERNAL_CONTENT_URI, null, null, null,
MediaStore.Audio.Playlists.DATE_ADDED + " asc");
[java]
view plaincopyprint?
Cursor cursor = query(MediaStore.Audio.Playlists.EXTERNAL_CONTENT_URI, null, null, null,
MediaStore.Audio.Playlists.DATE_ADDED + " asc");
Cursor cursor = query(MediaStore.Audio.Playlists.EXTERNAL_CONTENT_URI, null, null, null, MediaStore.Audio.Playlists.DATE_ADDED + " asc");
该命令将返回所有在外部存储卡上的专辑信息,其中常用的信息如下:
view plaincopy to clipboardprint?
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));
[java]
view plaincopyprint?
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));
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)的歌曲:
view plaincopy to clipboardprint?
query(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, null,
MediaStore.Audio.Media.ARTIST_ID + "=" + aid, null,
MediaStore.Audio.Media.TITLE);
[java]
view plaincopyprint?
query(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, null,
MediaStore.Audio.Media.ARTIST_ID + "=" + aid, null,
MediaStore.Audio.Media.TITLE);
query(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, null, MediaStore.Audio.Media.ARTIST_ID + "=" + aid, null, MediaStore.Audio.Media.TITLE);
查询属于指定专辑(专辑id 为 aid)的歌曲:
view plaincopy to clipboardprint?
return query(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, null,
MediaStore.Audio.Media.ALBUM_ID + "=" + aid, null,
MediaStore.Audio.Media.TITLE);
[java]
view plaincopyprint?
return query(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, null,
MediaStore.Audio.Media.ALBUM_ID + "=" + aid, null,
MediaStore.Audio.Media.TITLE);
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实例。
设置播放源(文件)
准备播放
开始播放
view plaincopy to clipboardprint?
MediaPlayer mp = new MediaPlayer(); mp.setDataSource(file_to_play); mp.prepare(); mp.start();
[java]
view plaincopyprint?
MediaPlayer mp = new MediaPlayer(); mp.setDataSource(file_to_play); mp.prepare(); mp.start();
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.
view plaincopy to clipboardprint?
<?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>
[java]
view plaincopyprint?
<?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>
<?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类中。
view plaincopy to clipboardprint?
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);
[java]
view plaincopyprint?
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);
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()中初始化它。
view plaincopy to clipboardprint?
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;
[java]
view plaincopyprint?
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;
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 右下文字
view plaincopy to clipboardprint?
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)); }
[java]
view plaincopyprint?
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)); }
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的实例,为获取歌曲信息做准备。
view plaincopy to clipboardprint?
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>
[java]
view plaincopyprint?
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>
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的句柄了。
view plaincopy to clipboardprint?
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; }
[java]
view plaincopyprint?
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; }
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()函数中对其进行初始化,它将负责音乐播放的主要功能。
view plaincopy to clipboardprint?
private MediaPlayer mMediaPlayer = null; public void onCreate() { super.onCreate(); mMediaPlayer = new MediaPlayer(); }
[java]
view plaincopyprint?
private MediaPlayer mMediaPlayer = null; public void onCreate() { super.onCreate(); mMediaPlayer = new MediaPlayer(); }
private MediaPlayer mMediaPlayer = null; public void onCreate() { super.onCreate(); mMediaPlayer = new MediaPlayer(); }
构架完成MusicPlaybackService的基本架构后,我们要定义一些常用的控制接口了,其他模块通过这些接口,可以控制音乐的播放,暂停,停止等功能。
view plaincopy to clipboardprint?
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; }
[java]
view plaincopyprint?
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; }
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的定义。
view plaincopy to clipboardprint?
<service android:name=".MusicPlaybackService" android:exported="true" > <intent-filter> <action android:name="com.ophone.musicplaybackservice" /> </intent-filter> </service>
[java]
view plaincopyprint?
<service android:name=".MusicPlaybackService" android:exported="true" > <intent-filter> <action android:name="com.ophone.musicplaybackservice" /> </intent-filter> </service>
<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()接口将被调用。
view plaincopy to clipboardprint?
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);
[java]
view plaincopyprint?
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);
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()函数会被调用。
view plaincopy to clipboardprint?
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(); }
[java]
view plaincopyprint?
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(); }
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如下:
view plaincopy to clipboardprint?
<?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>
[java]
view plaincopyprint?
<?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>
<?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()接口将被调用。
view plaincopy to clipboardprint?
private TextView mTextView = null; private Button mPlayPauseButton = null; private Button mStopButton = null;
[java]
view plaincopyprint?
private TextView mTextView = null; private Button mPlayPauseButton = null; private Button mStopButton = null;
private TextView mTextView = null; private Button mPlayPauseButton = null; private Button mStopButton = null;
在onCreate函数中,增加如下的代码:
view plaincopy to clipboardprint?
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(); } } });
[java]
view plaincopyprint?
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(); } } });
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
view plaincopy to clipboardprint?
public static final String PLAYER_PREPARE_END = "com.ophone.musicplaybackservice.prepared"; public static final String PLAY_COMPLETED = "com.ophone.musicplaybackservice.playcompleted";
[java]
view plaincopyprint?
public static final String PLAYER_PREPARE_END = "com.ophone.musicplaybackservice.prepared"; public static final String PLAY_COMPLETED = "com.ophone.musicplaybackservice.playcompleted";
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,监听准备完毕和播放结束的消息。
view plaincopy to clipboardprint?
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);
[java]
view plaincopyprint?
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);
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来处理这两个消息:
view plaincopy to clipboardprint?
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);
[java]
view plaincopyprint?
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);
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
view plaincopy to clipboardprint?
public class StartAlarm extends BroadcastReceiver { public void onReceive(Context context, Intent intent) { // 添加处理程序,启动播放。 } }
[java]
view plaincopyprint?
public class StartAlarm extends BroadcastReceiver { public void onReceive(Context context, Intent intent) { // 添加处理程序,启动播放。 } }
public class StartAlarm extends BroadcastReceiver { public void onReceive(Context context, Intent intent) { // 添加处理程序,启动播放。 } }
在AndroidManifest.xml中添加定义:
view plaincopy to clipboardprint?
<receiver android:name=".StartAlarm" />
[java]
view plaincopyprint?
<receiver android:name=".StartAlarm" />
<receiver android:name=".StartAlarm" />
2)注册Alarm Service
view plaincopy to clipboardprint?
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);
[java]
view plaincopyprint?
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);
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:
view plaincopy to clipboardprint?
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; }[java]
view plaincopyprint?
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; }
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接口,设置歌曲为振铃:
view plaincopy to clipboardprint?
Settings.Profile.setRingTone(resolver, ringUri);
[java]
view plaincopyprint?
Settings.Profile.setRingTone(resolver, ringUri);
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识别的标准格式。例如:
view plaincopy to clipboardprint?
Hprof-conv heap-dump-xxxxx-pidxxx.hprof standard-dump-file.hprof
[java]
view plaincopyprint?
Hprof-conv heap-dump-xxxxx-pidxxx.hprof standard-dump-file.hprof
Hprof-conv heap-dump-xxxxx-pidxxx.hprof standard-dump-file.hprof
9. 使用MAT工具打开 standard-dump-file.hprof, 你将看到类似下图的分析报告。
分析报告提供了详尽的heap信息,同时还指出了可疑的内存泄漏的对象。
大家可以根据MAT提供的详细Heap信息,查找漏洞了。
附录:源代码:
uploads/File/MusicPlayerDemo.zip
http://www.ophonesdn.com/article/show/35
相关文章推荐
- OPhone程序开发入门之音乐播放器
- OPhone程序开发入门之音乐播放器
- 图文解说OpenCV开发一 - 环境配置和入门程序详解
- .NET Remoting程序开发入门篇(二)
- hadoop编程入门学习笔记-3 开发MapReduce程序
- iPhone开发入门(11)— 在App Store上发布程序
- JavaScript极其简单入门——小程序开发前准备
- XAML开发WPF程序入门之开发环境介绍
- 开发宝典:程序开发新手入门之D语言
- iPhone程序开发入门条件
- J2ME程序开发新手入门九大要点
- Winsock开发网络通信程序的经典入门
- 【android开发入门】第一个android程序遇到的问题
- 微信小程序开发入门教程
- 使用C#开发SmartPhone程序入门
- 新手入门——Ophone(Android)开发项目文件结构
- [置顶] 微信小程序开发入门篇
- iPhone开发入门3 - 创建一个UITable View程序
- 小程序开发入门
- Android入门(4) 开发第一个Android程序