Android——后台服务

news/2024/11/20 19:43:48/

Android应用编程实验

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

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

文章目录

  • Android应用编程实验
  • 一、后台音乐服务程序
    • 1.1 实验原理
    • 1.2 实验过程记录
      • 1.2.1布局文件
      • 1.2.2控制文件
        • 1.2.2.1实例化对象
        • 1.2.2.2重载onCreate方法
        • 1.2.2.3编写mClick函数
        • 1.2.2.4编写AudioSrv服务程序
        • 1.2.2.5重载AudioSrv构造方法
    • 1.3 实验中存在的问题及解决方案
    • 1.4 实验结果
  • 二、信息广播程序
    • 2.1实验原理
    • 2.2 实验过程记录
      • 2.2.1布局文件
      • 2.2.2控制文件
        • 2.2.2.1实例化对象
        • 2.2.2.2重载onCreate方法
        • 2.2.2.3编写mClick函数
        • 2.2.2.4编写TestReceiver控制程序
      • 2.2.3配置文件
    • 2.3 实验中存在的问题及解决方案
    • 2.4 实验结果
  • 三、后台服务广播音乐的播放或暂停
    • 3.1实验原理
    • 3.2实验过程记录
      • 3.2.1布局文件
      • 3.2.2控制文件
        • 3.2.2.1实例化对象
        • 3.2.2.2重载onCreate方法
        • 3.2.2.3重载onDestroy方法
        • 3.2.2.4编写ClickHandler函数
        • 3.2.2.5编写AudioService控制文件
        • 3.2.2.6编写 Broadcast控制文件
      • 3.2.3配置文件
    • 3.3 实验中存在的问题及解决方案
      • 3.3.1安卓源文件链接失败问题
      • 3.3.2 ClickHandler方法未被调用问题
      • 3.3.3音乐服务不播放问题
      • 3.3.4动态权限申请
    • 3.4实验结果
  • 四、附录
    • 4.1 ex5_1
      • 4.1.1 activity_main.xml
      • 4.1.2 MainActivity.java
      • 4.1.3 AudioSrv.java
      • 4.1.4 AndroidManifest.xml
    • 4.2 ex5_2
      • 4.2.1 activity_main.xml
      • 4.2.2 MainActivity.java
      • 4.2.3 TestReceiver.java
      • 4.2.4 AndroidManifest.xml
    • 4.3 ex5_3
      • 4.3.1 activity_main.xml
      • 4.3.2 MainActivity.java
      • 4.3.3 AudioService.java
      • 4.3.4 Broadcast.java
      • 4.3.5 PermisionUtils.java
      • 4.3.6 AndroidManifest.xml
  • 五、实验总结
  • 六、部分参考资料

一、后台音乐服务程序

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"><TextViewandroid:layout_width="match_parent"android:layout_height="wrap_content"android:id="@+id/text1"android:text="@string/hello"android:textSize="24dp"/><Buttonandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:id="@+id/btn1"android:text="启动后台音乐服务程序"android:textSize="24dp"/><Buttonandroid: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;@Overrideprotected 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{@Overridepublic 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;@Overridepublic IBinder onBind(Intent intent) {return null;}@Overridepublic void onCreate(){super.onCreate();play = MediaPlayer.create(this, R.raw.happy);Toast.makeText(this, "创建后台服务...", Toast.LENGTH_LONG).show();}@Overridepublic 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;}@Overridepublic 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"><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"><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"><TextViewandroid: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"/><Buttonandroid: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;@Overrideprotected 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{@Overridepublic 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{@Overridepublic 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"><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"><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"><Buttonandroid:onClick="ClickHandler"android:layout_width="match_parent"android:layout_height="wrap_content"android:id="@+id/btnPlayOrPause"android:text="播放"/><Buttonandroid:onClick="ClickHandler"android:layout_width="match_parent"android:layout_height="wrap_content"android:id="@+id/btnStop"android:text="停止"/><TextViewandroid: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;@Overridepublic 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);}@Overrideprotected 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;}@Overridepublic 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;}@Overridepublic 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{@Overridepublic 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 Permissionsprivate 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 permissionint permission = ActivityCompat.checkSelfPermission(activity,Manifest.permission.WRITE_EXTERNAL_STORAGE);if (permission != PackageManager.PERMISSION_GRANTED) {// We don't have permission so prompt the userActivityCompat.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><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"><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

http://www.ppmy.cn/news/650945.html

相关文章

什么是边缘计算?

注&#xff1a;本篇翻译自施巍松教授的论文《Edge Computing : Vision and Challenges》 目录 文章目录 摘要简介什么是边缘计算什么是边缘计算边缘计算的优点 案例研究云卸载视频分析智能家居智慧城市 机遇和挑战编程可行性命名数据抽象服务管理私密性最优化指标 小结 摘要 …

什么是项目管理?怎么管?(一)

前言 项目管理是团队建立共同语言的需要、保证每个项目结果的需要、积累企业过程资产必要&#xff0c;同时还是打造企业战略执行力和项目管理核心竞争力的需要。 项目管理就是要做好项目的事、做好团队的事、做好企业组织的事、做好商业的事&#xff0c;就是又要当爹又要当妈。…

fiddler抓包——手机添加代理后APP连不上网的常见4种原因及解决办法

更新 遇到的问题是&#xff1a; Android6.0及以下系统可以抓包&#xff0c;而Android7.0及以上系统不能再抓包。 原因&#xff1a; Android7.0的版本新增了证书验证&#xff0c;即app内不再像原来一样默认信任用户的证书。 Android7.0以上无法抓包问题解决。 fiddler抓包—…

Android 编写开启自启动的脚本服务

前言 因为公司有一款手机在升级之后用户找不到内部sdcard 中的数据&#xff0c;分析了主要原因是因为升级前后内部sdcard 的链接的路径改变了。之前sdcard的数据在/sdcard/emulated/ 目录下&#xff0c;升级时候放在了/sdcard/emulated/0/ 下面。一个解决方案就是在手机启动的时…

java是什么?好学吗?

什么是程序员以及编程语言 java是一种编程语言&#xff0c;是软件开发的一种&#xff0c;而软件开发人员属于程序员的一种。 要了解java是什么&#xff0c;最好是先对程序员有一个基本了解&#xff0c;有兴趣的朋友可以先看一看我的这篇关于程序员的文章&#xff1a; 程序员是…

网页上怎么查询服务器地址,怎么查看一个网页的服务器地址

怎么查看一个网页的服务器地址 内容精选 换一换 本章节介绍如何通过控制台查看云手机实例的详细信息。登录管理控制台。在管理控制台左上角,选择待查看云手机所在的区域。在服务列表页,选择“计算 > 云手机 CPH”。进入云手机页面。进入云手机页面。单击左侧导航栏的“实例…

es文件浏览器开启ftp服务器,es文件浏览器访问ftp服务器

es文件浏览器访问ftp服务器 内容精选 换一换 obsftp工具于2021年2月9日正式下线,下线后OBS不再对此工具提供维护和客户支持服务,给您带来不便敬请谅解。文件传输协议(File Transfer Protocol,FTP)当前广泛应用于个人和企业场景,而随着使用时间越来越长,用户越来越多,本地…

阿里云国际版短信验证码及服务教程

阿里云国际版服务器开通之后&#xff0c;为了更多地运营网站&#xff0c;同时做到快速的信息通知及推广&#xff0c;阿里云短信服务就是很有必要的一种选择方式&#xff0c;调用API或用群发助手&#xff0c;即可发送验证码、通知类和营销类短信&#xff1b;国内验证短信秒级触达…