使用MediaStore提供器访问存储的音频文件,将使用android.provider.MediaStore.Audio程序包。
为了使用MediaStore,需要指定想要返回的数据。可以通过使用在android.provider.MediaStore.Audio.Media类中定义的常量创建一个字符串数组来实现该操作。这些常量都是保存在MediaStore中以用于音频的标准字段。
String[] columns = { MediaStore.Audio.Media.DATA,//音频文件的实际路径 MediaStore.Audio.Media._ID,//内部ID MediaStore.Audio.Media.TITLE,//标题 MediaStore.Audio.Media.DISPLAY_NAME,//显示名称 MediaStore.Audio.Media.MIME_TYPE, MediaStore.Audio.Media.ARTIST,//艺术家 MediaStore.Audio.Media.ALBUM,//唱片集 MediaStore.Audio.Media.IS_RINGTONE, MediaStore.Audio.Media.IS_ALARM, MediaStore.Audio.Media.IS_MUSIC, MediaStore.Audio.Media.IS_NOTIFICATION};接着通过调用managedQuery方法来查询MediaStore。managedQuery方法接受内容提供器的Uri作为参数。在当前情况下,该内容提供器是音频MediaStore,对应的Uri是android.provider.MediaStore.Audio.Media.EXTERNAL_CONTENT_URI。这个Uri指定我们想要存储在SD卡上的音频。如果想要存储在内存中的音频文件,那么将使用android.provider.MediaStore.Audio.Media.INTERNAL_CONTENT_URI。
除了指向MediaStore的Uri,managedQuery方法还接受想返回的列数组、一条SQL WHERE子句、用于WHERE子句的值以及一条SQL ORDER BY子句。
String where = android.provider.MediaStore.Audio.Media.ALBUM + "=?"; String whereVal[] = { cursor.getString(cursor.getColumnIndex(MediaStore.Audio.Albums.ALBUM)) }; String orderBy = android.provider.MediaStore.Audio.Media.TITLE; cursor = managedQuery(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, columns, where, whereVal, orderBy);managedQuery方法返回一个Cursor对象。Cursor类允许我们与从数据库查询返回的数据集交互。
要做的第一件事是创建几个变量,以保存一些想要从结果中访问的列的编号。虽然这不是必须的,但是获得索引值将会非常方便,从而不必在每次需要它们的时候调用Cursor的方法。
int fileColumn = cursor.getColumnIndex(MediaStore.Audio.Media.DATA); int mimeTypeColumn = cursor.getColumnIndex(MediaStore.Audio.Media.MIME_TYPE);由MediaStore返回的数据在Cursor对象中可用,且通过行以及列的方式组织起来。通过调用moveToFirst方法和检索它的结果,可以获得返回的第一个结果。如果没有返回任何行,那么该方法将返回一个布尔值false,所以可将它包括在一条if语句中以确保存在数据。
为了获得实际数据,可以调用Cursor上的“getXXX”方法之一,并传入希望检索的列索引。如果数据预期是一个字符串,那么可以调用getString。如果数据预期是一个整数,那么可以调用getInt。对于所有的基本数据类型,都有一个合适的“get”方法。
String audioFilePath = cursor.getString(fileColumn); String mimeType = cursor.getString(mimeTypeColumn);一旦获得了文件的路径和MIME类型就可以使用它们构造意图,以启动内置的音频播放器应用程序,并播放文件(也可以使用MediaPlayer直接播放文件)。为了将音频文件的路径构造成Uri,可以构造一个File对象并调用Uri.fromFile方法来获取Uri。
Intent intent = new Intent(android.content.Intent.ACTION_VIEW); File newFile = new File(audioFilePath); intent.setDataAndType(Uri.fromFile(newFile), mimeType);
以下是完整的源码示例——如何使用唱片集Uri来查询设备上的所有唱片集:
public class AudioBrowser extends ListActivity { Cursor cursor; public static int STATE_SELECT_ALBUM = 0; public static int STATE_SELECT_SONG = 1; int currentState = STATE_SELECT_ALBUM; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); String[] columns = { android.provider.MediaStore.Audio.Albums._ID, android.provider.MediaStore.Audio.Albums.ALBUM };//希望返回的唱片集的名称 cursor = managedQuery(MediaStore.Audio.Albums.EXTERNAL_CONTENT_URI, columns, null, null, null); String[] displayFields = new String[] { MediaStore.Audio.Albums.ALBUM };//显示的Cursor对象中的列名 int[] displayViews = new int[] { android.R.id.text1 }; setListAdapter(new SimpleCursorAdapter(this, android.R.layout.simple_list_item_1, cursor, displayFields, displayViews));//将Cursor对象绑定到ListView对象上 } protected void onListItemClick(ListView l, View v, int position, long id) { if (currentState == STATE_SELECT_ALBUM) { //传入在列表中选定唱片集的位置,同时Cursor对象利用该位置,通过moveToPosition获知是哪个唱片集 if (cursor.moveToPosition(position)) { String[] columns = { MediaStore.Audio.Media.DATA,//音频文件的实际路径 MediaStore.Audio.Media._ID, MediaStore.Audio.Media.TITLE, MediaStore.Audio.Media.DISPLAY_NAME, MediaStore.Audio.Media.MIME_TYPE, }; String where = android.provider.MediaStore.Audio.Media.ALBUM + "=?"; //字符数组,其中每个字符串对应一个使用的“?”符号 String whereVal[] = { cursor.getString(cursor .getColumnIndex(MediaStore.Audio.Albums.ALBUM)) }; String orderBy = android.provider.MediaStore.Audio.Media.TITLE; //查询只属于特定唱片集的媒体文件 cursor = managedQuery( MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, columns, where, whereVal, orderBy); String[] displayFields = new String[] { MediaStore.Audio.Media.DISPLAY_NAME }; int[] displayViews = new int[] { android.R.id.text1 }; setListAdapter(new SimpleCursorAdapter(this, android.R.layout.simple_list_item_1, cursor, displayFields, displayViews)); currentState = STATE_SELECT_SONG; } } else if (currentState == STATE_SELECT_SONG) { if (cursor.moveToPosition(position)) { int fileColumn = cursor .getColumnIndex(MediaStore.Audio.Media.DATA); int mimeTypeColumn = cursor .getColumnIndex(MediaStore.Audio.Media.MIME_TYPE); String audioFilePath = cursor.getString(fileColumn); String mimeType = cursor.getString(mimeTypeColumn); Intent intent = new Intent(android.content.Intent.ACTION_VIEW); File newFile = new File(audioFilePath); intent.setDataAndType(Uri.fromFile(newFile), mimeType); startActivity(intent); } } } }
1.服务
当应用程序不再位于前台且没有正在使用它的活动的时候,为了确保音频继续播放,我们需要创建一个服务。服务是安卓应用程序的一个组件,其用于在后台运行任务,而无须与用户交互。
2. 本地服务与远程服务
安卓中存在几个可用的不同服务类。本地服务(Local Service):作为特定应用程序的一部分存在,而且只能通过该应用程序访问和控制。远程服务(Remote Service):是另一种类型的服务,它们可以与其他应用程序进行通信,由其他应用程序访问和控制。在此,仅介绍使用一个本地服务提供音频播放的功能。
3.本地服务
服务类需要继承android.app.Service类。该类是抽象类,所以为了扩展它,必须实现onBind方法。
public IBinder onBind(Intent intent) { // TODO Auto-generated method stub return msBinder;//* }通常如果只是实现简单服务,并不实现“绑定”的话,可以选择return null。
还有三个表示服务生命周期的方法,onCreate和onDestroy就不用说了,重点说说onStartCommand。每当利用一个匹配服务的意图调用startService时,就会调用onStartCommand方法,因此可能会多次调用它。onStartCommand方法将返回一个整数值,其表示如果结束该服务,那么操作系统应该如何执行操作。可以使用START_STICKY表明如果结束服务,那么将重新启动该服务。
public int onStartCommand(Intent intent, int flags, int startId) { // TODO Auto-generated method stub Log.v(TAG,"onStartCommand"); if(!mediaPlayer.isPlaying()) { mediaPlayer.start(); } return START_STICKY; //return super.onStartCommand(intent, flags, startId); }
插一句:从android2.0中引入了onStartCommand方法,在此之前使用的是onStart方法。onStart方法的参数是一个意图和一个表示startId的整数。它不包括int类型的flags参数,而且没有返回值。如果目标电话在2.0之前运行,那么需要使用onStart方法。
注意,别忘了在清单文件中加入一个条目指定该服务。
下一步,我们还希望能够通过活动来控制服务中的MediaPlayer,而发出命令则显得更为复杂。为了控制MediaPlayer,需要利用bindService方法把该活动与服务绑定在一起(解绑定则使用unbindService)。一旦这样做了,由于活动与服务在相同的进程中运行,因此可以直接调用服务中的方法。如果正在创建一个远程服务,那么必须采取更深入一步的步骤。
//启动音乐服务 playMusicServiceIntent=new Intent(this,MusicService.class); startService(playMusicServiceIntent); //serviceConnection是一个ServiceConnection类型的对象,它是一个接口,用于监控所绑定服务的状态 serviceConnection=new ServiceConnection() { @Override public void onServiceDisconnected(ComponentName name) { // TODO Auto-generated method stub musicService=null; } //注意该方法传入了一个IBinder对象,其实际上是由服务本身创建并提交的 @Override public void onServiceConnected(ComponentName name, IBinder service) { // TODO Auto-generated method stub musicService=((MusicService.MusicServiceBinder)service).getService(); } }; //绑定服务时,需要传入intent和serviceConnection bindService(playMusicServiceIntent, serviceConnection, Context.BIND_AUTO_CREATE);我们在服务类中创建了一个私有内部类,其继承自Binder类,在活动中请求连接服务时,用于返回服务本身
public class MusicServiceBinder extends Binder//* { MusicService getService() { return MusicService.this; } }
现在就搭好了基础,可以向服务中添加任何喜欢的功能,同时通过绑定服务,可以直接调用服务中定义的各种方法。如果不绑定服务的话,那么除了启动和停止服务之外,我们将不能做任何其他的事情。
完整代码示例:
活动中的代码:
//启动音乐服务 playMusicServiceIntent=new Intent(this,MusicService.class); startService(playMusicServiceIntent); serviceConnection=new ServiceConnection() { @Override public void onServiceDisconnected(ComponentName name) { // TODO Auto-generated method stub musicService=null; } @Override public void onServiceConnected(ComponentName name, IBinder service) { // TODO Auto-generated method stub musicService=((MusicService.MusicServiceBinder)service).getService(); } }; //绑定服务 bindService(playMusicServiceIntent, serviceConnection, Context.BIND_AUTO_CREATE); //控制音乐服务的按钮 Musicbtn=(Button) findViewById(R.id.musicOn); Musicbtn.setBackgroundResource(R.drawable.musicon); Musicbtn.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { // TODO Auto-generated method stub if(musicService.IsPlayNow()) { Musicbtn.setBackgroundResource(R.drawable.musicoff); musicService.PauseMusic(); } else { Musicbtn.setBackgroundResource(R.drawable.musicon); musicService.ResumeMusic(); } } });
服务类代码:
public class MusicService extends Service implements OnCompletionListener { static final String TAG="PLAYERSERVICE"; MediaPlayer mediaPlayer; private final IBinder msBinder=new MusicServiceBinder();//* public class MusicServiceBinder extends Binder//* { MusicService getService() { return MusicService.this; } } @Override public void onCreate() { // TODO Auto-generated method stub //super.onCreate(); Log.v(TAG,"onCreate"); mediaPlayer=MediaPlayer.create(this,R.raw.music); mediaPlayer.setOnCompletionListener(this); } @Override public void onDestroy() { // TODO Auto-generated method stub if(mediaPlayer.isPlaying()) { mediaPlayer.stop(); } mediaPlayer.release(); Log.v(TAG,"onDestroy"); //super.onDestroy(); } @Override public int onStartCommand(Intent intent, int flags, int startId) { // TODO Auto-generated method stub Log.v(TAG,"onStartCommand"); if(!mediaPlayer.isPlaying()) { mediaPlayer.start(); } return START_STICKY; //return super.onStartCommand(intent, flags, startId); } @Override public IBinder onBind(Intent intent) { // TODO Auto-generated method stub return msBinder;//* } @Override public void onCompletion(MediaPlayer mediaPlayer) { // TODO Auto-generated method stub //stopSelf(); mediaPlayer.start(); } public void PauseMusic() { if(mediaPlayer.isPlaying()) { mediaPlayer.pause(); } } public void ResumeMusic() { if(!mediaPlayer.isPlaying()) { mediaPlayer.start(); } } public boolean IsPlayNow() { return mediaPlayer.isPlaying(); } }
注意:在创建引导(bootstrap)任务或者通知(notification)任务前请确保通知处理( notification handler)已经被配置好。通知任务(Notification jobs)的介绍
由于正常情况下设备不会主动连接服务器,为了能让任务运行起来,管理员可以创建一个通知任务(Notification jobs)来告知设备去连接设备管理服务器(Device Manager Server)。通常,通知(Notification)是通过无线路由来发送的。管理员也可以用通知任务(Notification jobs)来检查设备是否被正确的配置,以便能成功的连接服务器。另外,通知(Notification)也会在以下情形下发送给基于OMA协议的设备(BaseOMA DM devices):
- 发送一个信息让设备建立初始化一个到服务器的连接,也叫做等待任务的通知(notification for waiting jobs)。
- 发送一个引导信息使设备达到能够连接到服务器并初始化的状态。初始化任务(bootstrap job)负责设备的初始化工作。
- 发送一个OMA客户端提供的信息使设备达到能够连接到服务器并可以初始化的状态。OMA客户端供给任务(OMA Client Provisioning job)负责初始化工作,并且在设备丢失信息的时候保存设备的连接信息,例如如对设备进行一次完整的初始化后。
通知任务对下面的设备生效:
- 一些基于OMA DM 的设备( BaseOMA DM devices)
- 一些Nokia OMA DM 设备
- 拥有塞班操作系统的设备
初始化目录收集工作(inventory collection job)对于Nokia OMA DM和BaseOMA DM 设备是不会发送通知的。
通知任务参数无
发送通知信息设备管理服务器可以通过推送机制来发送通知。服务器必须知道设备的地址,电话号或者一些其他能够和设备通讯的手段。当设备连接到服务器被支配期间,会有个隐式确认( implicit acknowledgment)的成功通知信息。
注意:只有当设备的常规属性面板里面的Bootstrapped参数设置为No的时候,通知任务才会被发送到设备上。
当你使用通知任务(或者是OMA 客户端供给任务、引导任务、短信测试任务)的时候,可以使用Device Manager里面的任意一个通知处理操作(notification handlers)。
Device Manager 支持下面的通知处理:
- WAPPushv12
- 生成 HTTP POST
- OSGi HTTP POST
http://pic.dhe.ibm.com/infocenter/tivihelp/v3r1/topic/com.ibm.websphere.dms.doc/dm/jobs_notify.html#User_Interface_Mode_setting
任务的一个选项-等待任务通知(Notification for waiting jobs)http://pic.dhe.ibm.com/infocenter/tivihelp/v3r1/topic/com.ibm.websphere.dms.doc/dm/jobs_notify.html#Event_notifications
使用桌面部署的WebSphere Everyplace部署http://pic.dhe.ibm.com/infocenter/tivihelp/v3r1/topic/com.ibm.websphere.dms.doc/dm/jobs_notify.html#Using_desktop_WED