Android实战项目——音乐播放器 由四大组件之一Service、使用Service进行本地通信、Handle消息机制、Android动画共同完成 类似主流音乐APP界面 简单实用

实际开发经常会涉及服务有Handler消息机制,两者具有紧密的联系,本文将通过一个音乐播放器案例演示如何使用服务进行本地通信,让大家更好的理解服务通信在实际开发中的应用。

实现音乐播放器功能的具体步骤如下:

1、创建程序

创建一个名为MusicPlayer的应用程序,指定包名为cn.itcast.musicplayer。

2、导入音乐文件

音频文件一般放在res/raw文件夹【raw文件夹中的文件会被映射到R.java文件中,访问该文件时可直接使用资源ID,即R.id.music(文件名)】中,因此需要在res文件夹中创建一个raw文件夹。首先将Android Studio中的选项卡切换到Project,接着选中程序中的res文件夹,右击选择【New】—>【Directory】选项,创建一个名为raw的文件夹,接着将音乐文件music.mp3(不能为中文)导入到raw文件夹中。

3、导入界面图片

将播放器界面所需的背景图片、音乐图片导入到程序中的drawable文件夹中。

4、放置界面控件

界面实现效果如图

在activity_main.xml布局文件中,放置1个ImageView控件用于显示界面上的旋转图片,1个SeekBar用于显示音乐播放器的进度条,2个TextView分别用于显示音乐播放的进度时间与音乐的总时间,4个Button控件分别用于显示“播放音乐”按钮、“暂停播放”按钮、“继续播放”按钮、“退出”按钮,完整布局代码如下:

<?xml version="1.0" encoding="utf-8"?><LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"    xmlns:app="http://schemas.android.com/apk/res-auto"    xmlns:tools="http://schemas.android.com/tools"    android:layout_width="match_parent"    android:layout_height="match_parent"    tools:context=".MainActivity"    android:orientation="vertical"    android:gravity="center"    android:background="@drawable/music_bg">    <ImageView        android:id="@+id/iv_music"        android:layout_width="240dp"        android:layout_height="240dp"        android:layout_gravity="center_horizontal"        android:layout_marginTop="15dp"        android:src="@drawable/music"/>    <SeekBar        android:id="@+id/sb"        android:layout_width="match_parent"        android:layout_height="wrap_content"/>    <RelativeLayout        android:layout_width="match_parent"        android:layout_height="wrap_content"        android:paddingLeft="8dp"        android:paddingRight="8dp">        <TextView            android:id="@+id/tv_progress"            android:layout_width="wrap_content"            android:layout_height="wrap_content"            android:text="00:00" />        <TextView            android:id="@+id/tv_total"            android:layout_width="wrap_content"            android:layout_height="wrap_content"            android:layout_alignParentRight="true"            android:text="00:00" />    RelativeLayout>    <LinearLayout        android:layout_width="match_parent"        android:layout_height="wrap_content"        android:orientation="horizontal">        <Button            android:id="@+id/btn_play"            android:layout_width="0dp"            android:layout_height="40dp"            android:layout_margin="8dp"            android:layout_weight="1"            android:background="@drawable/btn_bg_selector"            android:text="播放音乐"/>        <Button            android:id="@+id/btn_pause"            android:layout_width="0dp"            android:layout_height="40dp"            android:layout_margin="8dp"            android:layout_weight="1"            android:background="@drawable/btn_bg_selector"            android:text="暂停播放"/>        <Button            android:id="@+id/btn_continue_play"            android:layout_width="0dp"            android:layout_height="40dp"            android:layout_margin="8dp"            android:layout_weight="1"            android:background="@drawable/btn_bg_selector"            android:text="继续播放"/>        <Button            android:id="@+id/btn_exit"            android:layout_width="0dp"            android:layout_height="40dp"            android:layout_margin="8dp"            android:layout_weight="1"            android:background="@drawable/btn_bg_selector"            android:text="退出"/>    LinearLayout>LinearLayout>

5、创建背景选择器btn_bg_selector.xml

通过背景选择器实现界面4个按钮背景的四个角是圆角,并且背景在按下与弹起时,背景颜色会有明显区别。选中drawable文件夹,右击选择【New】——>【Drawable resource file】选项,创建一个背景选择器btn_bg_selector.xml,具体代码+注释如下:

<?xml version="1.0" encoding="utf-8"?><selector xmlns:android="http://schemas.android.com/apk/res/android">    <item android:state_pressed="true">        <shape android:shape="rectangle">            <corners android:radius="3dp"/>            <solid android:color="#d4d4d4"/>        shape>    item>    <item android:state_pressed="false">        <shape android:shape="rectangle">            <corners android:radius="3dp"/>            <solid android:color="#ffffff"/>        shape>    item>selector>

6、创建MusicService服务

由于音乐的加载、播放、暂停以及播放进度条的更新是一件比较耗时的操作,因此需要创建一个MusicService服务来处理这些操作。首先选择cn.itcast.musicplayer包,右击选择【new】—>【Service】—>【Service】选项,创建名为MusicService的服务。具体代码如下:

package cn.itcast.musicplayer;import android.app.Service;import android.content.Intent;import android.media.MediaPlayer;import android.os.Binder;import android.os.Bundle;import android.os.IBinder;import android.os.Message;import java.util.Timer;import java.util.TimerTask;public class MusicService extends Service {    private MediaPlayer player;    private Timer timer;    public MusicService() {    }    @Override    public IBinder onBind(Intent intent) {        return new MusicControl();    }    public void onCreate(){        super.onCreate();        player = new MediaPlayer();//创建音乐播放器对象    }    public void addTimer(){     //添加计时器用于设置音乐播放器中的播放进度条        if(timer ==null){            timer = new Timer();       //创建计时器对象            TimerTask task = new TimerTask() {                @Override                public void run() {     //创建一个线程                    if(player == null) return;                    //获取歌曲总时长                    int duration = player.getDuration();                    //获取播放进度                    int currentPosition = player.getCurrentPosition();                    //创建消息对象                    Message msg = MainActivity.handler.obtainMessage();                    //将音乐的总时长和播放进度封装至消息对象中                    Bundle bundle = new Bundle();                    bundle.putInt("duration",duration);                    bundle.putInt("currentPosition",currentPosition);                    msg.setData(bundle);                    //将消息发送到主线程的消息列表                    MainActivity.handler.sendMessage(msg);                }            };            //调用Timer对象的schedule()方法执行TimerTask任务            // 该方法有三个参数:1、要执行的任务;2、开始执行计时任务的5毫秒后第一次执行task任务;3、每隔500毫秒执行一次            timer.schedule(task,5,500);        }    }    class MusicControl extends Binder{//实现播放、暂停、继续播放、设置音乐播放进度条的功能        public void play(){            try {                player.reset();     //重置音乐播放器                //加载多媒体文件                player = MediaPlayer.create(getApplicationContext(),R.raw.music);                player.start();//播放音乐                addTimer();     //添加计时器            }catch (Exception e){                e.printStackTrace();            }        }        public void pausePlay(){            player.pause();     //暂停播放音乐        }        public void continuePlay(){            player.start();     //继续播放音乐        }        public void seekTo(int progress){            player.seekTo(progress);    //设置音乐的播放位置        }    }    public void onDestroy(){        super.onDestroy();        if(player == null) return;        if(player.isPlaying()) player.stop();   //停止播放音乐        player.release();                       //释放占用的资源        player = null;                          //将player置为空    }}

上述代码中,addTimer()方法用于每隔500毫秒更新音乐播放器的进度条,在该方法中首先创建一个计时器Timer的对象,接着创建一个TimerTask任务task,在该任务中重写了run()方法创建一个线程,在run()方法中通过getDuration()方法getCurrentPosition()方法分别获取歌曲的总时长与歌曲当前的播放进度。getDuration()方法与getCurrentPosition()方法是MediaPlayer常用方法,更多Mediaplayer常用方法见:MediaPlayer常用方法介绍

7、编写界面交互代码

MainActivity实现了音乐文件的播放、暂停播放、继续播放、播放进度的设置、退出音乐播放界面的功能以及实现音乐播放界面4个按钮的点击事件。具体代码如下(带详细注释)

package cn.itcast.musicplayer;import androidx.appcompat.app.AppCompatActivity;import android.animation.ObjectAnimator;import android.content.ComponentName;import android.content.Intent;import android.content.ServiceConnection;import android.os.Bundle;import android.os.Handler;import android.os.IBinder;import android.os.Message;import android.view.View;import android.view.animation.LinearInterpolator;import android.widget.ImageView;import android.widget.SeekBar;import android.widget.TextView;public class MainActivity extends AppCompatActivity implements View.OnClickListener {    private static SeekBar sb;    private static TextView tv_progress, tv_total;    private ObjectAnimator animator;    private MusicService.MusicControl musicControl;    MyServiceConn conn;    Intent intent;    private boolean isUnbind = false;//记录服务是否被解绑    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_main);        init();    }    private void init() {        tv_progress = (TextView) findViewById(R.id.tv_progress);        tv_total = (TextView) findViewById(R.id.tv_total);        sb = (SeekBar) findViewById(R.id.sb);        findViewById(R.id.btn_play).setOnClickListener(this);        findViewById(R.id.btn_pause).setOnClickListener(this);        findViewById(R.id.btn_continue_play).setOnClickListener(this);        findViewById(R.id.btn_exit).setOnClickListener(this);        intent = new Intent(this, MusicService.class);//创建意图对象        conn = new MyServiceConn();//创建服务连接对象        bindService(intent, conn, BIND_AUTO_CREATE);  //绑定服务        //为滑动条添加事件监听        sb.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {            @Override            //滑动条进度改变时会调用该方法            public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {                if(progress ==seekBar.getMax()){    //当滑动条滑到末端时,结束动画                    animator.pause();               //停止播放动画                }            }            //滑动条开始滑动时调用            @Override            public void onStartTrackingTouch(SeekBar seekBar) {            }            //滑动条停止滑动时调用            @Override            public void onStopTrackingTouch(SeekBar seekBar) {                //根据拖动的进度改变音乐播放进度                int progress = seekBar.getProgress();   //获取seekBar的进度                musicControl.seekTo(progress);          //改变播放进度            }        });        ImageView iv_music = (ImageView) findViewById(R.id.iv_music);        animator = ObjectAnimator.ofFloat(iv_music,"rotation",0f,360.0f);        animator.setDuration(10000);    //动画旋转一周的时间为10秒        animator.setInterpolator(new LinearInterpolator());        animator.setRepeatCount(-1);    //-1表示设置动画无线循环    }    //创建消息处理器对象    public static Handler handler = new Handler(){        //在主线程中处理从子线程发送过来的消息        public void handleMessage(Message msg){            Bundle bundle = msg.getData(); //获取从子线程发送过来的音乐播放进度            int duration = bundle.getInt("duration");               //歌曲的总时长            int currentPostition = bundle.getInt("currentPosition");//歌曲当前进度            sb.setMax(duration);             //设置SeekBar的最大值为歌曲总时长            sb.setProgress(currentPostition);//设置SeekBar当前的进度位置            //歌曲的总时长转换格式            int minute = duration / 1000 / 60;            int second = duration / 1000 % 60;            String strMinute = null;            String strSecond = null;            if (minute < 10) {              //如果歌曲的时间中的分钟小于10                strMinute = "0" + minute; //在分钟的前面加一个0            } else {                strMinute = minute + "";            }            if (second < 10) {           //如果歌曲的时间中的秒钟小于10                strSecond = "0" + second;//在秒钟前面加一个0            } else {                strSecond = second + "";            }            tv_total.setText(strMinute + ":" + strSecond);            //歌曲当前播放时长            minute = currentPostition / 1000 / 60;            second = currentPostition / 1000 % 60;            if (minute < 10) {             //如果歌曲的时间中的分钟小于10                strMinute = "0" + minute;//在分钟的前面加一个0            } else {                strMinute = minute + "";            }            if (second < 10) {               //如果歌曲的时间中的秒钟小于10                strSecond = "0" + second;  //在秒钟前面加一个0            } else {                strSecond = second + "";            }            tv_progress.setText(strMinute + ":" + strSecond);        }    };    @Override    public void onClick(View v) {        switch (v.getId()) {            case R.id.btn_play:                //播放按钮点击事件                musicControl.play();           //播放音乐                animator.start();               //播放动画                break;            case R.id.btn_pause:               //暂停按钮点击事件                musicControl.pausePlay();     //暂停播放音乐                animator.pause();              //暂停播放动画                break;            case R.id.btn_continue_play:     //继续播放按钮点击事件                musicControl.continuePlay(); //继续播放音乐                animator.start();              //播放动画                break;            case R.id.btn_exit:                //退出按钮点击事件                unbind(isUnbind);               //解绑服务绑定                isUnbind = true;                //完成解绑服务                finish();                         //关闭音乐播放界面                break;        }    }    private class MyServiceConn implements ServiceConnection {  //用于实现连接服务        @Override        public void onServiceConnected(ComponentName componentName, IBinder service) {            musicControl = (MusicService.MusicControl) service;        }        @Override        public void onServiceDisconnected(ComponentName componentName) {        }    }    private void unbind(Boolean isUnbind){        if(!isUnbind){                  //判断服务是否解绑            musicControl.pausePlay();   //暂停播放音乐            unbindService(conn);        //解绑服务            stopService(intent);        //停止服务        }    }    protected void onDestroy() {        super.onDestroy();        unbind(isUnbind); //解绑服务    }}

8、运行程序

运行上诉程序,分别点击界面上的“播放音乐”按钮、“暂停播放”按钮、“继续播放”按钮,可实现音乐的播放、暂停、继续播放的功能。点击界面上的“退出”按钮,可退出音乐播放器界面。运行结果如图所示:

文件创建地址不明白的可参考下图:

Handler消息机制:

Handler消息处理首先需要在UI线程中创建一个Handler对象,然后在子线程中调用Handler的SendMessage()方法,接着这个消息会存放在UI线程的MessageQueue中,通过Looper对象取出MessageQueue中的消息,最后分发回Handler的HandleMessage()方法中。
谢谢观看!祝学习进步编程开心~希望可以得到你的赞与关注哟

更多相关文章

  1. 豆瓣FM(离线播放):旅途听音乐必备的Android软件
  2. Android多媒体学习六:访问网络上的Audio对应的M3U文件,实现网络音
  3. Android(安卓)使用Vitamio打造自己的万能播放器(8)――细节优化
  4. Android(安卓)mp3音乐播放器实例-----概述
  5. android 验证码按钮点击,判断网络和匹配手机号并自动更新时间;
  6. Android:在service和activity之中,实现音乐播放进度条传递信息的两
  7. 基于 Android(安卓)的 3D 视频样本代码
  8. 基于android的网络音乐播放器-回调实现音乐播放及音乐收藏的实现
  9. [置顶] Android(安卓)播放器二次封装与实现

随机推荐

  1. android sqlite3查询
  2. TSLib ported to Android(安卓)for touch
  3. android sqlite 增删查 demo
  4. Android所有系统资源图标android.R.drawa
  5. android 中判断有无网络连接
  6. 启动android默认浏览器
  7. Android客户端GPS定位源码
  8. Android(安卓)7.0,8.0拍照loadXmlMetaData
  9. mtk android sd卡调试
  10. 关于android菜单