精华内容
下载资源
问答
  • <script src="https://unpkg.com/axios/dist/axios.min.js"></script>
  • Android中服务分为前台服务和后台服务。 前台服务需要通知用户一个Notification,表示用户正式使用该服务; 后台服务运行在后台,不需要通知用户启动了该服务。 然后后台服务在空闲状态被终止,打印如下: system_...

    Android中服务分为前台服务和后台服务。

    前台服务需要通知用户一个Notification,表示用户正式使用该服务;
    后台服务运行在后台,不需要通知用户启动了该服务。

    然后后台服务在空闲状态被终止,打印如下:

    system_process W/ActivityManager: Stopping service due to app idle: u0a66 -1m44s91ms com.ad.demo /.demo.DemoService
    com.ad.demo I/DemoService: onDestroy
    

    说明:当应用程序不再前台显示的时候,或者应用程序被关闭的时候,或者系统资源不足的时候,Android系统将后台服务kill掉了。

    尝试1:网上找到的一种方法,在服务onStartCommand中返回START_FLAG_RETRY,但是无效。服务并没有重启。

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        Log.i(TAG,"onStartCommand");
        new InitSocketThread().start();
        flags = Service.START_FLAG_RETRY;
        //flags = START_STICKY;
        return super.onStartCommand(intent, flags, startId);
        //return START_STICKY;
    }
    

    尝试2:在onDestroy重启服务:
    1.在AndroidManifest.xml中,增加重启服务的广播;
    2.在onDestroy()中发送重启的广播,收到广播后,启动服务。

    发现报错:
    Not allowed to start service Intent { cmp=com.ad.demo/.demo.Demo Service }: app is in background uid UidRecord

    尝试3:使用JobIntentService替换Service

    public class DemoService extends JobIntentService{
    	public static void enqueueWork(Context context, Intent work) {
            enqueueWork(context, CarMultiScreenService.class, JOB_ID, work);
        }
        
        @Override
        public void onCreate() {
             super.onCreate();
    	}
    	
        @Override
        public void onDestroy() {
            super.onDestroy();
        }
        
    	@Override
        protected void onHandleWork(@NonNull Intent intent) {
            Log.i(TAG,"onHandleWork");
        }
    }
    

    //启动服务

    DemoService .enqueueWork(context,new Intent());
    

    发现报错:
    JobServiceContext: Time-out while trying to bind 1725d35 #u0a66/1 com.ad.carlauncher/.socket.CarMultiScreenService, dropping.

    解决方法:去掉原来的:
    // @Nullable
    // @Override
    // public IBinder onBind(Intent intent) {
    // return null;
    // }

    但是,JobIntentService只适合执行特定的任务,在onHandleWork执行完任务后,就执行onDestroy退出了。

    可实现的解决办法——使用WorkManager实现永久的后台服务

    展开全文
  • 搭建后台服务器

    千次阅读 2019-05-10 17:01:29
    一个非常完整的搭建后台服务器的步骤,以及通过json字符串进行数据传输、连接数据库、还有servlet发送接收请求等。 https://blog.csdn.net/Mr_Megamind/article/details/71404618 ...

    一个非常完整的搭建后台服务器的步骤,以及通过json字符串进行数据传输、连接数据库、还有servlet发送接收请求等。
    https://blog.csdn.net/Mr_Megamind/article/details/71404618

    展开全文
  • Android——后台服务

    千次阅读 2019-05-19 11:42:16
    实验名称:Android 后台服务 实验目的:通过Service设计后台服务程序,通过Broadcast实现信息广播机制 实验内容: 设计一个简单的后台音乐服务程序; 设计一个简单的信息广播程序示例; 利用Broadcast实现后台服务...

    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
    展开全文
  • windows后台服务

    千次阅读 2016-09-14 14:42:04
    后台服务程序开发模式 一直感觉VC++太复杂了,但昨天看了汪蒲阳编著的因特网应用编程,其中写到后台服务程序的编写,论述的非常详细,而且逻辑清晰,看了之后感觉明白不少,故拿来与需要之人共享,并更正了原程序...

    后台服务程序开发模式

    一直感觉VC++太复杂了,但昨天看了汪蒲阳编著的因特网应用编程,其中写到后台服务程序的编写,论述的非常详细,而且逻辑清晰,看了之后感觉明白不少,故拿来与需要之人共享,并更正了原程序的一些错误,补充了一些材料。另外还有一种用C++编写后台服务程序的思路(不算.NET上服务程序开发模型),以后整理好了再发上来。

     

    在2000/XP等基于NT 的操作系统中,有一个服务管理器,它管理的后台进程被称为 service。服务是一种应用程序类型,它在后台运行,与 UNIX 后台应用程序类似。服务应用程序通常可以在本地和通过网络为用户提供一些功能,例如客户端/服务器应用程序、Web 服务器、数据库服务器以及其他基于服务器的应用程序。

     

    后台服务 程序是在后台悄悄运行的。我们通过将自己的程序登记为服务,可以使自己的程序不出现在任务管理器中,并且随系统启动而最先运行,随系统关闭而最后停止。

     

    服务控制管理器是一个RPC 服务器,它显露了一组应用编程接口,程序员可以方便的编写程序来配置服务和控制远程服务器中服务程序。

     

    服务程序通常编写成控制台类型的应用程序,总的来说,一个遵守服务控制管理程序接口要求的程序

    包含下面三个函数:

    1。服务程序主函数(main):调用系统函数 StartServiceCtrlDispatcher 连接程序主线程到服务控制管理程序。

    2。服务入口点函数(ServiceMain):执行服务初始化任务,同时执行多个服务的服务进程有多个服务入口函数。

    3。控制服务处理程序函数(Handler):在服务程序收到控制请求时由控制分发线程引用。(此处是Service_Ctrl)。

     另外在系统运行此服务之前需要安装登记服务程序:installService函数。删除服务程序则需要先删除服务安装登记:removeService 函数。

     

    服务类型:

    类型

    说明

    SERVICE_FILE_SYSTEM_DRIVER=2

    文件系统驱动服务。

    SERVICE_KERNEL_DRIVER=1

    驱动服务。

    SERVICE_WIN32_OWN_PROCESS=16

    独占一个进程的服务。

    SERVICE_WIN32_SHARE_PROCESS=32

    与其他服务共享一个进程的服务。

    新建WIN32控制台程序, 其源文件名为service.cpp 。我用的开发工具是VC++.NET。

    1.服务程序主函数

    服务控制管理程序启动服务程序后,等待服务程序主函数调用系统函StartServiceCtrlDispatcher。一个SERVICE_WIN32_OWN_PROCESS类型的服务应该立即调用 StartServiceCtrlDispatcher 函数,可以在服务启动后让服务入口点函数完成初始化工作。对于 SERVICE_WIN32_OWN_PROCESS 类型的服务和程序中所有服务共同的初始化工作可以在主函数中完成,但不要超过30秒。否则必须建立另外的线程完成这些共同的初始化工作,从而保证服务程序主函数能及时地调用 StartServiceCtrlDispatcher 函数。

     

    主函数处理了三中命令行参数:- install,- remove,-debug,分别用于安装,删除和调试服务程序。如果不带参数运行,则认为是服务控制管理出现启动该服务程序。参数不正确则给出提示信息。

     

    StartServiceCtrlDispatcher 函数负责把程序主线程连接到服务控制管理程序。具体描述如下:

    BOOL StartServiceCtrlDispatcher(constLPSERVICE_TABLE_ENTRY lpServiceTable);

    lpServiceStartTable 指向 SERVICE_TABLE_ENTRY 结构类型的数组,他包含了调用进程所提供的每个服务的入口函数和字符串名。表中的最后一个元素必须为 NULL,指明入口表结束。SERVICE_TABLE_ENTRY 结构具体描述如下:

    typedef struct _SERVICE_TABLE_ENTRY

    {

    LPTSTRlpServiceName; 

    LPSERVICE_MAIN_FUNCTIONlpServiceProc;

    } SERVICE_TABLE_ENTRY, *LPSERVICE_TABLE_ENTRY;

     

    lpServiceName 是一个以 NULL 结尾的字符串,标识服务名。如果是 SERVICE_WIN32_OWN_PROCESS 类型的服务,这个字符串会被忽略。

    lpServiceProc 指向服务入口点函数。

    //服务程序主函数。

    #include "stdafx.h"

    #include "Windows.h"

    #define SZAPPNAME                 "serverSample"        //服务程序名

    #define SZSERVICENAME         "serviceSample"      //标识服务的内部名

     

    //内部变量

    bool                                       bDebugServer=false;

    SERVICE_STATUS                        ssStatus;

    SERVICE_STATUS_HANDLE      sshStatusHandle;

    DWORD                                        dwErr=0;

    TCHAR                                           szErr[256];

     

    //下面的函数由程序实现

    void  WINAPI  Service_Main(DWORD dwArgc, LPTSTR *lpszArgv);

    void  WINAPI  Service_Ctrl(DWORD dwCtrlCode);

    void installService();

    void removeService();

    void debugService(int argc,char** argv);

    bool ReportStatusToSCMgr(DWORD dwCurrentState,DWORD dwWin32ExitCode,DWORD dwWaitHint);

    void AddToMessageLog(LPTSTR lpszMsg);

     

    int _tmain(int argc, _TCHAR* argv[])

    {

             SERVICE_TABLE_ENTRY dispatchTable[]=

             {

                       {TEXT(SZSERVICENAME),(LPSERVICE_MAIN_FUNCTION)Service_Main},

                       { NULL,NULL}

             };

            

             if((argc>1)&&((*argv[1]=='-')||(argv[1]=="/")))

             {

                       if(_stricmp("install",argv[1]+1)==0)

                       {

                       installService();

                       }

                       else if(_stricmp("remove",argv[1]+1)==0)

                       {

                       removeService();

                       }

                       else if(_stricmp("debug",argv[1]+1)==0)

                       {

                                bDebugServer=true;

                                debugService(argc,argv);

                       }

                       else

                       {        //如果未能和上面的如何参数匹配,则可能是服务控制管理程序来启动该程序。立即调用

                  //StartServiceCtrlDispatcher 函数。

                                printf("%s - install to install the service \n",SZAPPNAME);

                                printf("%s - remove to remove the service \n",SZAPPNAME);

                                printf("%s - debug to debug the service \n",SZAPPNAME);

                                printf("\n StartServiceCtrlDispatcher being called.\n");

                                printf("This may take several seconds.Please wait.\n");

                                if(!StartServiceCtrlDispatcher(dispatchTable))

                                         AddToMessageLog(TEXT("StartServiceCtrlDispatcher failed."));

                                else

                                         AddToMessageLog(TEXT("StartServiceCtrlDispatcher OK."));

                       }

                       exit(0);

             }

             return 0;

    }

     

    2.服务入口点函数

    服务入口点函数 service_main 首先调用系统函数 RegisterServiceCtrlHandler 注册服务控制处理函数service_ctrl,然后调用 ReportStatusToSCMgr 函数,它通过系统函数 SetServiceStatus 更新服务的状态,然后调用特定的服务初始化入口函数ServiceStart 完成具体的初始化工作。

     

    //服务入口点函数

    void ServiceStart(DWORD dwArgc,LPTSTR* lpszArgv);//具体服务的初始化入口函数
     void  WINAPI  Service_Main(DWORD dwArgc, LPTSTR *lpszArgv)

    {

        //注册服务控制处理函数

        sshStatusHandle=RegisterServiceCtrlHandler(TEXT(SZSERVICENAME),Service_Ctrl);

        //如果注册失败

        if(!sshStatusHandle)

        {

             goto cleanup;

             return;

        }

        //初始化 SERVICE_STATUS 结构中的成员

        ssStatus.dwServiceType=SERVICE_WIN32_OWN_PROCESS;

        ssStatus.dwServiceSpecificExitCode=0;

        //更新服务状态

        if(!ReportStatusToSCMgr(

             SERVICE_START_PENDING,//服务状态,The service is starting.

             NO_ERROR,            //退出码        

             3000))                   //等待时间

             goto cleanup;        //更新服务状态失败则转向 cleanup

        ServiceStart(dwArgc,lpszArgv);

        return;

    cleanup:

        //把服务状态更新为 SERVICE_STOPPED,并退出。

        if(sshStatusHandle)

             (void)ReportStatusToSCMgr(SERVICE_STOPPED,dwErr,0);

    }

     

    3.控制处理程序函数

    函数Service_Ctrl 是服务的控制处理程序函数,由主函数线程的控制分发程序引用。在处理控制请求码时,应该在确定的时间间隔内更新服务状态检查点,避免发生服务不能响应的错误。

     

    //控制处理程序函数

    void WINAPI Service_Ctrl(DWORD dwCtrlCode)

    {

        //处理控制请求码

        switch(dwCtrlCode)

        {

             //先更新服务状态为 SERVICDE_STOP_PENDING,再停止服务。

        case SERVICE_CONTROL_STOP:

             ReportStatusToSCMgr(SERVICE_STOP_PENDING,NO_ERROR,500);

             ServiceStop();     //由具体的服务程序实现

             return;

             //暂停服务

        case SERVICE_CONTROL_PAUSE:

             ReportStatusToSCMgr(SERVICE_STOP_PENDING,NO_ERROR,500);

             ServicePause();    //由具体的服务程序实现

             ssStatus.dwCurrentState=SERVICE_PAUSED;

             return;

             //继续服务

        case SERVICE_CONTROL_CONTINUE:

             ReportStatusToSCMgr(SERVICE_STOP_PENDING,NO_ERROR,500);

             ServiceContinue(); //由具体的服务程序实现

             ssStatus.dwCurrentState=SERVICE_RUNNING;

             return;

             //更新服务状态

        case SERVICE_CONTROL_INTERROGATE:

             break;

             //无效控制码

        default:

             break;

        }

        ReportStatusToSCMgr(ssStatus.dwCurrentState,NO_ERROR,0);

    }

     

    除了系统定义的五种控制码外(还有一种是:SERVICE_CONTROL_SHUTDOWN),用户还可自定义控制码,其取值范围是128-255。用户可以通过控制面板中的服务项向特定服务程序的控制处理函数发送控制码,程序员可以调用系统函数 ControlService 直接向服务程序的控制处理函数发送控制码。其函数原型如下:

     

    BOOL ControlService(SC_HANDLE hService,DWORD

    dwControl,

    LPSERVICE_STATUS

    lpServiceStatus);

    hService :函数 OpenService or CreateService 返回的服务程序句柄。

    dwControl :控制码,不能是SERVICE_CONTROL_SHUTDOWN

    lpServiceStatus:返回最后收到的服务状态信息。

    4.安装服务程序

    每个已安装服务程序在HKEY_LOCAL_MACHINE\SYSTE\CurrentControlSet\Services 下都有一个服务名的关键字,程序员可以调用系统函数 CreateService 安装服务程序,并指定服务类型,服务名等。这个函数创建一个服务对象,并将其增加到相关的服务控制管理器数据库中。

    下面是函数原型:

     

    SC_HANDLE CreateService(

                 SC_HANDLE hSCManager, //服务控制管理程序维护的登记数据库的句柄,由系统函数OpenSCManager 返回

                 LPCTSTR lpServiceName, //以NULL结尾的服务名,用于创建登记数据库中的关键字

                 LPCTSTR lpDisplayName, //以NULL结尾的服务名,用于用户界面标识服务

                 DWORD dwDesiredAccess, //指定服务返回类型

                 DWORD dwServiceType, //指定服务类型

                 DWORD dwStartType, //指定何时启动服务

                 DWORD dwErrorControl, //指定服务启动失败的严重程度

                 LPCTSTR lpBinaryPathName, //指定服务程序二进制文件的路径

                 LPCTSTR lpLoadOrderGroup, //指定顺序装入的服务组名

                 LPDWORD lpdwTagId, //忽略,NULL

                 LPCTSTR lpDependencies, //指定启动该服务前必须先启动的服务或服务组

                 LPCTSTR lpServiceStartName, //以NULL结尾的字符串,指定服务帐号。如是NULL,则表示使用LocalSystem帐号

                 LPCTSTR lpPassword //以NULL结尾的字符串,指定对应的口令。为NULL表示无口令。但使用LocalSystem时填NULL);

     

    对于一个已安装的服务程序,可以调用系统函数 OpenService 来获取服务程序的句柄

    下面是其函数原型:

    SC_HANDLE OpenService(

                 SC_HANDLE hSCManager,

                 LPCTSTR lpServiceName,

                 DWORD dwDesiredAccess);

    hSCManager :服务控制管理程序微服的登记数据库的句柄。由函数OpenSCManager function 返回 这个句柄。

    lpServiceName :将要打开的以NULL 结尾的服务程序的名字,和 CreateService  中的 lpServiceName 相对应。

    dwDesiredAccess :指定服务的访问类型。服务响应请求时,首先检查访问类型。

    用CreateService 或OpenService打开的服务程序句柄使用完毕后必须用CloseServiceHandle 关闭。

    OpenSCManager打开的服务管理数据库句柄也必须用它来关闭。

    //安装服务程序

    void installService()

    {

        SC_HANDLE schService;

        SC_HANDLE schSCManager;

        TCHAR szPath[512];

        //得到程序磁盘文件的路径

        if(GetModuleFileName(NULL,szPath,512)==0)

        {

             _tprintf(TEXT("Unable to install %s - %s \n"),

                 TEXT(SZAPPNAME),

             GetLastError());//@1获取调用函数返回的最后错误码

             return;

        }

        //打开服务管理数据库

        schSCManager=OpenSCManager(

                               NULL,    //本地计算机

                               NULL,    //默认的数据库

                               SC_MANAGER_ALL_ACCESS  //要求所有的访问权

                               );

    if(schSCManager)

        {

             //登记服务程序

             schService=CreateService(

                 schSCManager,                    //服务管理数据库句柄

                 TEXT(SZSERVICENAME),             //服务名

                 TEXT(SZAPPNAME),       //用于显示服务的标识

                 SERVICE_ALL_ACCESS,              //响应所有的访问请求

                 SERVICE_WIN32_OWN_PROCESS,       //服务类型

                 SERVICE_DEMAND_START,            //启动类型

                 SERVICE_ERROR_NORMAL,            //错误控制类型

                 szPath,                              //服务程序磁盘文件的路径

                 NULL,                                //服务不属于任何组

                 NULL,                                //没有tag标识符

                 NULL,              //启动服务所依赖的服务或服务组,这里仅仅是一个空字符串

                 NULL,                                //LocalSystem 帐号

                 NULL);

             if(schService)

             {

                 _tprintf(TEXT("%s installed. \n"),TEXT(SZAPPNAME));

                 CloseServiceHandle(schService);

             }

             else

             {

                 _tprintf(TEXT("CreateService failed - %s \n"),GetLastError());

             }

             CloseServiceHandle(schSCManager);

        }

        else

             _tprintf(TEXT("OpenSCManager failed - %s \n"),GetLastError());

    }

     

    5.停止和删除已安装的服务程序

     

    //停止和删除已安装的服务程序

    void removeService()

    {

        SC_HANDLE schService;

        SC_HANDLE schSCManager;

        //打开服务管理数据库

        schSCManager=OpenSCManager(

                           NULL,    //本地计算机

                           NULL,    //默认的数据库

                           SC_MANAGER_ALL_ACCESS  //要求所有的访问权

                           );

        if(schSCManager)

        {

             //获取服务程序句柄

             schService=OpenService(

                 schSCManager,          //服务管理数据库句柄

                 TEXT(SZSERVICENAME),   //服务名

                 SERVICE_ALL_ACCESS     //响应所有的访问请求

                 );

             if(schService)

             {

                 //试图停止服务

                 if(ControlService(

                      schService,                 //服务程序句柄

                      SERVICE_CONTROL_STOP,  //停止服务请求码

                      &ssStatus              //接收最后的服务状态信息

                      ))

                 {

                      _tprintf(TEXT("Stopping %s."),TEXT(SZAPPNAME));

                      Sleep(1000);

     

                      //等待服务停止

                      //

                      while(QueryServiceStatus(schService,&ssStatus))

                      {

                           if(SERVICE_STOP_PENDING==ssStatus.dwCurrentState)

                           {

                               _tprintf(TEXT("."));

                               Sleep(1000);

                           }

                           else

                               break;

                      }

                      if(SERVICE_STOPPED==ssStatus.dwCurrentState)

                           _tprintf(TEXT("\n %s stopped. \n"),TEXT(SZAPPNAME));

                      else

                           _tprintf(TEXT("\n %s failed to stopp. \n"),TEXT(SZAPPNAME));

                 }

                 //删除已安装的服务程序安装

                 if(DeleteService(schService))

                      _tprintf(TEXT("%s removed. \n"),TEXT(SZAPPNAME));

                  else

    _tprintf(TEXT("DeleteService failed - %s. \n"), GetLastError());

                 CloseServiceHandle(schService);

             }

             else

                 _tprintf(TEXT("OpenService failed - %s \n"),GetLastError());

             CloseServiceHandle(schSCManager);

        }

        else

             _tprintf(TEXT("OpenSCManager failed - %s \n"),GetLastError());

    }

     

     

    在编译程序的时候,我们会发觉ServiceStop();ServicePause();ServiceContinue();等三个函数没有具体实现,这对于理解此文的人来说应该不难编写,在此我可以给点文档内的参考:声明     SetTheServiceStatus()函数,

    //     
       //  SetTheServiceStatus -   This just wraps upSetServiceStatus.
       // 
       void SetTheServiceStatus(DWORD dwCurrentState, DWORDdwWin32ExitCode,
                               DWORD dwCheckPoint,   DWORD dwWaitHint)
       {
           SERVICE_STATUSss;  // Current status of the service.

           // 
           //Disable control requests until the service is started.
           // 
           if(dwCurrentState == SERVICE_START_PENDING)
               ss.dwControlsAccepted = 0;
           else
               ss.dwControlsAccepted =
                          SERVICE_ACCEPT_STOP|SERVICE_ACCEPT_SHUTDOWN;
                          // Other flags include SERVICE_ACCEPT_PAUSE_CONTINUE
                          // and SERVICE_ACCEPT_SHUTDOWN.

            //Initialize ss structure.
            ss.dwServiceType            = SERVICE_WIN32_OWN_PROCESS;
            ss.dwServiceSpecificExitCode= 0;
            ss.dwCurrentState           = dwCurrentState;
            ss.dwWin32ExitCode          = dwWin32ExitCode;
            ss.dwCheckPoint             = dwCheckPoint;
            ss.dwWaitHint               = dwWaitHint;

            // Sendstatus of the service to the Service Controller.
            if(!SetServiceStatus(ssh, &ss))
               ErrorStopService(TEXT("SetServiceStatus"));
        }

        然后用如下的方式来调用函数来实现源程序中缺少的功能 : 

       SetTheServiceStatus(SERVICE_STOPPED, GetLastError(), 0, 0);// Stop the service.

    展开全文
  • 之前的《后台服务优化原则》中提到后台service的一些拆分原则,也就是单个服务内对外接口拆分的一些原则。其实,在服务部署时,也会有一些不同的部署策略,来实现另一种意义上的拆分。最重要的作用在于「防火隔离」...
  • Android后台服务Service

    万次阅读 2018-03-27 00:16:57
    后台服务桌面应用程序:可见服务:不可见 长期在后台运行 帮助应用执行耗时的操作 安卓的服务:安卓四大组件之一 不可见 后台长期运行 界面与服务有时候要执行数据交互如何创建服务:1. 创建一个类 继承Service....
  • Android Service 后台服务之本地服务

    千次阅读 2016-05-07 15:53:58
    Service是Android系统的服务组件,适用于开发没有用户界面且长时间在后台运行的功能 ...因此,Android系统需要一种后台服务机制,允许在没有用户界面的情况下,使程序能够长时间在后台运行,实现应用程序的后台服务
  • JupyterNotebook 后台服务启动

    千次阅读 2019-07-03 16:40:54
    JupyterNotebook 后台服务启动
  • 后台服务被恶意脚本访问

    万次阅读 2019-06-24 16:42:23
    这几天写了后台API服务给前端调用,看命令行打印的log日志...很明显是有人用恶意脚本试图访问到我的后台服务的管理页面,虽然我后台已经用filer做了身份认证,但是看到log那里打印一堆恶意请求的信息还是很不爽。 ...
  • C++创建Windows后台服务程序

    万次阅读 多人点赞 2018-08-24 11:37:58
    后台服务 程序是在后台悄悄运行的。我们通过将自己的程序登记为服务,可以使自己的程序不出现在任务管理器中,并且随系统启动而最先运行,随系统关闭而最后停止。 服务程序通常编写成控制台类型的应用程序,总的来...
  • 融云后台服务创建token

    千次阅读 2016-06-17 12:12:38
    融云后台服务器获取token
  • Web前端连接后台服务

    千次阅读 2019-10-06 17:42:55
    Web前端连接后台服务 用的是servlet接口连接,servlet是中间控制层。 servlet: Servlet任务 读取客户端(浏览器)发送的显式的数据。这包括网页上的 HTML 表单,或者也可以是来自 applet 或自定义的 HTTP 客户端...
  • 使用 brew services 管理后台服务(MacOS)

    千次阅读 2018-12-05 13:32:39
    在编写项目的时候,时常需要开启一些诸如 nginx、mysql、redis 等后台服务,然而每次开机都要重新手动去开启这些服务,而且有些还要保留一些终端窗口去维持服务。 这时我们可以用到 brew services 来管理这些后台...
  • 后台服务器架构设计要点

    千次阅读 2018-12-22 10:49:08
    想做后台服务器架构设计,要把握以下几个因素 1. 要处理多大的数据量 2. 有多少种的数据 3. 延迟有多高 4. 要不要处理通知 通常情况下,数据种类越多,数据量越大,系统架构越复杂; 比如 处理 百万级的请求 一台...
  • APP后台服务器阿里云Win系统服务器搭建的方法
  • windows后台服务程序编写

    千次阅读 2014-12-26 11:30:59
    windows 后台服务编写心得
  • 在Flutter启动Android的后台服务

    千次阅读 2020-04-08 20:07:36
    Android和iOS在后台服务的处理方式不同,因此Flutter并没有在这方面做统一处理,而是需要调用原生平台的代码来实现该功能,下面就简单介绍如何在Flutter中与Android的后台服务进行交互。 实现 在Flutter中有一个...
  • 客户端网页是纯的javascript写的,后台服务是c#语言写的。 于是,用websocket通信的时候问题就来了,后台服务在接收客户端网页的多帧数据的时候,出现了粘包问题。 具体来说就是: 1、后台服务使用异步的 client....
  • 安卓前台服务和后台服务的区别

    千次阅读 2014-04-17 09:37:57
    类别 区别 应用 ...当服务被终止的时候,通知一栏的 Notification 也会消失,这样对于用户有一定的通知作用。...后台服务 ...默认的服务即为后台服务,即不会在通知一栏显示 ONGOING
  • 后台服务中有个长连接,在程序在后台运行时,长连接connect lost(服务没有挂掉), 导致频繁重连。但是连接usb的时候,长连接就不会被断开。请问这个问题 是什么个情况,在线等,急!
  • Android后台服务在屏幕休眠保持运行

    千次阅读 2020-05-05 20:27:31
    比如音乐播放就是Service的典型应用,在开发移动APP时,很多业务场景需要用到Service保持在后台运行,在实现过程中让Service在屏幕休眠下继续保持运行,往往没有按照预期运行,下面例程中实现后台服务定时上报GPS...
  • Windows 下配置 Logstash 为后台服务

    千次阅读 2019-07-09 13:15:00
    到目前为止,logstash 没有给出官方的,在 windows 系统中作为后台服务运行的方式。本文将介绍如何使用第三方工具 nssm 让 logstash 作为后台服务运行在 windows 中。说明:演示的环境为 windows server 2016,...
  • Nginx 后台服务怎么访问nginx中的前端页面; 就是外网部署的是nginx前端页面,内网部署的是后端服务,那么后端服务怎么进入到前端页面呢
  • 开源Api后台服务/管理系统 HoServer

    千次阅读 2020-04-17 09:41:23
    开源Api后台服务/管理系统 HoServer简介功能特性HoServer Pro 特性 :gem:支持开源版使用须知快速开始系统功能截图 简介 HoServer 是基于Node.js开发开箱即用的后台服务和管理平台脚手架,可视化对象定义,一行代码...
  • xshell退出保持后台服务运行的方法

    万次阅读 2018-08-07 17:04:32
    Linux后台启动了一个服务,但是退出命令终端后或者退出xshell后,服务就关闭了,要想保持后台服务一直启动,可以使用下面的命令来启动服务 #nohup python3.6 /opt/testmanage/WebDevelop/Flask/SsjApiPlat2/run.py...
  • 让IPFS处于后台服务

    千次阅读 2018-05-12 15:15:53
    让IPFS处于后台服务中以下是基于 Ubuntu Service 创建一个服务。cd /lib/systemd/system/ nano ipfs.service 粘贴以下代码让IPFS遇到故障后能自动重启服务。[Unit] Description=IPFS [Service] ExecStart=/usr/local...
  • 使用Swift4.0搭建后台服务器

    千次阅读 2018-03-15 15:11:41
    时隔半年,又想起了这回事儿,恰好现在有空,就来弄着玩玩,以后写个简单的app还可以自己搭建简单的后台服务。 搭建过程: 1. 先在本机上安装Homebrew + Vapor 2. 在服务器上安装Swift4.0 + Vapor 本机 ...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 88,681
精华内容 35,472
关键字:

后台服务