第8章系统服务(简易音频播放器的实现)
开发一个简易音乐播放器,要求实现: 综合使用Service,BroadCast,ContentProvider等组件实现后台播放。
- 播放和暂停、上一首、下一首、停止;
- 后台播放功能, 按下返回键退出应用后再次打开应用,UI 显示应能与当前的播放状态保持一致;
- 显示正在播放的歌曲名、作者;
- 一首歌曲播放完毕能实现自动播放下一首歌曲,并更新界面,显示相关信息;
提交 MusicService 和 MainActivity,运行结果截图。
评分: 1.基本分,60分,实现基本功能,提交的内容完整,条理清晰,可读性好。 2.上一首、下一首;+20分 3.UI销毁后再次打开应能与当前的播放状态保持一致;+20分
在模拟器中上传音乐:
找到路径:C:\Users\17511\Documents\AndroidStudio\DeviceExplorer\emulator-5554\storage\emulated\legacy\Music
MainActivity.java
package com.example.player;import android.Manifest;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.PackageManager;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.ImageButton;
import android.widget.TextView;
import android.widget.Toast;import androidx.appcompat.app.AppCompatActivity;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;public class MainActivity extends AppCompatActivity implements View.OnClickListener {public static final String CONTROL = "iet.jxufe.cn.android.control";//控制播放、暂停public static final String UPDATE = "iet.jxufe.cn.android.update";//更新界面显示// 定义音乐的播放状态,0x11代表没有播放;0x12代表正在播放;0x13代表暂停int status = 0x11;//获取界面中显示歌曲标题、作者文本框TextView title, author;// 播放/暂停、停止按钮ImageButton play, stop,next,pre;ActivityReceiver activityReceiver;//“启动”服务的intentIntent MusicServiceIntent;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);// 获取程序界面中的两个按钮以及两个文本显示框play = (ImageButton) this.findViewById(R.id.play);stop = (ImageButton) this.findViewById(R.id.stop);next = (ImageButton)this.findViewById(R.id.next);pre = (ImageButton)this.findViewById(R.id.pre);title = (TextView) findViewById(R.id.title);author = (TextView) findViewById(R.id.author);// 为两个按钮的单击事件添加监听器play.setOnClickListener(this);stop.setOnClickListener(this);next.setOnClickListener(this);pre.setOnClickListener(this);activityReceiver = new ActivityReceiver();IntentFilter filter = new IntentFilter(UPDATE);// 注册BroadcastReceiverregisterReceiver(activityReceiver, filter);MusicServiceIntent = new Intent(this, MusicService.class);//判断权限够不够,不够就给if (ContextCompat.checkSelfPermission(MainActivity.this, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {ActivityCompat.requestPermissions(MainActivity.this, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, 1);} else {//够了就设置路径等,准备播放startService(MusicServiceIntent);}}//获取到权限回调方法@Overridepublic void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {switch (requestCode) {case 1:if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {startService(MusicServiceIntent);} else {Toast.makeText(this, "权限不够获取不到音乐,程序将退出", Toast.LENGTH_SHORT).show();finish();}break;default:break;}}public void onClick(View source) {// 创建IntentIntent intent = new Intent(CONTROL);//System.out.println(source.getId());//System.out.println(source.getId() == R.id.play);switch (source.getId()) {// 按下播放/暂停按钮case R.id.play:intent.putExtra("control", 1);Log.d("MusicService","play pressed");break;// 按下停止按钮case R.id.stop:intent.putExtra("control", 2);Log.d("MusicService","stop pressed");break;case R.id.next:intent.putExtra("control", 3);Log.d("MusicService","next pressed");break;case R.id.pre:intent.putExtra("control", 4);Log.d("MusicService","pre pressed");break;}// 发送广播 ,将被Service组件中的BroadcastReceiver接收到sendBroadcast(intent);}// 自定义的BroadcastReceiver,负责监听从Service传回来的广播public class ActivityReceiver extends BroadcastReceiver {public void onReceive(Context context, Intent intent) {// 获取Intent中的update消息,update代表播放状态,默认为-1int update = intent.getIntExtra("update", -1);// 获取Intent中的current消息,current代表当前正在播放的歌曲,默认为-1String songs_title = "";songs_title = intent.getStringExtra("songs_title");String songs_author = "";songs_author = intent.getStringExtra("songs_author");if (update >= 0) {title.setText(songs_title);author.setText(songs_author);}switch (update) {case 0x11:play.setImageResource(R.drawable.play);status = 0x11;break;// 控制系统进入播放状态case 0x12:// 播放状态下设置使用暂停图标play.setImageResource(R.drawable.pause);// 设置当前状态status = 0x12;break;// 控制系统进入暂停状态case 0x13:// 暂停状态下设置使用播放图标play.setImageResource(R.drawable.play);// 设置当前状态status = 0x13;break;}}}@Overrideprotected void onDestroy() {super.onDestroy();unregisterReceiver(activityReceiver);}
}
这样就会自动添加权限
MusicService.java
package com.example.player;import android.app.Service;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.media.MediaPlayer;
import android.os.Environment;
import android.os.IBinder;
import android.util.Log;import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;public class MusicService extends Service {private static final String TAG = "MusicService";private List<Map<String,Object>> musicList = new ArrayList<Map<String,Object>>();private int songsCount = 0;private ServiceReceiver serviceReceiver;private MediaPlayer mPlayer;//当前的状态,0x11 代表没有播放 ;0x12代表 正在播放;0x13代表暂停private int status = 0x11;// 记录当前正在播放的音乐private int current = 0;public MusicService() {}@Overridepublic IBinder onBind(Intent intent) {// TODO: Return the communication channel to the service.throw new UnsupportedOperationException("Not yet implemented");}public void onCreate(){Log.d(TAG,"创建音乐播放服务");// 创建BroadcastReceiverserviceReceiver = new ServiceReceiver();// 创建IntentFilterIntentFilter filter = new IntentFilter(MainActivity.CONTROL);registerReceiver(serviceReceiver, filter);// 创建MediaPlayermPlayer = new MediaPlayer();initSongsData();// 为MediaPlayer播放完成事件绑定监听器mPlayer.setOnCompletionListener(new MediaPlayer.OnCompletionListener(){public void onCompletion(MediaPlayer mp){/* current++;if (current >= songsCount){current = 0;}*/setNextMusic();/* 发送广播通知Activity更改文本框 */Intent sendIntent = new Intent(MainActivity.UPDATE);Map<String,Object> map = musicList.get(current);status = 0x12;sendIntent.putExtra("update", status);sendIntent.putExtra("songs_title",(String)map.get("songs_title"));sendIntent.putExtra("songs_author",(String)map.get("songs_author"));// 发送广播 ,将被Activity组件中的BroadcastReceiver接收到sendBroadcast(sendIntent);// 准备、并播放音乐prepareAndPlay((File)map.get("songs_path"));}});super.onCreate();}@Overridepublic int onStartCommand(Intent intent, int flags, int startId) {Log.d(TAG,"启动音乐播放服务");//Map<String,Object> map = musicList.get(current);/* 发送广播通知Activity更改图标、文本框 *//*Intent sendIntent = new Intent(MainActivity.UPDATE);sendIntent.putExtra("update", status);sendIntent.putExtra("songs_title", (String)map.get("songs_title"));sendIntent.putExtra("songs_author",(String)map.get("songs_author"));if (status == 0x11){sendIntent.putExtra("songs_title","");sendIntent.putExtra("songs_author","");}// 发送广播 ,将被Activity组件中的BroadcastReceiver接收到sendBroadcast(sendIntent);*/return super.onStartCommand(intent, flags, startId);}private void setNextMusic(){current++;if(current>=songsCount){current = 0;}}private void setPreMusic(){current--;if(current<0){current = songsCount-1;}}// 初始化MediaPlayer歌曲路径,让MediaPlayer进入到准备状态private void initSongsData() {try {//歌曲路径Map<String,Object> map = new HashMap<String, Object>();map.put("songs_title","你是温柔本身");map.put("songs_author","UN1K");map.put("songs_path", new File(Environment.getExternalStorageDirectory(), "/Music/a1.mp3"));musicList.add(map);map = new HashMap<String, Object>();map.put("songs_title","白羊");map.put("songs_author","徐秉龙");map.put("songs_path", new File(Environment.getExternalStorageDirectory(), "/Music/a2.mp3"));musicList.add(map);map = new HashMap<String, Object>();map.put("songs_title","星空");map.put("songs_author","Richard Clayderman .天籁村");map.put("songs_path", new File(Environment.getExternalStorageDirectory(), "/Music/a3.mp3"));musicList.add(map);map = new HashMap<String, Object>();map.put("songs_title","我的一个道姑朋友");map.put("songs_author","双笙");map.put("songs_path", new File(Environment.getExternalStorageDirectory(), "/Music/a4.mp3"));musicList.add(map);songsCount = musicList.size();Log.d(TAG,songsCount+"");//mPlayer.setDataSource(((File)musicList.get(0)).getPath()); // 指定音频文件的路径//mPlayer.prepare(); // 让MediaPlayer进入到准备状态} catch (Exception e) {Log.d(TAG, "设置资源,准备阶段出错");e.printStackTrace();}}public class ServiceReceiver extends BroadcastReceiver {public void onReceive(final Context context, Intent intent){Log.d(TAG,"服务中的广播接收器收到广播");int control = intent.getIntExtra("control", -1);Map<String,Object> map = musicList.get(current);switch (control){// 播放或暂停case 1:// 原来处于没有播放状态if (status == 0x11){// 准备、并播放音乐prepareAndPlay((File) map.get("songs_path"));status = 0x12;}// 原来处于播放状态else if (status == 0x12){mPlayer.pause();// 暂停status = 0x13;// 改变为暂停状态}// 原来处于暂停状态else if (status == 0x13){mPlayer.start();// 播放status = 0x12;// 改变状态}break;// 停止声音case 2:// 如果原来正在播放或暂停if (status == 0x12 || status == 0x13){current = 0;//mPlayer.reset();mPlayer.stop();// 停止播放status = 0x11;}break;case 3:setNextMusic();status = 0x12;map = musicList.get(current);prepareAndPlay((File)map.get("songs_path"));break;case 4:// 上一首setPreMusic();status = 0x12;map = musicList.get(current);prepareAndPlay((File) map.get("songs_path"));break;}/*/* 发送广播通知Activity更改图标、文本框 */Intent sendIntent = new Intent(MainActivity.UPDATE);sendIntent.putExtra("update", status);sendIntent.putExtra("songs_title", (String)map.get("songs_title"));sendIntent.putExtra("songs_author",(String)map.get("songs_author"));if (status == 0x11){sendIntent.putExtra("songs_title","");sendIntent.putExtra("songs_author","");}// 发送广播 ,将被Activity组件中的BroadcastReceiver接收到sendBroadcast(sendIntent);}}private void prepareAndPlay(File songsPath){try{mPlayer.reset();//使用MediaPlayer加载指定的声音文件。mPlayer.setDataSource(songsPath.getPath());mPlayer.prepare();// 准备声音mPlayer.start();// 播放}catch (IOException e){e.printStackTrace();}}@Overridepublic void onDestroy() {super.onDestroy();unregisterReceiver(serviceReceiver);}}
activity_main.xml
<?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="wrap_content"android:background="@drawable/bg"android:orientation="vertical"tools:context=".MainActivity"><LinearLayoutandroid:layout_width="fill_parent"android:layout_height="fill_parent"android:orientation="horizontal"><ImageButtonandroid:id="@+id/pre"android:layout_width="wrap_content"android:layout_height="wrap_content"android:background="@drawable/selector_btn"android:src="@drawable/previous_button" /><ImageButtonandroid:id="@+id/stop"android:layout_width="wrap_content"android:layout_height="wrap_content"android:background="@drawable/selector_btn"android:src="@drawable/stop" /><ImageButtonandroid:id="@+id/play"android:layout_width="wrap_content"android:layout_height="wrap_content"android:background="@drawable/selector_btn"android:src="@drawable/play" /><ImageButtonandroid:id="@+id/next"android:layout_width="wrap_content"android:layout_height="wrap_content"android:background="@drawable/selector_btn"android:src="@drawable/next_button" /></LinearLayout><LinearLayoutandroid:layout_width="fill_parent"android:layout_height="fill_parent"android:orientation="vertical"><TextViewandroid:id="@+id/title"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_weight="1"android:textColor="#ffffff"android:layout_gravity="fill_vertical"android:textSize="20sp" /><TextViewandroid:id="@+id/author"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_weight="1"android:layout_gravity="fill_vertical"android:textColor="#ffffff"android:textSize="18sp" /></LinearLayout></LinearLayout>
AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"package="com.example.player"><uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" /><uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /><applicationandroid:allowBackup="true"android:icon="@mipmap/ic_launcher"android:label="@string/app_name"android:roundIcon="@mipmap/ic_launcher_round"android:supportsRtl="true"android:theme="@style/AppTheme"><serviceandroid:name=".MusicService"android:enabled="true"android:exported="true"></service><activity android:name=".MainActivity"><intent-filter><action android:name="android.intent.action.MAIN" /><category android:name="android.intent.category.LAUNCHER" /></intent-filter></activity></application></manifest>
初始界面:
点击播放:
点击暂停:
点击下一首:
点击停止:
相关文章:

await使用中的阻塞和并发(一)
好吧,不加点陈述不让发首页。那我们来陈述一下本篇提到的问题和对应的方法。 在.NET4.5中,我们可以配合使用async和await两个关键字,来以写同步代码的方式,实现异步的操作。 好处我目前看来有两点: 1.不会阻塞UI线程。…

中国经济是前所未有二元经济(转)
今天的中国是一个前所未有的二元经济,而且是三七开的二元经济,我国这么多年的经济发展,每一年GDP以10%的成长率增长的原因,就是二元经济的过热部门推动的,因此我们GDP的组成是非常扭曲的,超过一半都是固定资…

Unity 简单示例代码和向导/Unity Aplication Block
Unity 简单示例代码和向导 关于Unity 的说明和下载地址,请访问[微软控制反转和依赖注入容器Unity 1.0发布] http://forum.entlib.com/Default.aspx?gposts&t25 。 下面的范例主要实现:首先,定义ILogger 接口。然后,定义一个实…

crontab修改默认编辑器
$ sudo select-editor 选择3或者4 然后再次打开 crontab -e 就会是vim的方式了。 转载于:https://www.cnblogs.com/jiqing9006/p/10343035.html

Programming C# 学习笔记(二) 出发:“Hello World”
小序: 准备写这章的学习笔记了,啊,Hello World!多么亲切的语句,呵呵,当初学C语言的第一个程序就是输出它, 还记得费了好大劲终于把它输出来时候的那种兴奋感觉,真是让我怀念哦&a…

多IP绑定与多网卡绑定
多IP绑定: 实验目的: 实现如下图网络连接 实现 A, B 在分配不同网段的网络地址的情况下可以互联 实验条件有限,在没有交换机的情况下,将主机A ,B,路由器R1处于同一网络。将三台虚拟机的网络适配器设置为仅主…

华硕WL-500W无线路由器使用感受
作为一款实用型的家庭或小型企业应用的无线路由器,WL-500W有着独特的外观:<?xml:namespace prefix v ns "urn:schemas-microsoft-com:vml" /><?xml:namespace prefix o ns "urn:schemas-microsoft-com:office:office"…

什么是物联网网关?物联网网关具备什么功能?_转
参考:什么是物联网?物联网产业链体系深度分析 随着物联网概念的不断深入,商业级的网络应用遍地开花,各种智能家电层出不穷,改善着我们的生活。与此同时,物联网网关也将成为连接的重要纽带。作为网关设备&am…

MSDN Webcast“深入浅出ASP.NET AJAX系列”
课程: ASP.NET AJAX深入浅出系列课程(1):ASP.NET AJAX 概述(3月13日):对于ASP.NET AJAX的大致功能进行概述和演示,通过简单的演示让听众了解到ASP.NET AJAX框架的强大之处,以及对于开发带来的便…

技巧:结合Zabbix与SNMP监控嵌入式设备
在如何利用Zabbix监控网络设备三篇文章的前两篇中,我们介绍了如何通过Zabbix代理监控网络设备。但有些设备无法安装Zabbix代理,需要采用其他方法监控。需要考虑无法安装软件的嵌入式设备或应用程序。对于这些设备,可通过SNMP进行监控。 …

值得收藏的146条经典民间偏方
1、本贴所用药物,以食物为主,绝对无毒。 2、为使读者易懂,剂量单位均用旧制,如:斤、两、钱等,有的用碗,是指一般性中碗。 3、所用药物凡带有*记号的一般可到中药店买,药店都有。 4、…

DataGridView打印类
一下这个类专门用于打印DataGridView,但是功能不是很强大 如果有个性化需求 可在此基础上简单修改 Code 1public class DataGridViewPrint 2 { 3 private DataGridView dataGridView; 4 private PrintDocument printDocument; 5 private PageSetu…

Asp.Net Core AsyncLocal 异步上下文
引子 阅读以下代码,并尝试分析 代码解析 在主线程中,线程Id为1,为线程变量赋值 变量d6ff开启一个新的task,此时线程Id为4,变量d6ff,并调用Task1开启一个同步Task3,线程Id为1。变量d6ffÿ…

JDBC数据库操作
启动MySQL数据库服务 网络课堂中共享的MySQL 5.1压缩包下载后,直接在C盘根目录下解压后, 进入bin目录,执行“启动服务.cmd”即可启动服务器(如果未放在C盘根目 录下,需要修改配置文件my.ini和启动服务.cmd)…

mybatis学习7 实战项目
自己写一个实战项目,最好和hibernate做对比。转载于:https://www.cnblogs.com/liufei1983/p/7492214.html

凤凰网制作的 “中国挺住” 桌面与MSN,QQ头像下载
凤凰网制作的 “中国挺住” 桌面与MSN,QQ头像下载。 点击下载 转载于:https://www.cnblogs.com/yajiya/archive/2008/05/24/1206574.html

树莓派Android Things物联网开发:创建一个Things项目
【转载请注明出处: http://blog.csdn.net/leytton/article/details/77854144】《树莓派Android Things物联网开发》系列文章专栏 http://blog.csdn.net/column/details/17741.html树莓派Android Things物联网开发:入门及资料汇总树莓派Android Things物联网开发&…

使用JDBC进行MySQL 5.1的数据连接、查询、修改等操作练习。
目录 顺序查找 控制游标 条件与排序查询 1. where子语句 一般格式: 2. 排序 更新、添加与删除操作 1.更新 2.添加 3.删除 通用查询 顺序查找 所谓顺序查询,是指ResultSet对象一次只能看到一个数据行,使用 next()方法移到下一个数据行…

Tensorflow多线程输入数据处理框架(一)——队列与多线程
参考书 《TensorFlow:实战Google深度学习框架》(第2版) 对于队列,修改队列状态的操作主要有Enqueue、EnqueueMany和Dequeue。以下程序展示了如何使用这些函数来操作一个队列。 #!/usr/bin/env python # -*- coding: UTF-8 -*- # c…

编译器错误信息: CS0016
编译器错误信息: CS0016: 未能写入输出文件,..\Temporary ASP.NET Files\WebApp\d087da43\c12930d8\9ze28k_8.dll”--拒绝访问 编译器错误信息: CS0016: 未能写入输出文件“d:\WINDOWS\Microsoft.NET\Framework\v1.1.4322\Temporary ASP.NET Files\5177u\d087da43\c12930d8\9ze…

Visual Studio 2008 Samples Page
Visual Studio 2008 Samples Page http://msdn.microsoft.com/zh-cn/bb330936(en-us).aspx 如何实现 - 常规开发http://msdn.microsoft.com/zh-cn/library/ms246578.aspx Visual Studio 2008http://msdn.microsoft.com/zh-cn/library/aa187917.aspx .NET Framework 3.5http://m…

iOS开发网络篇—HTTP协议
说明:apache tomcat服务器必须占用8080端口 一、URL 1.基本介绍 URL的全称是Uniform Resource Locator(统一资源定位符) 通过1个URL,能找到互联网上唯一的1个资源 URL就是资源的地址、位置,互联网上的每个资源都有一个…

操作系统导论部分章节习题
进程 一。单选题(共1题,10.0分) 当某个进程进行系统调用以通过网络传输TCP数据包时,以下哪些步骤不会总是发生? A进程进入内核模式。 B,CPU的程序计数器转移到地址空间的内核部分。 C,该进…

Go-技篇第一 技巧杂烩
Go-技篇第一 技巧杂烩一句话技巧把你面向对象的大脑扔到家里吧,去拥抱接口。mikegehard学习如何使用Go的方式做事,不要把别的的编程风格强行用在Go里面。DrNic多用接口总比少用好。evanphx拥抱这种简洁、并行、工整的语言。francesc阅读官网golang.org上…

Visual Studio 2008 到底有多强?漫画告诉你 !
插入数据需要无数个SQL语句支持,而现在,你只需要有一个强大的LinQ巴士,就直接一次性全搞定,方便快捷 " 强大的中转站,帮你解决额外的问题,数据传输选择最优路径,完成工作。数据给了WCF&a…

使用c#生成高品质小空间的缩略图
dot自带的生成缩略图的方法是Bitmap.GetThumbnailImage这个方法生成的缩略图不够清晰,一般我们会使用Graphics高质量插值发生成清晰的缩略图,这时候大小会不尽人意,怎么办?我们可以使用jpeg压缩的方法压缩一下图片,这样…

将Project的内容导出成单独的XPO文件
AX跟VSS整合的版本管理可以通过创建知识库将当前层的代码全部签入到VSS中,但是如果不是一个团队开发solution,而是针对客户的需求随时做得一些小改动,一般都希望以Project的形式组织代码和发布代码。AX与VSS的整合没有提供一种方法可以将一个…

.Net Core MVC初学习
.net core已经出来很长一段时间了,没有很好的学习过,现在工作不那么忙了,参考官方文档,在这里记录自己的学习过程! ASP.NET Core 是一个跨平台的高性能开源框架,用于生成基于云且连接 Internet 的新式应用程…

编译原理考点复习
名词填空、名词解释、解答题: 文法是什么?文法、句子、句型、确定有穷自动机(DFA)、非确定有穷自动机(NFA)的定义?编译过程主要有哪些功能模块?有什么辅助模块(表格管理…

网络工程师如何才能实现职位晋升
一、如何在公司得到攀升的机会?如果不想保持低月薪,就运用你的技术积极参与公司的业务发展从最近我们公司招生来看,一个很明显的问题阴魂不散的笼罩在“网络工程师”这个行业。问题的根本是对网络工程师职业前途的严重误解。很多人认为目前的…