zoukankan      html  css  js  c++  java
  • 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:AndroidStudiohomeworkfiveex5_1appsrcmain es awhappyWhistlingUkulele.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,之后通过switchcase语句进行判断。如果点击了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中获取接收到的广播数据,使用switchcase语句进行判断,其中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
  • 相关阅读:
    用Photoshop制作一寸照片
    每天只问孩子这4句话,胜过百般疼爱
    机场也有打折季,你知道吗?请收好这份扫货指南
    这8个习惯会让孩子越来越笨,甚至抑郁!父母赶紧收手
    读后感该怎么写
    vue-cli 4058错误
    bootstrap img自适应
    移动端高清、多屏适配方案
    去掉页面滚动条
    js 404页面跳转
  • 原文地址:https://www.cnblogs.com/ZHJ0125/p/12904516.html
Copyright © 2011-2022 走看看