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);
- }
2)初始化音乐:
- try {
- mPlayer.setDataSource("/sdcard/test.mp3"); //选择资源
- mPlayer.prepare(); //准备就绪
- text.setText("初始化歌曲…");
- } catch (IOException e){
- text.setText("初始异常");
- e.printStackTrace();
- //获取音乐的总长度以设置进度条的最大长度
- seekbar.setMax(mPlayer.getDuration());
- mPlayer.setOnCompletionListener(complete);
3)对开始/暂停键事件触发:
- 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); //完全退出系统
- };
6)歌曲播放结束的事件:
- privateMediaPlayer.OnCompletionListenercomplete = new MediaPlayer.OnCompletionListener(){
- @Override
- public void onCompletion(MediaPlayerarg0) {
- try {
- handler.removeCallbacks(updateThread);//移除对线程的调用
- bn_play.setImageDrawable(getResources().getDrawable(R.drawable.play));
- mPlayer.reset(); nbsp; //恢复到初始状态
- 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
- }
- };
8)添加时间进度显示:
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);
3.利用Service开启(停止)后台服务进行后台播放
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里的方法
3) 将之前写的MediaPlayer相关的代码全部移动到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();
- }
- }
B. OnDestroy()
- @Override
- publicvoid onDestroy() {
- // TODO Auto-generated methodstub
- super.onDestroy();
- mPlayer.release();
- }
C. startorpauseMusic()
- publicboolean startorpauseMusic()
- {
- boolean playing =false;
- if(!mPlayer.isPlaying()){
- playing = false;
- mPlayer.start();
- }
- else{
- playing = true;
- mPlayer.pause();
- }
- return playing;
- }
D. stopMusic()
- 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();
- }
- }
4) 通过Binder来保持Activity与Service的通信
- 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中还需要定义的方法:
- //获取音乐的长度
- lic 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.youkuaiyun.com/cjjky/article/details/6552852
[5]
http://www.pocketdigi.com/20100908/92.html