Android——后台服务

Android应用编程实验

实验名称:Android 后台服务
实验目的:通过Service设计后台服务程序,通过Broadcast实现信息广播机制
实验内容:

  1. 设计一个简单的后台音乐服务程序;
  2. 设计一个简单的信息广播程序示例;
  3. 利用Broadcast实现后台服务广播音乐的播放或暂停信息,接收器接收到信息后执行改变用户界面按钮上文本的操作。

文章目录

一、后台音乐服务程序

1.1 实验原理

通过按钮触发启动后台服务程序,进行后台服务的创建、启动和初始化,在服务程序中播放音乐。再通过按钮触发服务程序的销毁。

1.2 实验过程记录

1.2.1布局文件

为实现基本的程序效果,使用最简便的线性布局,将水平方式设置为vertical垂直布局。首先在线性布局中添加一个Textview组件,用来显示当前程序中,服务的执行状态。然后添加两个Button组件,分别用来触发开启后台服务和关闭后台服务。

1.2.2控制文件

1.2.2.1实例化对象

修改MainActivity.java文件,以实现对程序的控制。首先,如下图所示,实例化所需要的对象。
在这里插入图片描述

图1.1实例化对象

在上图中,分别实例化了用于触发事件的“Button”对象“startbtn”和“stopbtn”、与后台服务相关的“Context”对象“context”、用于在主控文件和服务控制文件之间传递信息的“Intent”对象“intent”、以及用于显示服务进程当前状态的“Textview”对象“txt”。

1.2.2.2重载onCreate方法

对构造函数进行修改,使其实现“关联布局文件和控制文件、设置按钮监听事件、创建intent对象”的功能。如图1.2所示:
在这里插入图片描述

图1.2重载onCreate方法

首先,通过findViewById方法关联图1.1中的相关组件,并为“startbtn”和“stopbtn”分别设置监听事件。之后,新建一个intent对象,将MainActivity和AudioSrv类相互绑定,使他们之间可以相互传递信息。

1.2.2.3编写mClick函数

为按钮的监听事件编写 mClick 类,以实现后台服务的开启和关闭功能。如图 1.3 所示:
在这里插入图片描述

图1.3编写mClick函数

上图中,构造了一个继承于 OnClickListener 的 mClick 类。通过判断点击按钮传入的参数v,可以对开启和结束服务进行区分,通过intent绑定机制,将信息传递给AudioSrv。之后分别通过setText方法,设置文本框的提示内容。

1.2.2.4编写AudioSrv服务程序

新建一个AudioSrv.java文件,用于创建消息服务程序。首先实例化对象,创建AudioSrv类,如图1.4所示:
在这里插入图片描述

图1.4创建AudioSrv类

上图中,构造了一个继承于 Service 的 AudioSrv 类,用于描述所要执行的服务。之后创建了一个MediaPlayer类的play对象,用于执行媒体音频相关的服务。

1.2.2.5重载AudioSrv构造方法

创建AudioSrv对象之后,会默认调用三个构造函数,分别为onBind、onCreate、onStartCommand、和onDestroy。
①首先重载onBind函数,如图1.5所示:
在这里插入图片描述

图1.5重载onBind函数

onBind方法用于与服务通信的信道进行绑定,这里返回空值。
②重载onCreate方法,如图1.6所示:
在这里插入图片描述

图1.6重载onCreate方法

onBind方法用于创建后台服务程序。首先,使用MediaPlayer.create() 方法调用资源文件中的音频对象。之后,通过Toast创建一个提示框,用于显示当前服务状态信息。
③重载onStartCommand方法,如图1.7所示:
在这里插入图片描述

图1.7重载onStartCommand方法

onBind方法用于启动后台服务程序。使用start方法启动play对象的播放进程,从而实现音频文件的播放。之后再通过Toast创建一个提示框,用于显示当前服务状态信息。
④重载 onDestroy方法,如图1.8所示:
在这里插入图片描述

图1.8重载 onDestroy方法

onDestroy方法用于销毁所有的后台服务程序,同时删除所有的服务调用。使用release方法释放媒体对象的调用,同时通过Toast显示提示消息。
1.2.3配置文件
由于程序中涉及到了与服务相关的AudioSrv.java文件,所以需要在AndroidManifest.xml文件中,对其进行相关注册和配置。
在这里插入图片描述

图1.9配置文件

如上图1.9所示,在原有的配置文件中,增加红色箭头所指部分,为AudioSrv注册service服务的权限。
1.2.4引入音频文件资源
为了能够确保后台音频服务正常启动,需要事先向项目目录中,添加音频文件资源。首先,如图1.10所示,在res目录下新建一个raw资源目录。
在这里插入图片描述

图1.10创建资源目录

在资源类型选项栏中,选择raw类型,如上图中红色箭头所示。之后,将本地的音频文件复制到res/raw文件夹下即可。

1.3 实验中存在的问题及解决方案

资源文件的文件命名问题
一开始我将引入的音频文件命名为happyWhistlingUkulele.mp4,编译之后出现如图1.11所示的错误信息:
在这里插入图片描述

图1.11报错信息截图

错误信息为:
“E:\AndroidStudio\homework\five\ex5_1\app\src\main\res\raw\happyWhistlingUkulele.mp4: Error: ‘W’ is not a valid file-based resource name character: File-based resource names must contain only lowercase a-z, 0-9, or underscore”。根据错误提示信息,资源文件名只能包含小写字母、数字以及下划线。于是我将文件名修改问happy.mp3,错误得以解决。

1.4 实验结果

程序具体运行效果请查看视频:http://47.95.13.239/Study/Android/show/ex5_1.mp4
程序编写结束后,启动安卓模拟器进行程序模拟运行。点击“启动后台音乐服务程序”,音乐开始播放,效果如图1.12和图1.13所示:
在这里插入图片描述

图1.12 开启服务

在这里插入图片描述

图1.13关闭服务

之后,点击“关闭后台音乐服务程序”,正在播放的音乐便停止播放。为了测试该服务是否是在后台运行,首先启动音乐服务,之后退出该APP至手机主界面,音乐依然正常播放。说明该音乐播放服务正在后台运行。

二、信息广播程序

2.1实验原理

Broadcast是Android系统应用程序之间传递数据的一种机制。当系统间需要传递某些信息时,由系统自身通过系统调用来引发事件。这种调用是由Broadcast类来实现的,这种系统调用称为广播机制。

2.2 实验过程记录

2.2.1布局文件

为实现基本的程序效果,使用最简便的线性布局,将水平方式设置为vertical垂直布局。首先在线性布局中添加一个TextView组件,用来显示接收到的广播消息。然后添加一个Button组件,分别用来触发广播消息的发送。

2.2.2控制文件

2.2.2.1实例化对象

修改MainActivity.java文件,以实现对程序的控制。首先,如下图所示,实例化所需要的对象。
在这里插入图片描述

图2.1实例化对象

在上图中,分别实例化了用于触发广播事件的“Button”对象“btn”,以及用于显示广播信息内容的“TextView”对象“txt”。

2.2.2.2重载onCreate方法

对构造函数进行修改,使其实现“关联布局文件和控制文件、设置按钮监听事件、创建intent对象”的功能。如图2.2所示:
在这里插入图片描述

图2.2重载onCreate方法

通过findViewById方法关联Textview和Button组件,并为“btn”对象设置监听事件。

2.2.2.3编写mClick函数

为按钮的监听事件编写 mClick 类,以实现广播功能的开启。如图 2.3 所示:
在这里插入图片描述

图2.3编写mClick函数

上图中,构造了一个继承于 OnClickListener 的 mClick 类。首先创建一个intent对象,并设置该对象的action属性。然后创建一个bundle对象,通过键值对的形式封装广播信息。最后sendBroadcast方法将intent广播出去。

2.2.2.4编写TestReceiver控制程序

新建一个TestReceiver.java文件,用于接收广播消息并通过Textview组件进行显示,如图2.4所示:
在这里插入图片描述

图2.4编写TestReceiver类

上图中,定义了一个str字符串,通过getExtras方法接收键为hello的字符串,并通过setText方法将textview组件的内容设置为str字符串。

2.2.3配置文件

由于程序中涉及到了与接收广播消息相关的TestReceiver.java文件,所以需要在AndroidManifest.xml文件中,对其进行相关注册和配置,如图2.5所示:
在这里插入图片描述

图2.5配置文件修改

如上图2.5所示,在原有的文件中追加红框中的内容,以注册TestReceiver.java文件和该文件的action属性。

2.3 实验中存在的问题及解决方案

广播消息接收不正常

编写完程序之后,我在电脑的安卓模拟器上进行测试,模拟器版本是Android9.0,API版本28。在该版本的模拟器上,广播消息接收不正常。为了测试程序是否进入了onReceiver构造函数,我改写该函数,在函数中添加“MainActivity.txt.setText(“Here…”);”语句(如图2.6所示),试图判断:在点击按钮之后,或者说是广播发出之后,程序是否进入了onReceiver函数。如果程序正常进入到了该函数,则可以在TextView中显示Here标志,说明广播接收功能正常,那么问题可能出在intent的键值和页面绑定等方面;如果程序不能正常在TextView中显示Here标志,那么说明广播的接收功能异常,问题可能出现在action属性等方面。
在这里插入图片描述

图2.6 查找广播接收问题原因

下面运行程序,发现再点击按钮之后,不能正常显示"Here…"字符串。我猜测(依据上次做简易相机的经历)问题有可能出在Android版本上,于是重新创建了一个Android7.1.1,API25版本的安卓模拟器,再次运行程序。结果程序正常运行(如图2.8所示),接收到了广播消息。
在这里插入图片描述

图2.7点击按钮之前的初始化界面

在这里插入图片描述

图2.8接收到广播消息的界面

图2.7是点击按钮之前的初始化界面,图2.8是点击按钮之后,接收到广播消息的界面。可以看到,intent正常接收到了广播消息,并通过setText方法将得到的文本信息正常显示在了textview组件上。
那么,问题是否真的是由于Android版本造成的呢?我将程序再次放到Android9.0的模拟器上运行,发现再点击按钮之后,程序依然没有反应。所以说该问题确实与Android版本有关。

2.4 实验结果

程序具体运行效果请查看视频:http://47.95.13.239/Study/Android/show/ex5_2.mp4
根据之前调试的问题及解决方法,我将笔记本上的模拟器版本更新为Android7.1.1,运行程序,效果如下图2.9、2.10所示:
在这里插入图片描述

图2.9初始化界面

在这里插入图片描述

图2.10接收广播界面

在初始化界面(图2.9)中,textview文本框中显示“广播消息Broadcast测试”,之后点击发送消息按钮,程序将发送广播消息,并通过intent接收,最后将接收到的消息显示在textview组件上,实现上图2.10中的效果。

三、后台服务广播音乐的播放或暂停

3.1实验原理

通过一个后台服务程序,广播音乐的播放或暂停信息,接收器接收到信息之后,执行改变用户界面上的文本操作。

3.2实验过程记录

3.2.1布局文件

布局文件同样采用线性布局,根据程序需求,只需要添加两个Button组件,分别用来表示播放和停止按钮。

3.2.2控制文件

3.2.2.1实例化对象

修改MainActivity.java文件,以实现对程序的控制。首先,如下图3.1所示,实例化所需要的对象。
在这里插入图片描述

图3.1实例化对象

在上图中,分别实例化了用于触发事件的“Button”类的对象“btnStart”和“btnStop”、与广播服务相关的“Broadcast”类的对象“mBroadcast”、用于在主控文件和服务控制文件之间传递信息的“Intent”类的对象“intent”,并且设置了音频文件的路径变量字符串AUDIO_PATH。

3.2.2.2重载onCreate方法

对构造函数进行修改,使其实现“关联布局文件和控制文件、创建filter对象、注册广播接收器”的功能。如图3.2所示:
在这里插入图片描述

图3.2重载onCreate方法

首先,通过findViewById方法关联图3.1中的相关组件,之后,新建一个filter对象,并将其action设置为“music”。创建Broadcast类的mBroadcast对象,并使用registerReceiver方法注册广播监听器。

3.2.2.3重载onDestroy方法

在关闭接收器之后,我们需要取消注册广播接收器,如图3.3所示,重载onDestroy方法。
在这里插入图片描述

图3.3重载onDestroy方法

为防止内存溢出等意外情况,需要在onDestroy方法中,使用unregisterReceiver函数取消对广播接收器的注册。

3.2.2.4编写ClickHandler函数

程序通过对按钮的监听,来触发事件,从而做出反应。下面编写ClickHandler函数,用来对按钮的监听事件做出处理,如图3.4所示。
在这里插入图片描述

图3.4编写ClickHandler函数

ClickHandler函数首先通过getId方法,取出参数v所对应组件的id,之后通过switch\case语句进行判断。如果点击了id为btnPlayOrPause的组件,就通过intent对象在主控文件和AudioService类之间传递数据,通过startService方法开启intent所绑定的服务;如果点击了id为btnStop的组件,就结束intent所绑定的服务。

3.2.2.5编写AudioService控制文件

新建一个AudioService.java文件,用于具体的广播消息的服务。
①首先实例化相关的对象,如图3.5所示:
在这里插入图片描述

图3.5实例化对象

在上图中,分别实例化了用于传递数据的“Intent”类的对象“intent2”和“Bundle”类的对象“bundle2”、与媒体播放服务相关的“MediaPlayer”类的对象“mediaPlayer”、以及用于表示媒体文件路径的“String”类的对象“audioPath”。
②重载onBind方法,如图3.6所示:
在这里插入图片描述

图3.6重载onBind函数

onBind方法用于与服务通信的信道进行绑定,这里返回空值。
③重载 onStartCommand方法,如图3.7所示:
在这里插入图片描述

图3.7重载onStartCommand函数

onStartCommand函数实现的功能是:控制媒体文件的播放、暂停与结束,同时通过调用sendUpdateUI方法,对按钮的文本信息进行修改,具体过程不再赘述。
④重载onDestroy方法,如图3.8所示:
在这里插入图片描述

图3.8重载onDestroy函数

onDestroy方法用于销毁所有的后台服务程序,同时删除所有的服务调用。使用release方法释放媒体对象的调用,同时通过调用sendUpdateUI方法实现对按钮文字的更新。
⑤编写 sendUpdateUI方法,如图3.9所示:
在这里插入图片描述

图3.9编写 sendUpdateUI函数

sendUpdateUI函数用于发送广播消息,后台服务把键名为backFlag的消息广播出去。发送成功后,Activity里的updateUIReceiver的onReceiver方法就能做出相应的更新按钮文字的工作。

3.2.2.6编写 Broadcast控制文件

新建一个Broadcast.java文件,用于接收广播消息并通过Textview组件进行界面的更新显示,如图3.10所示:
在这里插入图片描述

图3.10 Broadcast控制文件

该方法中,重载了onReceiver方法,从intent中获取接收到的广播数据,使用switch\case语句进行判断,其中0是播放、1是暂停,2表示停止播放。

3.2.3配置文件

由于程序中涉及到了与接收广播消息相关的AudioService.java文件,所以需要在AndroidManifest.xml文件中,对其进行相关注册和配置,如图3.11所示:
在这里插入图片描述

图3.11修改配置文件

如上图2.5所示,在原有的文件中追加红框中的内容,以注册AudioService.java文件和该文件的action属性。

3.3 实验中存在的问题及解决方案

3.3.1安卓源文件链接失败问题

编写完成文件之后,经过我的测试,当在AndroidManifest.xml配置文件中,添加有关注册AudioService服务的标签之后,在编译时总会出错,错误信息截图如下图3.12所示。
在这里插入图片描述

图3.12 编译报错

在报错信息中,有一句话引起了我的注意“Daemon: AAPT2 aapt2-3.2.0-4818971-windows Daemon #0”,通过查找相关资料,我了解到:AAPT2是Android Asset Packaging Tool的缩写,是编译和打包资源的工具。之前的错误,是由于AAPT2强校验AndroidManifest.xml中的嵌套关系造成的,说白了就是我在添加service标签的时候,格式错了。于是修改文件为图3.13红框中所示。
在这里插入图片描述

图3.13修改配置文件

重新进行编译操作,该错误得以解决。

3.3.2 ClickHandler方法未被调用问题

通过修改配置文件,解决了编译报错问题,然而我发现在MainActivity.java主控文件里,用于被触发的ClickHandler方法显示波浪线,表示该方法未被调用。通过阅读程序我们知道,该ClickHandler方法是用于对按钮被触发后事件的相应,如果他在程序中没有被调用,那么程序运行时一定会出问题。
仔细阅读MainActivity.java文件,我发现在onCreate构造方法中,没有写setOnClickListener方法,当时我认为也许是这里有问题,于是我尝试修改文件,使用setOnClickListener的方式实现监听接口,如图3.14所示。
在这里插入图片描述

图3.14更改监听事件

然而并没有什么用。程序中依然没有调用ClickHandler。我想,也许程序中监听事件的实现方法上没有问题,只是设置监听事件的方式与以往不同。也是我查找了一下资料,发现实现监听事件有四种方式:
①使用匿名内部类的方式实现监听事件。
②使用外部类的方式实现监听事件。
③使用接口方式实现监听事件。
④直接绑定到标签。
在程序里,实际上是使用了第四种方式。
在这里插入图片描述

图3.15绑定到标签的方式
(图片引自:https://blog.csdn.net/kyi_zhu123/article/details/52601691)

这样的话,我需要在原有的基础上,修改布局文件里的内容,为每一个button组件,增加onClick属性,并将其绑定到ClickHandler方法上,于是改写程序为下图3.16所示。
在这里插入图片描述

图3.16修改布局文件

如上图3.16所示,增加红色框图中的内容,使button组件全部绑定到ClickHandler方法上。现在,主控文件中的ClickHandler已经不再显示未被调用了。现在编译程序,并在模拟器上运行,在点击播放按钮之后,按钮文字变为了“暂停”,说明现在鼠标的监控事件已经奏效,问题得以解决。

3.3.3音乐服务不播放问题

为了能使程序正常播放音频文件,我通过AndroidStudio的“Device File Explorer”设备文件管理器,将事先准备好的音频文件拷贝到了“/sdcard/Music”文件夹下,名将其命名为“happy.mp3”,同时在主控文件中,将AUDIO_PATH字符串的值改写为“/sdcard/Music/happy.mp3”。运行程序,发现虽然程序能够对鼠标的点击做出相应,但是音频文件依然没有播放。
重启程序,点击播放按钮,并在LogCat下查看程序运行情况。可以看到,在点击播放按钮的时候,日志会输出错误信息,如图3.17所示。
在这里插入图片描述

图3.17错误日志

在下面的日志输出框中,用红框圈出的部分表示输出的错误信息,上方代码框中,用红框标出来的表示抛出异常的位置。
问题分析:
仔细看一下,发现报错这个是因为媒体文件初始化的时候,因为此时mediaPlayer 为null,所以需要new一个mediaPlayer对象并进行初始化,问题就出在初始化上。我认为这一部分的代码,能出问题的有两个原因:广播接收到的媒体文件路径问题、或者初始化有逻辑问题。
①首先我来测试一下,是不是因为广播接收器接收到的媒体文件路径audioPath有误,导致mediaPlayer在初始化的时候不能正常找到正确的媒体文件呢?
为了能够得到程序接收到的广播消息,我在布局文件中,增设了一个textview组件,用来显示媒体文件的路径信息,并将onStartCommand方法进行修改,如图3.18所示:
在这里插入图片描述

图3.18修改onStartCommand方法

我在消息广播接收器接收到数据之后,增加了一个“MainActivity.txt.setText(audioPath);”语句,图中红框所示,用来将接收到的媒体文件路径信息显示在界面的文本框中,从而判断是不是广播消息接收机制出现了问题。
运行代码,并点击“播放”按钮,得到以下结果(图3.19):
在这里插入图片描述

图3.19运行调试结果

通过在界面上展示的textview组件,可以看到,当前接收到的媒体文件路径是没有问题的!所以说,问题应该不在于广播接收机制,而是在于媒体播放部分。
我又将媒体路径audioPath直接设置成了“/sdcard/Music/happy.mp3”,但程序在运行的时候,依旧没有音频播放,也依旧会在日志中输出上面提到的错误信息。这更加证明了,程序错误之处在于媒体播放部分。
②这样的话,基本可以确定程序的错误之处在于媒体文件的播放部分。
然而,这部分没有问题。
我在查阅资料的过程中发现,如果想要访问SD卡中的文件,需要在配置文件中声明读取SD卡的权限,之前做的程序中也有类似的增加权限的例子。我这才恍然大悟——忘加SD卡访问权限了。于是我在配置文件中加上了对SD卡的访问权限,在模拟器运行测试。运行成功!问题算是得以解决。

3.3.4动态权限申请

既然提到了SD卡权限的问题,那就深究一下。之前测试过的程序中,很多因为版本问题不兼容,我发现其原因多数是因为权限的问题。经过查阅资料我发现,在Android6.0版本以后,求权限的管理变得更加严格了,不仅仅需要在配置文件中声明需要申请的权限,而是需要在运行程序的时候进行动态申请。之前由于大家的手机版本普遍都是Android7.0及以上的版本,对权限的管理比较严格,如果仅仅在配置文件中添加申请权限,就会导致在程序开启时,由于没有获取到足够的权限而不能正常运行。
现在尝试去动态申请权限。
①首先在配置文件中声明对SD卡的读取权限,如图3.20所示:
在这里插入图片描述

图3.20增加SD卡读写权限

②编写 PermisionUtils类
下面编写PermisionUtils类,用于动态权限的申请,如图3.21所示:
在这里插入图片描述

图3.21动态权限申请

首先要设置两个私有静态变量,用来表示SD卡的读写权限。之后编写verifyStoragePermissions方法,用来检测和申请SD卡权限。该方法将检测APP程序是否具有SD卡的读写权限,没有权限就会去动态申请,弹出申请对话框,获取用户的允许。
③调用verifyStoragePermissions方法
将写好的verifyStoragePermissions方法在需要授权的地方进行调用,这里我将它置于MainActivity.java的onCreate构造方法中,这样程序首次运行时,都会进行权限的检查。值得注意的是,程序获取SD卡读写权限成功后,就会一直持有该权限。就算你把代码里的verifyStoragePermissions(this); 语句删了,只要不卸载应用程序,权限就仍然存在。
现在可以进行动态权限的测试了。编译并在模拟器上运行,首次运行时,会出现如下图3.22所示的权限申请框。
在这里插入图片描述

图3.22动态权限申请框

权限申请框中表述的意思为:是否允许ex5_3这个程序,访问你设备上的照片、媒体和文件吗?点击“ALLOW”表示允许,这样程序就获取了用户所授权的,对SD卡的读写权限。
为了确保动态权限对于高版本的Android设备同样行之有效,我在Android9.0版本的模拟器上进行了测试,首次运行时截图如下图2.23所示:
在这里插入图片描述

图2.23Android9.0版本测试

首次运行时,同样会显示这样的动态申请框。点击允许之后,接下来的程序运行均正常稳定。

3.4实验结果

程序具体运行结果请查看视频:http://47.95.13.239/Study/Android/show/ex5_3.mp4
将程序卸载并重新在Android9.0版本的模拟器上进行测试。初次运行时,将显示动态权限申请框(如图3.24),点击“允许”后,进入初始化界面(如图3.25),点击“播放”即可播放音频文件,此时按钮上的文本由“播放”变为了“暂停”(如图3.26),点击“暂停”即可暂停音频播放,点击“停止”按钮可停止音频播放(如图3.27)。
在这里插入图片描述

图3.24权限申请

在这里插入图片描述

图3.25初始化界面

在这里插入图片描述

图3.26播放音频

在这里插入图片描述

图3.27赞暂停播放

四、附录

说明:附录只包含关键的文件,三个项目的工程文件均已上传至GitHub,如有需要可自行查看。GitHub网址:https://github.com/ZHJ0125/AndroidLeaning

4.1 ex5_1

4.1.1 activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">
    <TextView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:id="@+id/text1"
        android:text="@string/hello"
        android:textSize="24dp"/>
    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:id="@+id/btn1"
        android:text="启动后台音乐服务程序"
        android:textSize="24dp"/>
    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:id="@+id/btn2"
        android:text="关闭后台音乐服务程序"
        android:textSize="24dp"/>

</LinearLayout>

4.1.2 MainActivity.java

package zhj.com.ex5_1;

import android.app.Activity;
import android.os.Bundle;
import android.content.Context;
import android.content.Intent;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.TextView;

public class MainActivity extends Activity {
    Button startbtn,stopbtn;
    Context context;
    Intent intent;
    static TextView txt;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        startbtn = (Button)findViewById(R.id.btn1);
        stopbtn = (Button)findViewById(R.id.btn2);
        startbtn.setOnClickListener(new mClick());
        stopbtn.setOnClickListener(new mClick());
        txt = (TextView)findViewById(R.id.text1);
        intent = new Intent(MainActivity.this, AudioSrv.class);
    }
    class mClick implements OnClickListener{
        @Override
        public void onClick(View v) {
            if (v == startbtn){
                MainActivity.this.startService(intent);
                txt.setText("Start Service ......");
            }
            else if (v == stopbtn){
                MainActivity.this.stopService(intent);
                txt.setText("Stop Service ......");
            }
        }
    }
}

4.1.3 AudioSrv.java

package zhj.com.ex5_1;

import android.app.Service;
import android.content.Intent;
import android.media.MediaPlayer;
import android.os.IBinder;
import android.widget.Toast;

public class AudioSrv extends Service{
    MediaPlayer play;

    @Override
    public IBinder onBind(Intent intent) {
        return null;
    }
    @Override
    public void onCreate(){
        super.onCreate();
        play = MediaPlayer.create(this, R.raw.happy);
        Toast.makeText(this, "创建后台服务...", Toast.LENGTH_LONG).show();
    }
    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        super.onStartCommand(intent, flags, startId);
        play.start();
        Toast.makeText(this, "启动后台服务程序,播放音乐...", Toast.LENGTH_LONG ).show();
        return START_STICKY;
    }
    @Override
    public void onDestroy() {
        play.release();
        super.onDestroy();
        Toast.makeText(this, "销毁后台服务...", Toast.LENGTH_LONG).show();
    }
}

4.1.4 AndroidManifest.xml

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="zhj.com.ex5_1">

    <application
        android: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">
        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
        <service android:name=".AudioSrv" android:enabled="true"/>
    </application>

</manifest>

4.2 ex5_2

(只在Android7.0版本上测试通过了)

4.2.1 activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_height="match_parent"
    android:layout_width="match_parent"
    android:orientation="vertical">
    <TextView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:id="@+id/txt1"
        android:text="广播消息Broadcast测试"
        android:textSize="20dp"
        android:layout_gravity="center"
        android:gravity="center"/>
    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:id="@+id/btn"
        android:layout_gravity="center"
        android:text="发送消息"/>

</LinearLayout>

4.2.2 MainActivity.java

package zhj.com.ex5_2;

import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.TextView;

public class MainActivity extends Activity {
    static TextView txt;
    Button btn;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        txt = (TextView)findViewById(R.id.txt1);
        btn = (Button)findViewById(R.id.btn);
        btn.setOnClickListener(new mClick());
    }
    class mClick implements OnClickListener{
        @Override
        public void onClick(View v) {
            Intent intent = new Intent();
            intent.setAction("abc");
            Bundle bundle = new Bundle();
            bundle.putString("hello", "这是广播消息");
            intent.putExtras(bundle);
            sendBroadcast(intent);
        }
    }
}

4.2.3 TestReceiver.java

package zhj.com.ex5_2;

import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;

public class TestReceiver extends BroadcastReceiver{
    @Override
    public void onReceive(Context context, Intent intent) {
        //MainActivity.txt.setText("Here........");
        String str = intent.getExtras().getString("hello");
        MainActivity.txt.setText(str);
    }
}

4.2.4 AndroidManifest.xml

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="zhj.com.ex5_2">

    <application
        android: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">
        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
        <receiver android:name=".TestReceiver">
            <intent-filter>
                <action android:name="abc"/>
            </intent-filter>
        </receiver>
    </application>

</manifest>

4.3 ex5_3

4.3.1 activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">
    <Button
        android:onClick="ClickHandler"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:id="@+id/btnPlayOrPause"
        android:text="播放"/>
    <Button
        android:onClick="ClickHandler"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:id="@+id/btnStop"
        android:text="停止"/>
    <TextView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:id="@+id/txt"/>


</LinearLayout>

4.3.2 MainActivity.java

package zhj.com.ex5_3;

import android.app.Activity;
import android.os.Bundle;
import android.content.Intent;
import android.content.IntentFilter;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;

import static zhj.com.ex5_3.PermisionUtils.verifyStoragePermissions;

public class MainActivity extends Activity {
    Broadcast mBroadcast=null;
    static Button btnStart;
    Button btnStop;
    Intent intent;
    String AUDIO_PATH="/sdcard/Music/happy.mp3";
    static TextView txt;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        verifyStoragePermissions(this);
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        txt = (TextView)findViewById(R.id.txt); //用于测试广播接收的文件路径是否正确
        btnStart=(Button)findViewById(R.id.btnPlayOrPause);
        btnStop=(Button)findViewById(R.id.btnStop);
        IntentFilter filter=new IntentFilter("music");
        mBroadcast=new Broadcast();
        registerReceiver(mBroadcast,filter);
    }

    @Override
    protected void onDestroy()
    {
        super.onDestroy();
        unregisterReceiver(mBroadcast);
    }

    public void ClickHandler(View v)
    {
        switch (v.getId())
        {
            case R.id.btnPlayOrPause:
                intent=new Intent(MainActivity.this,zhj.com.ex5_3.AudioService.class);
                Bundle bundle=new Bundle();
                bundle.putString("audioPath",AUDIO_PATH);
                intent.putExtras(bundle);
                startService(intent);
                break;
            case R.id.btnStop:
                if(intent != null)
                {
                    stopService(intent);
                }
                break;
        }
    }
}

4.3.3 AudioService.java

package zhj.com.ex5_3;

import android.app.Service;
import android.content.Intent;
import android.media.MediaPlayer;
import android.os.Bundle;
import android.os.IBinder;
import android.util.Log;

public class AudioService extends Service{
    private MediaPlayer mediaPlayer = null;
    private Intent intent2=null;
    private Bundle bundle2=null;
    private String audioPath;

    private void sendUpdateUI(int flag)
    {
        intent2=new Intent();
        intent2.setAction("music");
        bundle2=new Bundle();
        bundle2.putInt("backFlag",flag);
        intent2.putExtras(bundle2);
        sendBroadcast(intent2);
    }

    public IBinder onBind(Intent intent)
    {
        return null;
    }

    @Override
    public int onStartCommand(Intent intent,int flags,int startId)
    {
        super.onStartCommand(intent,flags,startId);
        audioPath=intent.getExtras().getString("audioPath");

        MainActivity.txt.setText(audioPath);

        if(mediaPlayer != null && mediaPlayer.isPlaying())
        {
            mediaPlayer.pause();
            sendUpdateUI(1);
        }
        else {
            if (mediaPlayer == null)
            {
                mediaPlayer = new MediaPlayer();
                try {
                    mediaPlayer.reset();
                    mediaPlayer.setDataSource(audioPath);
                    mediaPlayer.prepare();
                } catch (Exception e) {
                    Log.e("player","player prepare() err");
                }
            }
            mediaPlayer.start();
            sendUpdateUI(0);
        }
        return START_STICKY;
    }

    @Override
    public void onDestroy()
    {
        if(mediaPlayer != null)
        {
            mediaPlayer.reset();
            mediaPlayer.release();
            mediaPlayer = null;
            sendUpdateUI(2);
        }
        super.onDestroy();
    }

}

4.3.4 Broadcast.java

package zhj.com.ex5_3;

import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;

class Broadcast extends BroadcastReceiver{
    @Override
    public void onReceive(Context context,Intent intent)
    {
        int backFlag=intent.getExtras().getInt("backFlag");
        switch (backFlag)
        {
            case 0:
                MainActivity.btnStart.setText("暂停");
                break;
            case 1:
            case 2:
                MainActivity.btnStart.setText("播放");
                break;
        }
    }
}

4.3.5 PermisionUtils.java

package zhj.com.ex5_3;

import android.Manifest;
import android.app.Activity;
import android.content.pm.PackageManager;
import android.support.v4.app.ActivityCompat;

public class PermisionUtils {

    // Storage Permissions
    private static final int REQUEST_EXTERNAL_STORAGE = 1;
    private static String[] PERMISSIONS_STORAGE = {
            Manifest.permission.READ_EXTERNAL_STORAGE,
            Manifest.permission.WRITE_EXTERNAL_STORAGE};

    /**
     * Checks if the app has permission to write to device storage
     * If the app does not has permission then the user will be prompted to
     * grant permissions
     *
     * @param activity
     */
    public static void verifyStoragePermissions(Activity activity) {
        // Check if we have write permission
        int permission = ActivityCompat.checkSelfPermission(activity,
                Manifest.permission.WRITE_EXTERNAL_STORAGE);

        if (permission != PackageManager.PERMISSION_GRANTED) {
            // We don't have permission so prompt the user
            ActivityCompat.requestPermissions(activity, PERMISSIONS_STORAGE,
                    REQUEST_EXTERNAL_STORAGE);
        }
    }
}

4.3.6 AndroidManifest.xml

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="zhj.com.ex5_3">
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"></uses-permission>
    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"></uses-permission>

    <application
        android: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">
        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
        <service android:name=".AudioService" android:enabled="true">
            <intent-filter>
                <action android:name="music"/>
            </intent-filter>
        </service>
    </application>

</manifest>

五、实验总结

这次实验中出现的问题还是比较多的,解决问题的过程也比较曲折,做了一些弯路,不过好在最后现有的问题都已得到解决。虽然这次实验中出现了很多问题,但通过解决问题,我也收获了很多编程方面的知识,能力得到了提升。
在Android编程方面,主要掌握了以下问题:

  1. 安卓资源文件命名问题
  2. 安卓版本控制问题
  3. 配置文件校验问题
  4. 设置监听事件的四种表达方式
  5. 动态权限申请问题
  6. 了解了service后台服务程序和Broadcast消息广播机制的实现过程

六、部分参考资料

  1. aapt2 工具介绍 https://www.jianshu.com/p/839969887e2c
  2. android 监听器实现的四种方式 https://blog.csdn.net/kyi_zhu123/article/details/52601691
  3. 将文件放到Android模拟器的SD卡中的两种解决方法
    https://blog.csdn.net/qq373036876/article/details/51122479
  4. Android Studio3.3如何将电脑文件上传到模拟器
    https://jingyan.baidu.com/article/d169e1861e8d9c436611d8fa.html
  5. MediaPlayer基本使用方式 https://blog.csdn.net/qq_33210042/article/details/78341518
  6. 解决安卓7.0系统写入SD卡权限失败问题
    https://blog.csdn.net/wi2rfl78/article/details/78314286
发布了44 篇原创文章 · 获赞 25 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/ZHJ123CSDN/article/details/90339096
今日推荐