OPhone程序开发入门之音乐播放器

OPhone平台提供了完整的多媒体解决方案。为开发者提供了统一的,简单易用的开发接口。本文首先介绍了OPhone平台的多媒体框架,然后详细介绍了 在OPhone平台上开发音乐播放程序所需的基本知识。通过一步一步构建一个简单的音乐播放器示例程序,来帮助读者了解具体的开发过程。该示例涵盖了 Application,Activity,Service,Intent,BroadCast Receiver等基本概念,使读者对OPhone程序的开发有一个全面的了解,进一步巩固和熟悉这些基本概念。最后介绍了如何利用MAT工具分析 OPhone 程序。

本文适合OPhone平台开发的初学者阅读。(作者:CMRI 孟钊)

OPhone平台的多媒体架构

在开始构建我们的示例程序前,先让我们大概了解一下OPhone平台的多媒体框架。

图   一

图一是OPhone平台的整体框架结构,从图上我们可以看出OPhone平台大致可以分成以下几个层次:

  1. 最上层是Application层。它包含了主屏,电话,浏览器,地址本等核心的应用程序。我们将开发的音乐播放器也属于这一层。
  2. 第二层是Application Framework层。这一层为开发者提供了完整的编程接口。多媒体部分提供了MediaPlayer, MediaRecorder等接口。同时MediaProvider,MediaScanner等系统服务也对媒体文件的管理提供了支持。本文将重点介绍 它们的使用。
  3. 第三层是Library层, 它由一系列的c/c++库组成,这些库的能力通过JNI封装成java接口,由Application Framework层提供给开发者。多媒体系统库OpenCore,它是OPhone多媒体的核心,来源于PacketVideo。它非常复杂,提供了完 整的多媒体解决方案。
  4. 最底层为Linux Kernel和驱动,负责与硬件的数据交互等。

  图二说明了在OPhone平台中播放音乐文件时的调用关系。

  对于应用程序开发者来说,需要重点学习和关注的是如何使用Appliation Framework层提供给开发者的接口。

音乐媒体信息的管理

  在开始构架程序之前,我们需要准备一下必须的基本知识。首先来了解一下在OPhone平台中应该如何获取音乐文件的信息以及如何管理这些信息。

  OPhone系统提供了 MediaScanner,MediaProvider,MediaStore等接口,并且提供了一套数据库表格,通过Content Provider的方式提供给用户。当手机开机或者有SD卡插拔等事件发生时,系统将会自动扫描SD卡和手机内存上的媒体文件,如 audio,video,图片等,将相应的信息放到定义好的数据库表格中。在这个程序中,我们不需要关心如何去扫描手机中的文件,只要了解如何查询和使用 这些信息就可以了。

  MediaStore中定义了一系列的数据表格,通过ContentResolver提供的查询接口,我们可以得到各种需要的信息。下面我们重点介绍如何管理SD卡上的音乐文件信息。

  先来了解一下ContentResolver的查询接口:

view plain  copy to clipboard  print  ?
  1. Cursor  query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder);

Uri:指明要查询的数据库名称加上表的名称,从MediaStore中我们可以找到相应信息的参数,具体请参考开发文档。 
        Projection: 指定查询数据库表中的哪几列,返回的游标中将包括相应的信息。Null则返回所有信息。
        selection: 指定查询条件
        selectionArgs:参数selection里有 ?这个符号是,这里可以以实际值代替这个问号。如果selection这个没有?的话,那么这个String数组可以为null。
        SortOrder:指定查询结果的排列顺序

  查询所有歌曲:

view plain  copy to clipboard  print  ?
  1. Cursor cursor = query(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,  null ,
  2. null ,  null , MediaStore.Audio.Media.DEFAULT_SORT_ORDER);

  该命令将返回所有在外部存储卡上的音乐文件的信息,其中常用的信息如下:

view plain  copy to clipboard  print  ?
  1. MediaStore.Audio.Media._ID:歌曲ID
  2. Int id = cursor.getInt(cursor.getColumnIndexOrThrow(MediaStore.Audio.Media._ID));
  3. MediaStore.Audio.Media.TITLE:歌曲的名称
  4. String tilte = cursor.getString(cursor.getColumnIndexOrThrow(MediaStore.Audio.Media.TITLE));
  5. MediaStore.Audio.Media.ALBUM :歌曲的专辑名
  6. String album = cursor.getString(cursor.getColumnIndexOrThrow(MediaStore.Audio.Media.ALBUM));
  7. MediaStore.Audio.Media.ARTIST:歌曲的歌手名
  8. String artist = cursor.getString(cursor.getColumnIndexOrThrow(MediaStore.Audio.Media.ARTIST));
  9. MediaStore.Audio.Media.DATA:歌曲文件的路径
  10. String url = cursor.getString(cursor.getColumnIndexOrThrow(MediaStore.Audio.Media.DATA));
  11. MediaStore.Audio.Media.DURATION:歌曲的总播放时长
  12. Int duration = cursor.getInt(cursor.getColumnIndexOrThrow(MediaStore.Audio.Media.DURATION));
  13. MediaStore.Audio.Media.SIZE: 歌曲文件的大小
  14. Int size = cursor.getLong(cursor.getColumnIndexOrThrow(MediaStore.Audio.Media.SIZE));

查询歌手信息:

view plain  copy to clipboard  print  ?
  1. Cursor cursor = query(MediaStore.Audio.Artists.EXTERNAL_CONTENT_URI,  null ,  null ,  null ,
  2. MediaStore.Audio.Artists.DEFAULT_SORT_ORDER);

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

view plain  copy to clipboard  print  ?
  1. MediaStore.Audio.Artists._ID:歌手id
  2. Int id = cursor.getInt(cursor.getColumnIndexOrThrow(MediaStore.Audio.Artists._ID));
  3. MediaStore.Audio.Artists.ARTIST :歌手姓名
  4. String name = cursor.getString(cursor.getColumnIndexOrThrow(MediaStore.Audio.Artists.ARTIST));
  5. MediaStore.Audio.Artists.NUMBER_OF_ALBUMS: 共有多少该歌手的专辑
  6. Int numOfAlbum = cursor.getInt(cursor.getColumnIndexOrThrow(MediaStore.Audio.Artists.NUMBER_OF_ALBUMS));
  7. MediaStore.Audio.Artists.NUMBER_OF_TRACKS: 共有多少该歌手的歌曲
  8. Int numOfSong = cursor.getInt(cursor.getColumnIndexOrThrow(MediaStore.Audio.Artists.NUMBER_OF_TRACKS));

  查询专辑信息:

view plain  copy to clipboard  print  ?
  1. Cursor cursor = query(MediaStore.Audio.Albums.EXTERNAL_CONTENT_URI,  null ,  null , null ,
  2. MediaStore.Audio.Albums.DEFAULT_SORT_ORDER);

    该命令将返回所有在外部存储卡上的专辑信息,其中常用的信息如下:

view plain  copy to clipboard  print  ?
  1. MediaStore.Audio.Albums._ID :专辑id
  2. Int id = cursor.getInt(cursor.getColumnIndexOrThrow(MediaStore.Audio.Albums._ID));
  3. MediaStore.Audio.Albums.ALBUM:专辑名称
  4. String name = cursor.getString(cursor.getColumnIndexOrThrow(MediaStore.Audio.Albums.ALBUM));
  5. MediaStore.Audio.Albums.NUMBER_OF_SONGS:共用多少歌曲属于该专辑
  6. Int numOfSong = cursor.getInt(cursor.getColumnIndexOrThrow(MediaStore.Audio.Albums.NUMBER_OF_SONGS));

  查询播放列表

view plain  copy to clipboard  print  ?
  1. Cursor cursor = query(MediaStore.Audio.Playlists.EXTERNAL_CONTENT_URI,  null ,  null ,  null ,
  2. MediaStore.Audio.Playlists.DATE_ADDED + " asc" );

该命令将返回所有在外部存储卡上的专辑信息,其中常用的信息如下:

view plain  copy to clipboard  print  ?
  1. MediaStore.Audio.Playlists._ID :播放列表id
  2. Int id = cursor.getInt(cursor.getColumnIndexOrThrow(MediaStore.Audio.Playlists._ID));
  3. MediaStore.Audio.Playlists.NAME:播放列表名称
  4. String name = cursor.getString(cursor.getColumnIndexOrThrow(MediaStore.Audio.Playlists.NAME));
  5. MediaStore.Audio.Playlists.DATE_ADDED :添加时间
  6. long  dateAdded = cursor.getLong(cursor.getColumnIndexOrThrow(MediaStore.Audio.Playlists.DATE_ADDED));
  7. MediaStore.Audio.Playlists.DATE_MODIFIED :修改时间
  8. long  dateModified = cursor.getLong(cursor.getColumnIndexOrThrow(MediaStore.Audio.Playlists.DATE_MODIFIED));

 通过组合这些查询结果,指定查询条件,用户可以很方便的查询指定的媒体信息,比如:查询属于指定歌手(歌手id 为 aid)的歌曲:

view plain  copy to clipboard  print  ?
  1. query(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,  null ,
  2. MediaStore.Audio.Media.ARTIST_ID + "="  + aid,  null ,
  3. MediaStore.Audio.Media.TITLE);

查询属于指定专辑(专辑id 为 aid)的歌曲:

view plain  copy to clipboard  print  ?
  1. return  query(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,  null ,
  2. MediaStore.Audio.Media.ALBUM_ID + "="  + aid,  null ,
  3. MediaStore.Audio.Media.TITLE);

  以上我们重点介绍了音乐媒体信息的查询方法,对于媒体信息的增删改等操作主要集中在对播放列表的管理上,也是通过Content Resolver的insert,update,delete等接口来实现的。只要搞清楚了各个参数的含义,相应URI以及各个字段的义,很容易实现。由 于篇幅原因,我们不再详细介绍,有兴趣的朋友可以查看OPhone开发文档。

音乐播放

  音乐文件的播放功能是由MediaPlayer类实现的,MediaPlayer提供了常用的接口,比如播放,暂停,停止,快速定位等。

播放音乐文件的基本调用流程:

  1. 生成MediaPlayer实例。
  2. 设置播放源(文件)
  3. 准备播放
  4. 开始播放
view plain  copy to clipboard  print  ?
  1. MediaPlayer mp =  new  MediaPlayer();
  2. mp.setDataSource(file_to_play);
  3. mp.prepare();
  4. mp.start();

以上代码即可以完成最简单的音乐播放功能。

除了MediaPlayer类,我们还需要注意几个播放器件Listener的使用,它们提供了播放器的更多的状态信息。

1.MediaPlayer.OnBufferingUpdateListener

当播放网络上的媒体文件或者流媒体时   MediaPlayer.OnBufferingUpdateListener 的onBufferingUpdate(MediaPlayer mp, int percent)接口函数会被回调,通知当前的缓冲进度信息。

通过setOnBufferingUpdateListener(MediaPlayer.OnBufferingUpdateListener listener) 函数来注册该Listener

2.MediaPlayer.OnCompletionListener

当前歌曲播放结束后,MediaPlayer.OnCompletionListener的 onCompletion(MediaPlayer mp) 接口会被回调,通知歌曲结束事件。
     通过setOnCompletionListener(MediaPlayer.OnCompletionListener listener) 函数来注册该监听器

3.MediaPlayer.OnErrorListener

当由于某种原因,MediaPlayer进入错误状态时,MediaPlayer.OnBufferingUpdateListener的 onError(MediaPlayer mp, int what, int extra)接口会被回调,通知错误信息。此时MediaPlayer 应该调用reset()函数,将MediaPlayer重新置于idle状态。如果发生无法回复的错误,需要重新获取MediaPlayer的实例。

4.MediaPlayer.OnPreparedListener

当播放网络媒体文件或流媒体时,播放器的准备时间较长,播放器准备完毕可以开始播放时,MediaPlayer.OnPreparedListener的onPrepared(MediaPlayer mp)接口会被回调,通知该信息。
当播放器需要支持播放流媒体或者网络媒体文件时,建议使用prepareAsync()接口调用来准备播放器,同时通过 MediaPlayer.OnPreparedListener来监听prepared信息。这样可以避免因为网络等因素造成的MediaPlayer准 备时间过长进而导致程序长时间无响应。    

构建音乐播放器程序

在学习了媒体信息管理和媒体播放的基本内容后,我们现在可以开始动手构建我们的简单播放器示例程序了。

一.创建工程
       在Eclipse开发环境中创建一个新的Android Project.
File > New > Android Project.
  设置工程名为MusicPlayerDemo, 设置packages名为   com.ophone

二.指定程序的Application,添加MusicPlayerDemoApp
        添加MusicPlayerDemoApp类,它继承自 android.app.Application。
         Application类用来存储程序的状态,它存在于整个程序的生命周期之中。
         修改AndroidManifest.xml如下,指定MusicPlayerDemoApp为示例程序的Application.

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

     我们需要注意Application的两个函数: onCreate() 和 onTerminate(). 当程序开始运行时,onCreate()函数会首先被调用,此时没有任何其他的对象在运行,在这里我们可以进行一些初始化的工作。当程序结束时, onTerminate()函数会被调用,程序进程将会退出,我们可以在此做一些最终的清理工作。需要注意的是,当因为系统资源紧张等问题,程序被系统 kill的时候,onTerminate()不会被调用到,程序将直接退出。
  稍后我们再来修改MusicPlayerDemoApp,先往下继续。

三.管理音乐信息的类MusicDBController
  为了使接口整洁,便于管理和使用,我们将在第三章介绍的         查询管理音乐信息的方法统一封装在MusicDBController类中。

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

  MusicDBController采用单例模式,使程序中只有唯一的实例。我们传入MusicPlayerDemoApp 作为Context生成Content Resolver,用来查询媒体库。

  现在,我们修改MusicPlayerDemoApp,添加一个MusicDBController的成员,并在onCreate()中初始化它。

view plain  copy to clipboard  print  ?
  1. private  MusicDBController mDBContorller =  null ;
  2. public   void  onCreate() {
  3. // TODO Auto-generated method stub
  4. super .onCreate();
  5. // init MusicDBController
  6. mDBContorller = MusicDBController.getInstance(this );
  7. }
  8. 并且提供一个获取MusicDBController的接口:
  9. public  MusicDBController getMusicDBController(){
  10. 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 plain  copy to clipboard  print  ?
  1. public   void  bindView(View view, Context context, Cursor cursor) {
  2. super .bindView(view, context, cursor);
  3. TextView titleView = (TextView) view.findViewById(android.R.id.text1);
  4. TextView artistView = (TextView) view.findViewById(android.R.id.text2);
  5. TextView durationView = (TextView) view.findViewById(android.R.id.text3);
  6. ImageView imageView = (ImageView) view.findViewById(android.R.id.listicon1);
  7. // Set icon
  8. imageView.setImageResource(R.drawable.cmcc_list_music);
  9. // Set track name
  10. titleView.setText(cursor.getString(cursor.getColumnIndexOrThrow(MediaStore.Audio.Media.TITLE)));
  11. // Set artist name
  12. artistView.setText(cursor.getString(cursor.getColumnIndexOrThrow(MediaStore.Audio.Media.ARTIST)));
  13. // Set duration
  14. int  duration = cursor.getInt(cursor.getColumnIndexOrThrow(MediaStore.Audio.Media.DURATION));
  15. durationView.setText(makeTimeString(duration));
  16. }

 注意,上面这段代码中的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 plain  copy to clipboard  print  ?
  1. private  MusicDBController mDBController =  null ;
  2. /** Called when the activity is first created. */
  3. @Override
  4. public   void  onCreate(Bundle savedInstanceState) {
  5. super .onCreate(savedInstanceState);
  6. setContentView(R.layout.main);
  7. mDBController = ((MusicPlayerDemoApp)getApplication()).getMusicDBController();
  8. }
  9. 通过MusicListAdapter,我们将从MusicDBController中拿到的媒体库信息,绑定到ListView,我们在onResume()完成这个工作。
  10. protected   void  onResume() {
  11. super .onResume();
  12. mCursor = mDBController.getAllSongs();
  13. MusicListAdapter adapter = new  MusicListAdapter( this , android.R.layout.cmcc_list_5, mCursor,  new  String[]{},  new   int []{});
  14. setListAdapter(adapter);
  15. }
  16. 将MusicListActivity添加到AndroidManifest.xml中
  17. <activity android:name=".MusicListActivity" >
  18. <intent-filter>
  19. <action android:name="android.intent.action.MAIN"  />
  20. <category android:name="android.intent.category.LAUNCHER"  />
  21. </intent-filter>
  22. </activity>

  现在运行一下我们的程序,它已经可以展现给你媒体库的音乐列表了。

  同样的,仿照上面的过程,我们还可以添加展示专辑列表,艺术家列表等等Activity,我们就不再一一介绍了。

五.后台播放-使用Service

现在我们需要考虑如何来播放这些媒体库中的文件了。我们希望当用户退出这个程序界面后,我们的程序仍然能够继续播放歌曲,比如用户在读邮件时,可以听听音 乐。为了达到后台播放的效果,需要使用Service。当程序的所有Activity都退出后,Service仍然可以在后台运行。在这个示例中我们使用 Local Service,它与应用程序运行在同一个进程中。(我们甚至可以不使用bind service就直接获得它的句柄,调用它所提供的函数。)

  首先,创建一个MusicPlaybackService类,它继承自android.app.Service,重载onBind方法,返回自 定义的LocalBinder,通过LocalBinder的getService()方法就可以获得MusicPlaybackService的句柄 了。

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

  我们继续完成MusicPlaybackService的基本构架,添加一个MediaPlayer成员,并在onCreate()函数中对其进行初始化,它将负责音乐播放的主要功能。

view plain  copy to clipboard  print  ?
  1. private  MediaPlayer mMediaPlayer =  null ;
  2. public   void  onCreate() {
  3. super .onCreate();
  4. mMediaPlayer = new  MediaPlayer();
  5. }

  构架完成MusicPlaybackService的基本架构后,我们要定义一些常用的控制接口了,其他模块通过这些接口,可以控制音乐的播放,暂停,停止等功能。

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

  最后,修改AndroidManifest.xml,添加MusicPlaybackService的定义。

view plain  copy to clipboard  print  ?
  1. <service android:name= ".MusicPlaybackService"  android:exported= "true"  >
  2. <intent-filter>
  3. <action android:name="com.ophone.musicplaybackservice"  />
  4. </intent-filter>
  5. </service>

六.开始播放歌曲

MusicPlaybackService准备就绪,我们可以利用它来播放歌曲了。修改MusicListActivity,在 onCreate() 中通过startService()函数启动MusicPlaybackService,并通过bindService()函数与之绑定。当绑定完成 时,ServiceConnection的 onServiceConnected()接口将被调用。

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

为MusicListActivity添加点击事件处理,当用户点击一个音乐item时,会开始自动播放该歌曲,当用户点击一个item时,onListItemClick()函数会被调用。

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

现在赶紧运行一下程序吧,看看是不是已经可以播放音乐了呢。

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

目前我们只能播放音乐,还无法控制音乐的播放,暂停,停止,等等,让我们进一步来完善这个播放程序,给它添加两个控制按钮。
修改MusicListActivity的layout文件list_layout.xml如下:

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

在MusicListActivity中,添加两个按钮点击事件的处理程序,通过Button的setOnClickListener()函数,为 button添加一个Button.OnClickListener,当有点击事件发生时,Button.OnClickListener的 onClick()接口将被调用。

view plain  copy to clipboard  print  ?
  1. private  TextView mTextView =  null ;
  2. private  Button mPlayPauseButton =  null ;
  3. private  Button mStopButton =  null ;

在onCreate函数中,增加如下的代码:

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

现在运行程序,我们还看不到这两个控制按钮,默认状态下他们是不可见状态。程序刚启动时,默认显示提示信息。当播放器状态发生改变,有歌曲进行播放时,我 们显示控制按钮,隐藏提示信息。我们使用Intent和BroadCast Receiver来实现这个功能。

定义准备完毕和播放完毕的Action String

view plain  copy to clipboard  print  ?
  1. public   static   final  String PLAYER_PREPARE_END =  "com.ophone.musicplaybackservice.prepared" ;
  2. public   static   final  String PLAY_COMPLETED =  "com.ophone.musicplaybackservice.playcompleted" ;

播放器状态发生改变的时候,通过Intent的形式,将消息广播出去,给mediaplayer添加 MediaPlayer.OnPreparedListener和MediaPlayer.OnCompletionListener,监听准备完毕和播 放结束的消息。

view plain  copy to clipboard  print  ?
  1. MediaPlayer.OnCompletionListener mCompleteListener =  new  MediaPlayer.OnCompletionListener() {
  2. public   void  onCompletion(MediaPlayer mp) {
  3. broadcastEvent(PLAY_COMPLETED);
  4. }
  5. };
  6. MediaPlayer.OnPreparedListener mPrepareListener = new  MediaPlayer.OnPreparedListener() {
  7. public   void  onPrepared(MediaPlayer mp) {
  8. broadcastEvent(PLAYER_PREPARE_END);
  9. }
  10. };
  11. private   void  broadcastEvent(String what) {
  12. Intent i = new  Intent(what);
  13. sendBroadcast(i);
  14. }
  15. 修改MusicPlaybackService,在mediaplayer中注册这个两个Listener:
  16. public   void  onCreate() {
  17. super .onCreate();
  18. mMediaPlayer = new  MediaPlayer();
  19. mMediaPlayer.setOnPreparedListener(mPrepareListener);
  20. mMediaPlayer.setOnCompletionListener(mCompleteListener);

在MusicListActivity中,我们定义一个BroadcastReceiver来处理这两个消息:

view plain  copy to clipboard  print  ?
  1. protected  BroadcastReceiver mPlayerEvtReceiver =  new  BroadcastReceiver() {
  2. @Override
  3. public   void  onReceive(Context context, Intent intent) {
  4. String action = intent.getAction();
  5. if  (action.equals(MusicPlaybackService.PLAYER_PREPARE_END)) {
  6. // will begin to play
  7. mTextView.setVisibility(View.INVISIBLE);
  8. mPlayPauseButton.setVisibility(View.VISIBLE);
  9. mStopButton.setVisibility(View.VISIBLE);
  10. mPlayPauseButton.setText(R.string.pause);
  11. } else   if (action.equals(MusicPlaybackService.PLAY_COMPLETED)) {
  12. mPlayPauseButton.setText(R.string.play);
  13. }
  14. }
  15. };
  16. 在onCreate()函数中,注册这个BroadcastReceiver来监听PLAYER_PREPARE_END 和PLAY_COMPLETED 这两个信息 ,在onCreate函数中添加下面的代码:
  17. IntentFilter filter = new  IntentFilter();
  18. filter.addAction(MusicPlaybackService.PLAYER_PREPARE_END);
  19. filter.addAction(MusicPlaybackService.PLAY_COMPLETED);
  20. registerReceiver(mPlayerEvtReceiver, filter);

OK,现在我们的音乐播放器已经成型了,马上运行一下吧。

给程序加点新功能

  下面介绍的功能,在我们的示例代码中并没有实现,如果您感兴趣的话,可以按照下文介绍的大概步骤,添加到程序中,他们其实都很简单。

1.利用Alarm service实现简单的闹铃功能。
  Alarm Service是OPhone平台提供的一个系统服务。程序可以向Alarm Service注册一个PendingIntent,当到达注册时间的时候,Alarm Service会发出这个事先注册的intent,程序监听这个intent就可以达到定时的效果。

 1)添加一个BroadcastReceiver

view plain  copy to clipboard  print  ?
  1. public   class  StartAlarm  extends  BroadcastReceiver {
  2. public   void  onReceive(Context context, Intent intent) {
  3. // 添加处理程序,启动播放。
  4. }
  5. }

  在AndroidManifest.xml中添加定义:

view plain  copy to clipboard  print  ?
  1. <receiver android:name= ".StartAlarm"  />

  2)注册Alarm Service

view plain  copy to clipboard  print  ?
  1. Intent startIntent =  new  Intent(Context, StartAlarm. class );
  2. PendingIntent startSender = PendingIntent.getBroadcast(
  3. Context, 0 , startIntent,  0 );
  4. // Schedule the alarm!  startTimeMillis 是定时时间
  5. AlarmManager am = (AlarmManager)getSystemService(ALARM_SERVICE);
  6. am.setRepeating(AlarmManager.RTC_WAKEUP, startTimeMillis,
  7. 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 plain  copy to clipboard  print  ?
  1. ContentResolver resolver = ctx.getContentResolver();
  2. // Set the flag in the database to mark this as a ringtone
  3. Uri ringUri = ContentUris.withAppendedId(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, songId);
  4. try  {
  5. ContentValues values = new  ContentValues( 2 );
  6. values.put(MediaStore.Audio.Media.IS_RINGTONE, "1" );
  7. values.put(MediaStore.Audio.Media.IS_ALARM, "1" );
  8. values.put(MediaStore.Audio.Media.IS_NOTIFICATION, "1" );
  9. resolver.update(ringUri, values, null ,  null );
  10. } catch  (UnsupportedOperationException ex) {
  11. return ;
  12. }

  第二步,通过android.provider.Settings.Profile的setRingTone接口,设置歌曲为振铃:

view plain  copy to clipboard  print  ?
  1. Settings.Profile.setRingTone(resolver, ringUri);

  现在给自己打个电话试试看,是不是振铃已经起作用了

  使用MAT分析OPhone程序
我们的示例代码已经完成了,大家可以按照上文的步骤自己一步一步来构造自己的音乐播放器,也可以使用附录的源代码包,将工程导入进Eclipse直接体验一下。最后和大家分享一下使用MAT分析OPhone程序的方法。

  通常来说我们调试OPhone程序有两个最常见的方法,一,利用OPhone平台提供的android.util.Log通过log信息来分析 错误发生的原因。 二,通过设置断点,一步一步的跟踪程序发现问题。这两个方法非常有效,介绍相关方法的文章也很多,大家google一下就找到了。
  还有一类常见的问题就是Memory Leak。对内存泄漏这类问题,以上两种方法不是很有效,在DDMS工具里面,我们也基本上只能查看到Heap的使用情况,对分析问题帮助不大。我们可以 利用Eclipse MAT (Memory Analyzer Tool)工具来分析此类问题。Eclipse Memory Analyzer是一个快速并且功能强大的Java heap分析器,能够帮助你查找内存泄漏和减少内存消耗。
  如何安装使用MAT工具,请到http://www.eclipse.org/mat/ 学习,我们主要来介绍一下如何在OPhone上得到程序运行的heap dump信息。

  1. Adb shell 登陆到手机或模拟器
  2. Su – 切换到root权限
  3. Chmod 777 /data/misc, 使/data/misc目录具有读写权限
  4. 通过ps命令,找到要调试的程序的pid
  5. Kill -10 pid
  6. 在/data/misc 目录下,会生成文件名类似heap-dump-xxxxx-pidxxx.hprof的文件。
  7. 通过adb pull 命令将.hprof文件拽到pc端
  8. 使用OPhone SDK提供的hprof-conv工具将OPhone生成的hprof文件转换成MAT识别的标准格式。例如:
view plain  copy to clipboard  print  ?
  1. Hprof-conv  heap-dump-xxxxx-pidxxx.hprof  standard-dump-file.hprof

 9. 使用MAT工具打开 standard-dump-file.hprof, 你将看到类似下图的分析报告。
     分析报告提供了详尽的heap信息,同时还指出了可疑的内存泄漏的对象。

       大家可以根据MAT提供的详细Heap信息,查找漏洞了。

OPhone程序开发入门之音乐播放器相关推荐

  1. 微信小程序开发:一个音乐播放器

    github源码地址 花了点时间撸了个微信小程序,分两个部分,音乐播放界面和音乐列表. 总结一下遇到的问题 UI分4层,第一层背景高斯模糊,第二层灰色半透明蒙层,第三层播放器,第四层列表 css设置背 ...

  2. 好程序员前端分享使用JS开发简单的音乐播放器

    好程序员前端分享使用JS开发简单的音乐播放器,最近,我们在教学生使用JavaScript,今天就带大家开发一款简单的音乐播放器.首先,最终效果如图所示: 首先,我们来编写html界面index.htm ...

  3. 微信小程序练手项目-音乐播放器

    微信小程序练手项目-音乐播放器 该项目只适合练手,大佬请绕道 项目展示图: 项目介绍 微信小程序音乐播放器 页面: 音乐推荐.播放器.播放列表 功能: 播放.暂停.上一首.下一首.跳转播放列表.实时进 ...

  4. java计算机毕业设计vue开发一个简单音乐播放器源码+mysql数据库+系统+lw文档+部署

    java计算机毕业设计vue开发一个简单音乐播放器源码+mysql数据库+系统+lw文档+部署 java计算机毕业设计vue开发一个简单音乐播放器源码+mysql数据库+系统+lw文档+部署 本源码技 ...

  5. 基于MSP430G2553官方开发板的音乐播放器

    基于MSP430G2553官方开发板的音乐播放器 实现目标 硬件资源 芯片资源使用情况 外接硬件 程序实现 开发环境配置 各部分硬件驱动 主循环功能实现 实现目标 实现以蜂鸣器为播放设备,能够对简谱乐 ...

  6. java计算机毕业设计vue开发一个简单音乐播放器MyBatis+系统+LW文档+源码+调试部署

    java计算机毕业设计vue开发一个简单音乐播放器MyBatis+系统+LW文档+源码+调试部署 java计算机毕业设计vue开发一个简单音乐播放器MyBatis+系统+LW文档+源码+调试部署 本源 ...

  7. 计算机毕业设计Javavue开发一个简单音乐播放器(源码+系统+mysql数据库+lw文档)

    计算机毕业设计Javavue开发一个简单音乐播放器(源码+系统+mysql数据库+lw文档) 计算机毕业设计Javavue开发一个简单音乐播放器(源码+系统+mysql数据库+lw文档) 本源码技术栈 ...

  8. 基于Arduino Uno开发板制作音乐播放器

    基于Arduino Uno开发板制作音乐播放器 本文将基于Arduino开发板实现一个音乐播放器. 利用Arduino Uno读取sd卡模块中内存卡的音乐,传输信号到扬声器进行播放. 一.项目软硬件简 ...

  9. JAVA毕业设计vue开发一个简单音乐播放器计算机源码+lw文档+系统+调试部署+数据库

    JAVA毕业设计vue开发一个简单音乐播放器计算机源码+lw文档+系统+调试部署+数据库 JAVA毕业设计vue开发一个简单音乐播放器计算机源码+lw文档+系统+调试部署+数据库 本源码技术栈: 项目 ...

最新文章

  1. 一流科技完成5000万人民币A轮融资,高瓴创投独家领投
  2. [mmu/cache]-ARMV8 MMU内存管理中的Memory attributes和Cache policies
  3. 4.等待链表与调度链表
  4. 蛋白质浓度与盐胁迫的关系_[202009024]巨大狼尾草:一种新兴的盐积累/耐盐的非传统作物,可用于可持续的盐碱农业和同步的植物修复...
  5. date js 半年_moment.js 搜索栏获取最近一周,一个月,三个月,半年,一年时间
  6. 计算机英语词组,计算机专业英语词组.doc
  7. MySQL-5.6.x二进制版本安装记录
  8. csdn博客中插入公式
  9. 功能选中jquery实现全选反选功能
  10. MapStruct 详解
  11. Pytorch模型量化
  12. C++程序注册Dll
  13. c语言日程报告闹钟,可以闹钟提醒的日程表,日程闹钟提醒怎么弄
  14. python 中的拷贝、浅拷贝与深拷贝
  15. AD如何修改PCB文件的黑色编辑区
  16. 计算机散热 测试,散热拷机实测_笔记本评测-中关村在线
  17. 定积分的基本性质3 保序性
  18. java中提供的好用的生成随机数字的工具类(可用来当验证码)
  19. 30、二维装箱(单品)
  20. centos7 /grub2/i386-pc/normal.mod not found 修复log

热门文章

  1. 经纬度和度分秒之间转换
  2. swagger 中加入 令牌
  3. java测试一个泰勒级数,泰勒级数+牛顿迭代公式+最简单的C语言求根号的值
  4. img src=1 onerror=alert(0)xssc
  5. 微信支付申请接入 app 流程
  6. 双机3D影厅收集,方便买不到IMAX的朋友,欢迎完善补充
  7. Halcon 鼠标涂抹交互--均值处理
  8. sklearn的系统学习——随机森林分类器与随机森林回归器(含有python完整代码及案例)
  9. 运动历史图MHI——程序解析(超详细)
  10. 家用智能IOT设备间的架构交互与通信,以及安全攻击面