Android 服务与多线程——编写简单的音乐播放器程序
一、实验目的
1) 学会使用MediaPlayer;
2) 学会简单的多线程编程,使用Handler更新UI;
3) 学会使用Service进行后台工作;
4) 学会使用Service与Activity进行通信。
二、实验要求
1) 实现音乐文件的播放控制(仅需要播放,暂停和停止)
2) 利用Handler更新播放进度
3) 利用Service开启(停止)后台服务进行后台播放
三、使用环境
Eclipse,Android 2.3
四、调试过程、代码解析及运行截图
1.向sdcard中添加音乐:
1)启动模拟器,打开DDMS视图;
2)选择FileExplorer标签页;
3)选择sdcard目录后点击右上角的push按钮即可。如下图:
2.创建MediaPlayer的对象,并利用控制条更新播放进度:
1)创建MediaPlayer及Handler:
MediaPlayermPlayer = new MediaPlayer(); Handler handler = new Handler(); RunnableupdateThread = new Runnable(){ public void run() { //获得歌曲现在播放位置并设置成播放进度条的值 seekbar.setProgress(mPlayer.getCurrentPosition()); //每次延迟100毫秒再启动线程 handler.postDelayed(updateThread, 100); }
try { mPlayer.setDataSource("/sdcard/test.mp3"); //选择资源 mPlayer.prepare(); //准备就绪 text.setText("初始化歌曲..."); } catch (IOException e){ text.setText("初始异常"); e.printStackTrace(); } //获取音乐的总长度以设置进度条的最大长度 seekbar.setMax(mPlayer.getDuration()); mPlayer.setOnCompletionListener(complete);
privateImageButton.OnClickListenerbtn_play = new ImageButton.OnClickListener(){ @Override public void onClick(View arg0) { //TODOAuto-generated method stub try { if(mPlayer !=null) { if(mPlayer.isPlaying()) { //判断是否正在播放 mPlayer.pause(); //暂停播放器 handler.post(updateThread); //使用handler的post方法用于更新 bn_play.setImageDrawable(getResources().getDrawable(R.drawable.play)); text.setText("已暂停"); //更新图标 } else if(!mPlayer.isPlaying()) { mPlayer.start(); //继续播放音乐 handler.post(updateThread); bn_play.setImageDrawable(getResources().getDrawable(R.drawable.pause)); text.setText("播放中"); } } } catch (Exception e) { text.setText("播放/暂停异常"); e.printStackTrace(); } } };
4)对停止键的事件触发:
private ImageButton.OnClickListenerbtn_stop = new ImageButton.OnClickListener(){ @Override public void onClick(View arg0) { // TODO Auto-generated methodstub try{ if(mPlayer.isPlaying()){ text.setText("已停止"); //更新文字提示 } else { text.setText("初始化歌曲..."); } mPlayer.stop(); //停止播放音乐 handler.removeCallbacks(updateThread); bn_play.setImageDrawable(getResources().getDrawable(R.drawable.play)); mPlayer.reset(); //恢复至初始状态 mPlayer.setDataSource("/sdcard/test.mp3"); mPlayer.prepare(); } catch (Exception e){ text.setText("停止异常"); e.printStackTrace(); } } };
5)退出的事件触发
private Button.OnClickListenerbtn_exit = new Button.OnClickListener(){ @Override public void onClick(View arg0) { // TODO Auto-generated methodstub onDestroy(); } }; @Override protected void onDestroy(){ mPlayer.release(); super.onDestroy(); System.exit(0); //完全退出系统 };
privateMediaPlayer.OnCompletionListenercomplete = new MediaPlayer.OnCompletionListener(){ @Override public void onCompletion(MediaPlayerarg0) { try { handler.removeCallbacks(updateThread);//移除对线程的调用 bn_play.setImageDrawable(getResources().getDrawable(R.drawable.play)); mPlayer.reset(); //恢复到初始状态 mPlayer.setDataSource("/sdcard/test.mp3"); mPlayer.prepare(); text.setText("播放结束!"); } catch (IOException e){ text.setText("完成异常"); e.printStackTrace(); } } };
7)对进度条的操作:
privateSeekBar.OnSeekBarChangeListenerseekb = new SeekBar.OnSeekBarChangeListener(){ @Override public void onProgressChanged(SeekBarseekBar, int progress,boolean fromUser) { // fromUser判断是用户改变的滑块的值 if(fromUser==true){ mPlayer.seekTo(progress); //转到相应的进程中 } } @Override public voidonStartTrackingTouch(SeekBar seekBar) { //TODOAuto-generated methodstub } @Override public void onStopTrackingTouch(SeekBarseekBar) { //TODOAuto-generated method stub } };
A.定义两个变量,一为现在正播放的时间:playtime;二为总时常alltime:
playtime =(TextView)findViewById(R.id.progress); alltime =(TextView)findViewById(R.id.alltime);
B.在播放的设置中添加获取音乐总时长的数据:
else if(!mPlayer.isPlaying()) { mPlayer.start(); int Alltime=mPlayer.getDuration(); alltime.setText(ShowTime(Alltime)); handler.post(updateThread); bn_play.setImageDrawable(getResources().getDrawable(R.drawable.pause)); text.setText("播放中"); }
C.在Runnable里获取正在运行的时间:
Runnable updateThread = new Runnable(){ public void run() { //获得歌曲现在播放位置并设置成播放进度条的值 //将播放的时间调到正在播放的时间 int CurrentPosition=mPlayer.getCurrentPosition(); playtime.setText(ShowTime(CurrentPosition)); seekbar.setProgress(CurrentPosition); //每次延迟100毫秒再启动线程 handler.postDelayed(updateThread, 100); } };
D.显示时间的函数
public String ShowTime(int time){ time/=1000; int minute=time/60; int hour=minute/60; int second=time%60; minute%=60; return String.format("%02d:%02d", minute, second); }
Android中的服务,它与Activity不同,它是不能与用户交互的,不能自己启动的,运行在后台的程序,如果我们退出应用时,Service进程并没有结束,它仍然在后台运行。使用Service来管理音乐的后台播放
1) 创建Service子类,并在Manifest.xml中进行注册
<service android:name=".MyService"/>
2) 创建接口MyBinder,负责Activity与Service之间进行沟通,帮助Activity调用Service里的方法
package com.yzh; import android.app.Service; import android.os.Binder; public interface MyBinder { public Service getService(); //负责Activity与Service之间进行沟通,帮助 Activity调用Service里的方法 }
A. OnCreate()
@Override publicvoid onCreate() { //初始化MediaPlayer // TODO Auto-generated method stub super.onCreate(); mPlayer =new MediaPlayer(); try { mPlayer.setDataSource("/sdcard/test.mp3"); mPlayer.prepare(); } catch (IOException e) { //TODO Auto-generated catch block e.printStackTrace(); } }
@Override publicvoid onDestroy() { // TODO Auto-generated methodstub super.onDestroy(); mPlayer.release(); }
publicboolean startorpauseMusic() { boolean playing =false; if(!mPlayer.isPlaying()){ playing = false; mPlayer.start(); } else{ playing = true; mPlayer.pause(); } return playing; }
public void stopMusic() { mPlayer.seekTo(0); mPlayer.stop(); try { mPlayer.prepare(); } catch (IllegalStateException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } }
private IBinder binder = new MyBinderImpl(); @Override public IBinder onBind(Intentarg0) { // TODO Auto-generated method stub return binder; } //在主Activity退出时必须解除绑定,否则会抛出异常 @Override public boolean onUnbind(Intent intent) { // TODO Auto-generated methodstub return super.onUnbind(intent); } public class MyBinderImpl extends Binderimplements MyBinder { @Override public Service getService() { // TODO Auto-generated methodstub return MyService.this; } }
5) 在主Activity中调用 bindService 保持与Service的通信:
ServiceConnection sc = new ServiceConnection(){ @Override public void onServiceConnected(ComponentName arg0, IBinder binder) { // TODO Auto-generated method stub MyBinder myBinder = (MyBinder)binder; service = (MyService)myBinder.getService(); seekbar.setMax(service.getDuration()); int length = service.getDuration(); int secLength = length/1000+1; service.onComplete(handler); } @Override public void onServiceDisconnected(ComponentName arg0) { // TODO Auto-generated method stub } };
6) 在Activity中将所有的MediaPlayer的方法都替换为Service里的函数。
7) 在Service中还需要定义的方法:
//获取音乐的长度 public int getDuration() { return mPlayer.getDuration(); } //获取音乐的播放位置 public int getPosition() { return mPlayer.getCurrentPosition(); } //跳转至音乐播放的进程 public void seekto(int progress){ mPlayer.seekTo(progress); }
4.实验截图
图1 初始化状态
图2 点击播放按钮:播放音乐
显示总时长,以及现在播放的时间,进度条实时更新
图3 点击暂停按钮,暂停播放
显示“已暂停”,停止更新
图4 点击停止按钮,停止播放
当再次点击播放按钮时,音乐从头开始
图5 当音乐播放完后,显示“播放结束!”
图6 关掉运行界面后,
播放器在后台运行仍然运行
图7 项目的文件结构
五、遇到的困难和解决方法
在使用Service作后台处理的时候,忽略了音乐播放结束后的事件触发,无法显示“播放结束”,原来判断音乐播放结束是MediaPlayer的一个方法,无法在Activity里进行,必须要在Service里定义,于是采用使用Handler来传参的方法。具体解决步骤如下:
A. 在Service中定义OnComplete方法:
public void onComplete(final Handler handler) { MediaPlayer.OnCompletionListener complete = newMediaPlayer.OnCompletionListener() { @Override public void onCompletion(MediaPlayer arg0) { // TODO Auto-generated method stub Message msg = new Message(); //获得消息 msg.arg1 = 0; handler.sendMessage(msg); //传递给handler } }; mPlayer.setOnCompletionListener(complete); }
B. 在Activity中获取消息:
Handler handler = new Handler(){ @Override public void handleMessage(Messagemsg) { // TODO Auto-generated method stub super.handleMessage(msg); if(msg.arg1 == 0) { handler.removeCallbacks(updateThread);//移除对线程的调用 bn_play.setImageDrawable(getResources().getDrawable(R.drawable.play)); text.setText("播放结束"); } } };
六、附录——main.xml
<?xml version="1.0"encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="fill_parent" android:layout_height="fill_parent" android:orientation="vertical"> <LinearLayout android:id="@+id/linearLayout7" android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="vertical"> <TextView android:id="@+id/textView2" android:layout_width="wrap_content" android:layout_height="wrap_content" android:textSize="25sp" android:layout_gravity="center_horizontal" android:textColor="#0489B1" android:text="My MusicPlayer" /> </LinearLayout> <LinearLayout android:id="@+id/linearLayout4" android:layout_width="match_parent" android:layout_height="wrap_content" android:gravity="center"> <LinearLayout android:id="@+id/linearLayout5" android:layout_width="wrap_content" android:layout_height="match_parent" android:orientation="vertical"> <TextView android:id="@+id/text" android:layout_width="fill_parent" android:layout_height="wrap_content" android:textSize="20dp" android:text="Enjoy yourMusic: " /> </LinearLayout> <LinearLayout android:id="@+id/linearLayout6" android:layout_width="wrap_content" android:layout_height="match_parent"> <TextView android:id="@+id/textView1" android:layout_width="wrap_content" android:layout_height="wrap_content" android:textSize="20dp" android:text="test.mp3"/> </LinearLayout> </LinearLayout> <LinearLayout android:id="@+id/linearLayout9" android:layout_width="match_parent" android:layout_height="wrap_content" android:gravity="center"> <TextView android:id="@+id/progress" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="00:00" android:textColor="#FFBF00" android:textSize="18dp"/> <TextView android:id="@+id/textView3" android:layout_width="wrap_content" android:layout_height="wrap_content" android:textColor="#FFBF00" android:textSize="18dp" android:text=" / "/> <TextView android:id="@+id/alltime" android:layout_width="wrap_content" android:layout_height="wrap_content" android:textColor="#FFBF00" android:textSize="18dp" android:text="00:00"/> </LinearLayout> <LinearLayout android:id="@+id/linearLayout8" android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="vertical"> <SeekBar android:id="@+id/seekbar" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_weight="1"/> </LinearLayout> <LinearLayout android:id="@+id/linearLayout1" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center_horizontal"> <ImageButton android:id="@+id/bn_play" android:layout_width="wrap_content" android:layout_height="wrap_content" android:src="@drawable/play"/> <ImageButton android:id="@+id/bn_stop" android:layout_width="wrap_content" android:layout_height="wrap_content" android:src="@drawable/stop"/> <Button android:id="@+id/bn_exit" android:layout_width="wrap_content" android:layout_height="match_parent" android:text="Exit"/> </LinearLayout> <LinearLayout android:id="@+id/linearLayout2" android:layout_width="match_parent" android:layout_height="wrap_content"> </LinearLayout> <LinearLayout android:id="@+id/linearLayout3" android:layout_width="match_parent" android:layout_height="wrap_content"> </LinearLayout> </LinearLayout>
七、参考链接
[1] http://www.verydemo.com/demo_c131_i30811.html
[2] http://griffinshi.iteye.com/blog/641037
[3] http://www.cnblogs.com/newcj/archive/2011/05/30/2061370.html
[4] http://blog.csdn.net/cjjky/article/details/6552852
[5] http://www.pocketdigi.com/20100908/92.html