Android实战项目——音乐播放器 由四大组件之一Service、使用Serv

天天见闻 天天见闻 2023-08-14 综合 阅读: 71
摘要: 实现音乐播放器功能的具体步骤是将播放器界面所需的背景图像、音乐图像导入程序中的文件夹中。在上述代码中,()方法用于每500毫秒更新音乐播放器的进度条,首先创建计时器Timer的对象,然后重写run()方法创建线程,在run()方法中,通过()方法和()方法分别创建获取歌曲总时间长度和歌曲当前播放进度的任务task。

实战项目——音乐播放器 由四大组件之一、使用进行本地通信、消息机制、动画共同完成 类似主流音乐APP界面 简单实用 实际开发经常会涉及服务有消息机制,两者具有紧密的联系,本文将通过一个音乐播放器案例演示如何使用服务进行本地通信,让大家更好的理解服务通信在实际开发中的应用。

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

1、创建程序

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

2、导入音乐文件

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

3、导入界面图片

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

4、放置界面控件

界面实现效果如图

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


<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、创建背景选择器.xml

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


<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、创建服务

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

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置为空
    }
}

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

7、编写界面交互代码

实现了音乐文件的播放、暂停播放、继续播放、播放进度的设置、退出音乐播放界面的功能以及实现音乐播放界面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、运行程序

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

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

消息机制:

消息处理首先需要在UI线程中创建一个对象,然后在子线程中调用的()方法,接着这个消息会存放在UI线程的中,通过对象取出中的消息,最后分发回的()方法中。

谢谢观看!祝学习进步编程开心~希望可以得到你的赞与关注哟

其他相关
腾讯公司申请地图绘制专利,提高地图绘制的准确性

腾讯公司申请地图绘制专利,提高地图绘制的准确性

作者: 天天见闻 时间:2024-03-31 阅读: 1
金融界2024年3月15日消息,据国家知识产权局公告,腾讯科技(深圳)有限公司申请一项名为“地图绘制方法、装置和计算机可读存储介质“,公开号CN117705081A,申请日期为2022年9月。 专利摘要显示,本申请实施例公开了一种地图绘制方法、装置和计算机可读存储介质,可应用于云技术、人工智能、智慧交通、辅助驾驶等各种场景;通过获取原始地图路网和历史轨迹点;根据历史轨迹点的位置信息,在路段中识别出历史轨迹点匹配的候选路段;基于历史轨迹点之间的距离和原始地图路网的连通关系,计算转移概率,并基于历史轨迹点和候选路段之间的距离,计算发射概率;根据转移概率和发射概率,在候选路段中筛选出目标路段;基于历史轨迹点之间的路径信息以及历史轨迹点到目标路段的转移概率,确定出历史轨迹点之间存在的缺失路段;根据缺失路段对原始地图路网进行绘制更新,得到目标地图路网。以此,提高地图绘制的准确性。...
美媒:中芯片初创企业融资远超美国

美媒:中芯片初创企业融资远超美国

作者: 天天见闻 时间:2024-02-02 阅读: 38
参考消息网2月1日报道据美国石英财经网站1月30日报道,在计算机芯片风险投资方面,美国与中国之间的差距从未如此之大。 报道称,市场研究公司“项目建议书”数据公司最近的一份报告显示,2023年,美国在全球半导体初创企业融资中所占的份额仅为11%,而中国占75%。“项目建议书”数据公司将半导体初创企业定义为从事芯片设计和制造以及相关产品和设备的公司。 在美国收紧对英伟达公司和超威半导体公司等制造商生产的人工智能芯片的出口管制之后,中国已将建设半导体产业列为优先事项。中国推出了由国家支持的投资工具——国家集成电路产业投资基金。...
据了解,京东仍在频繁接触东宇汇,去京东会是个好归宿吗?

据了解,京东仍在频繁接触东宇汇,去京东会是个好归宿吗?

作者: 天天见闻 时间:2024-01-30 阅读: 39
今日,京东仍在频繁接触董宇辉的消息,在网络上掀起波澜。据知情人士透露,京东内部已多次召开小型会议,探讨引进董宇辉的可能性,并探讨未来的合作模式。 此前,东方甄选“小作文”风波爆发之时,京东有意招募董宇辉的传闻便已传得沸沸扬扬。有网传截图显示,京东的人力副总裁曾携带刘强东的亲笔签名前往陕西与董宇辉洽谈,且双方沟通顺畅,似乎董宇辉加盟京东的可能性颇高。然而,董宇辉对此予以否认,他表示:“此消息并不属实,目前并未与任何公司接触。” 罗永浩也在社交媒体上发文力挺董宇辉,他表示:“真的没必要再浪费生命去打工了。如果董宇辉老师本人有创业的想法,很愿意与一些做投资和企业的朋友们共同为他搭建一个平台,助力他顺利创业。”罗永浩坦言,他并不追求在这件事上有任何利益回报,只是将董宇辉视为年轻时的自己,希望给予他更多的支持和鼓励。...
渴望“经济胜利”!美媒:拜登可能针对英特尔、台积电宣布巨额补贴

渴望“经济胜利”!美媒:拜登可能针对英特尔、台积电宣布巨额补贴

作者: 天天见闻 时间:2024-01-29 阅读: 40
【环球时报驻美国、韩国特约记者 冯亚仁 丁玲】 “拜登政府急于在大选临近之际强调自己的标志性经济举措。”美国《华尔街日报》27日报道称,拜登政府预计将在未来几周正式宣布向包括英特尔、台积电在内的半导体大厂提供数十亿美元的建厂补贴,以帮助它们建设新工厂。该补贴是拜登于2022年8月签署的《芯片和科学法》的一部分,但由于条件苛刻、实施效率低下等原因,台积电、三星电子等接连宣布推迟投产。报道称,随着前总统特朗普日前接连在初选中取得胜利,拜登兑现自己标志性的经济政策变得迫在眉睫。 ...
不买英伟达芯片,也不卖镓和锗,中国的对战帝,急之余,黄仁勋来中国跳舞。

不买英伟达芯片,也不卖镓和锗,中国的对战帝,急之余,黄仁勋来中国跳舞。

作者: 天天见闻 时间:2024-01-27 阅读: 34
美国对华围起“小院高墙”,中方自然有办法反制,且拿准美方的要害反制,这不,美国半导体巨头英伟达CEO坐不住了,急得来华扭起大秧歌。 美媒发现中方已暂停对美出口镓和锗 参考消息网报道,中方自出台金属镓和锗的管制措施后,完全停止了对美国出口这两项关键原材料,同时,中国对美国盟友的镓锗出口量也大幅减少,尤其是与美国站在统一战线的日本。 不过,中方对镓锗出口下降比例不同,数据显示,2023年全年中国镓对外出口,同比下降了三分之二,锗对外出口则下降了8%。这大概率是因为中国在两项关键原材料领域占据的领导地位有差别,中国生产镓占全球产量的95%,锗占60%,因此中方只要做到严控镓的出口量,便可以影响美国供应链。...
轮到米卡脖子了吗?中国的反制化有效果!美企不能彻底坐,急着来中国跳舞。

轮到米卡脖子了吗?中国的反制化有效果!美企不能彻底坐,急着来中国跳舞。

作者: 天天见闻 时间:2024-01-26 阅读: 44
据中国经营报援引《华尔街日报》的消息,这次英伟达面临着一个比较尴尬的问题,那就是阿里巴巴、腾讯、字节跳动等中国AI芯片的大买家不再计划购买性能降级版的英伟达AI芯片。虽然阿里巴巴、腾讯、字节跳动等公司并未对上述消息作出回应,但是《中国经营报》记者从多位业内人士处获悉,英伟达计划特供中国市场的AI芯片并非所谓的“改良版”,而是“减配版”——性能可能会缩水80%。中国企业对于特供芯片的性能和稳定性存在疑虑,再加上美国政府的一再干预,因此国内企业对于是否购买表现得十分谨慎。 对此,在前不久召开的商务部例行新闻发布会上,发言人何亚东表示,有关具体商业活动建议向企业了解,中方反对有关国家干涉半导体正常贸易。何亚东说:“如果出现这种情况,我想原因也显而易见。中方反对有关国家干涉半导体正常贸易,干扰企业间正常交流合作。我想强调的是,中国是全球主要的半导体市场之一,我们欢迎各国半导体企业和产品进入中国,共享机遇,共赢发展。”...
我来说两句

年度爆文