您的位置:首页 > 移动开发 > Android开发

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

2016-11-06 21:04 543 查看
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:

[java]
view plain
copy

MediaPlayermPlayer = new MediaPlayer();

Handler handler = new Handler();
RunnableupdateThread = new Runnable(){
public void run() {
//获得歌曲现在播放位置并设置成播放进度条的值
seekbar.setProgress(mPlayer.getCurrentPosition());
//每次延迟100毫秒再启动线程
handler.postDelayed(updateThread, 100);
}

2)初始化音乐:

[java]
view plain
copy

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)对开始/暂停键事件触发:

[java]
view plain
copy

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)对停止键的事件触发:

[java]
view plain
copy

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)退出的事件触发

[java]
view plain
copy

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)歌曲播放结束的事件:

[java]
view plain
copy

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)对进度条的操作:

[java]
view plain
copy

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:

[java]
view plain
copy

playtime =(TextView)findViewById(R.id.progress);
alltime =(TextView)findViewById(R.id.alltime);

B.在播放的设置中添加获取音乐总时长的数据:

[java]
view plain
copy

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里获取正在运行的时间:

[java]
view plain
copy

Runnable updateThread = new Runnable(){
public void run() {
//获得歌曲现在播放位置并设置成播放进度条的值
//将播放的时间调到正在播放的时间

int CurrentPosition=mPlayer.getCurrentPosition();
playtime.setText(ShowTime(CurrentPosition));
seekbar.setProgress(CurrentPosition);

//每次延迟100毫秒再启动线程
handler.postDelayed(updateThread, 100);
}
};

D.显示时间的函数

[java]
view plain
copy

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里的方法

[java]
view plain
copy

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()

[java]
view plain
copy

@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()

[java]
view plain
copy

@Override
publicvoid onDestroy() {
// TODO Auto-generated methodstub
super.onDestroy();
mPlayer.release();
}

C. startorpauseMusic()

[java]
view plain
copy

publicboolean startorpauseMusic()
{
boolean playing =false;
if(!mPlayer.isPlaying()){
playing = false;
mPlayer.start();
}
else{
playing = true;
mPlayer.pause();
}

return playing;
}

D. stopMusic()

[java]
view plain
copy

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的通信

[java]
view plain
copy

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的通信:

[java]
view plain
copy

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中还需要定义的方法:

[java]
view plain
copy

//获取音乐的长度
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方法:

[java]
view plain
copy

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中获取消息:

[java]
view plain
copy

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

[html]
view plain
copy

<?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
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息