Android 服务与多线程——编写简单的音乐播放器程序

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按钮即可。如下图:

Android 服务与多线程——编写简单的音乐播放器程序_第1张图片

 

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();                       //恢复到初始状态
                   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中还需要定义的方法:

   //获取音乐的长度
public int getDuration()
   {
       return mPlayer.getDuration();
   }
  
   //获取音乐的播放位置
   public int getPosition()
   {
       return mPlayer.getCurrentPosition();
   }
  
   //跳转至音乐播放的进程
   public void seekto(int progress){
       mPlayer.seekTo(progress);
   }

4.实验截图

Android 服务与多线程——编写简单的音乐播放器程序_第2张图片

图1 初始化状态

Android 服务与多线程——编写简单的音乐播放器程序_第3张图片 

图2 点击播放按钮:播放音乐

显示总时长,以及现在播放的时间,进度条实时更新

 

图3 点击暂停按钮,暂停播放

显示“已暂停”,停止更新

Android 服务与多线程——编写简单的音乐播放器程序_第4张图片 

图4 点击停止按钮,停止播放

当再次点击播放按钮时,音乐从头开始

 

图5 当音乐播放完后,显示“播放结束!”

Android 服务与多线程——编写简单的音乐播放器程序_第5张图片 

图6 关掉运行界面后,

播放器在后台运行仍然运行

Android 服务与多线程——编写简单的音乐播放器程序_第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



你可能感兴趣的:(Android 服务与多线程——编写简单的音乐播放器程序)