Service
前言
本文主要通过学习官方文档的服务,通过代码和一些简单的实例来说明服务是是什么,为什么会用到服务。
什么是服务
Service 是一种可在后台执行长时间运行操作而不提供界面的应用组件。服务可由其他应用组件启动,而且即使用户切换到其他应用,服务仍将在后台继续运行。此外,组件可通过绑定到服务与之进行交互,甚至是执行进程间通信 (IPC)。例如,服务可在后台处理网络事务、播放音乐,执行文件 I/O 或与内容提供程序进行交互。
简而言之:长期处于后台运行的程序。(是一个组件,用于长期运行的任务,没有用户交互。)
为什么使用服务
服务可在后台处理网络事务、播放音乐,执行文件 I/O 或与内容提供程序进行交互。即:我们有时候需要没有界面,但希望程序仍然运行。
另外,当内存不足时,我们会销毁 空进程>后台进程>服务进程>可见进程>前台进程
- 前台进程:直接与用户交互的界面,操作Activity界面。
- 可见进程:可以看见,不可操作的界面。弹出对话框时,对话框为前台进程,而当前Activity为可见进程。
- 服务进程:正在工作的后台进程。
- 后台进程:不可见的,不工作的进程。
- 空进程:不工作,没有任何东西在进程上,仅作缓存作用。(按返回键)
音乐,后台下载
服务的生命周期
继承与ContextWrapper–继承Context
onCreate()
onStart()//已过时
onStartCommand()
onStop()
onDestory()
onCreate()和onDestory()创建于销毁
onStartCommand()和onStop()
由于Service不可交互,所以没有onResume和onPause方法
开启服务的基本使用
第一步:继承Service 实现onBind方法 返回IBinder对象
第二步:在manifest 清单文件声明
第三步:startService(intent); intent.setClass(this,Service.class);
停止服务:stopService(intent);
开启服务执行了生命周期:
第一次开启:onCreate和onStartCommand和onStart
第二次及以后:onStartCommand和onStart
停止服务执行了生命周期:onStop和onDestory方法
当开启了服务后,但Activity销毁了,但是服务还是没关。(在App里,Running中还是能看到服务正在开启。)只有停止了服务,才能销毁掉服务。
调用内部方法
在Service中,定义一个方法。public static void sayhello(){}
Service.sayhello();
如果直接在Activity中调用该方法会直接崩掉,会报出空指针异常(Context为空)。Context必须由系统去创建,所以会空指针。
所以,通过绑定服务去调用方法。
绑定启动服务
由于绑定服务,需要IBinder对象。即:当你实现Service对象时的onBinder方法。
所以,绑定服务时,必须返回IBinder对象。
必须定义一个class类实现于IBinder对象。返回IBinder对象,进行数据传递。
由于:IBinder有很多无用的方法代码,所以我们找一个实现类Binder
定义一个class继承Binder。
在Binder类中,暴露一个方法sayhello();
基本使用
- 定义一个内部类class继承Binder。
在Binder类中,暴露一个方法sayhello(); - 在Service中onBinder方法中返回一个IBinder对象(即:继承Binder类)
- 绑定服务:bindService(intent,ServiceConnection,flags);(要绑定的Service,Service连接器用于数据之间的传递,标识符BIND_AUTO_CREATE);标识符:如果没有创建,绑定服务,会自动创建。如果有创建,就不会创建,配合开启服务和绑定服务联合使用
- new ServiceConnection的类,回调接口类,实现两个方法。一个是正在连接,一个是取消连接服务
- 在ServiceConnection正在连接服务方法,返回IBinder对象强转为内部Binder类对象,置为成员变量。
- 在取消连接服务时,将IBinder对象置为空
- 内部Binder类对象调用sayhello()方法
- 解绑服务:unbindService(ServiceConnection);
接口实现方法私有化(接口暴露方法)
当我们把内部方法类方法私有化(为了更加安全),这时我们不能够直接调用该内部方法。这时我们可以通过一个接口来暴露私有化内部类的方法。
- 定义一个接口,接口方法。
- 内部Binder实现接口方法。
- 在Service Connection中返回IBinder对象强转成定义接口的对象。
- 用接口对象 调用接口方法。
我们把内部Binder类私有化后,在onBinder方法中返回了内部Binder类对象。在ServiceConnection即连接成功时返回了该对象强转成了接口对象。(由于所有对现象都能在编译时转成接口,但在运行时会根据返回的对象是否实现类接口。如果,没实现接口的话会报错。)所以,返回的内部Binder类可以接口强转,在运行时,它会调用私有化内部类中该接口类的接口方法。
此方法,十分方便我们分工合作。(面向接口编程)
例子(面向接口编程)
面向接口编程:一部分负责实现,一部分负责调用接口方法
银行服务(对应功能):
存钱,取钱,贷款(普通用户)
查询用户信用,冻结用户账号(银行工作人员)
修改账号金额(银行行长)
- 首先声明银行服务接口
- 实现接口(分工开发) 实现接口方法,由于银行服务要通讯所以应该继承Binder
- 调用接口方法(分工开发)
- 通过隐式意图注册服务 exported暴露服务 设置action和category。
- 根据不同意图action来返回不同IBinder对象(即不同银行服务对象)。
- UI设计,当有相同的UI的时候可以include
- 绑定服务 bindservice(intent,ServiceConnection,BIND_AUTO_CREATE);intent为上面的隐式意图。
- 通过ServiceConnection接收IBinder对象置为成员变量
- 通过IBinder接口调用方法
- 解绑服务 释放资源
android 5.0以后服务启用隐式意图要设置package:
开启服务和绑定服务的区别
开启服务:长期位于后台运行。而绑定服务:不能位于后台运行。
开启服务:不能够进行通讯。而绑定服务:可以进行通讯
跨进程通讯AIDL
AIDL:android interface definition language安卓接口定义语言
framwork层用比较多。支付宝,支付成功和失败。通过AIDL来通讯的。(通过SDK封装AIDL)
系统开发,会经常用AIDL。管理对象,保存对象。
跨应用,跨进程用 AIDL。
基本使用
- 创建AIDL文件 接口(定义接口方法)
- 接口方法若要输入值必须在类型前面加 in。
- 点击make project。生成 .java文件 (继承Binder的一个类)
- 继承AIDL文件名.Stub。
- 之前的IBinder返回对象可以用该类代替(由于继承Binder类)。
- 通过AIDL文件名.Stub.asInterface(IBinder)来转化对象。
AIDL的一些使用例子
模仿支付宝(返回支付成功)
提高程序稳定性:app测试和稳定代码能力
根据支付结果,修改UI控件
- PayService继承于Service
- 注册,给第三方exported ,intent-filter action和category
- 根据action返回IBinder对象 return 第三方支付Binder对象
- 第三方支付使用AIDL接口 定义发起支付:orderinfo,paymoney,CallBack(AIDL回调接口) CallBack方法(支付成功,支付失败(错误码,错误信息))
- 继承第三方支付AIDL的类 并返回Binder对象
- 创建支付界面,显示支付信息(支付信息,支付金额,支付密码,确定支付)
- 接口方法中,直接打开支付页面并传递支付信息过去。 设置不同栈中,intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
- 在接口方法,创建回调接口方法给外部调用
- 在支付页面,绑定服务,获取别的界面传递的支付信息。将信息加载到控件上。设置点击事件。
- 定义继承于Binder的支付方法和用户取消支付(支付失败)。 (用于支付信息上传服务器,对比密码等一系列逻辑的服务类)
- 创建第三方应用:显示充值操作,绑定服务。
AIDL的通信(模仿支付过程)大致流程:
先定义AIDL接口(用来通信,数据传递)。在AIDL的定义一个第三方请求支付的接口(使第三方应用通过自己支付页面,通过绑定服务打开暴露出去的服务,直接打开支付app的支付页面。并将信息传递过去。)在定义一个请求的回调接口。(通过回调接口回调信息)
定义支付Service,通过export暴露给第三方应用打开。设置action值,通过action来返回一个第三方应用的Binder对象请求信息。(由于信息跨进程传递这个Binder对象为AIDL),(通过这个Binder对象打开PayActivity支付页面并将信息传递过去)
定义支付页面PayActivity,将支付信息,支付金额(由传递信息获取),支付密码和确认支付。通过支付密码(网络请求)来确认是否成功。成功的话,通过本地服务去通知第三方应用回调(即Activity与Service的通信)。失败也一样。
在PayActivity的定义一个本地服务,并将本地服务绑定都Service上来显示支付回调。返回onBinder中返回一个PayAction的Binder对象(来确认是否支付成功)。并将这个回调结果通知给第三方应用(即AIDL来传递,将返回这个对象变成成员变量),在AIDL对象中定义成功和失败的方法(与本地服务通信并传递结果),并定义一个第三方支付结果Callback来接收是否支付成功。CallBack.成功方法和失败方法。(本地服务PayAction调用成功和失败方法,通过成员变量来传递结果)。
在PayActivity中解绑服务等一系列操作防止内存泄漏。
第三方应用先将AIDL文件复制一份到自己的应用(用来信息传递)
然后绑定服务,将支付app暴露出来的服务绑定过去(action和package)。
通过确认支付,将支付信息和支付金额传递过去,由于已经绑定了服务,所以直接通过AIDL的第三请求接口传递信息。这时就会打开支付app的支付页面,通过上述的逻辑(通过支付密码等一系列的网络请求)就会返回支付结果。由于支付结果已经通过app的本地服务通知AIDL的第三方支付请求结果。
这时我们可以得到结果。通过结果来通知UI更新。
混合启动服务
服务开启两种方式:
- startService()开启服务------>stopService()来停止服务。
优点:长期位于后台运行。缺点:不能够进行通讯。
最基本的生命周期:
onCreate()—>onStartCommand()---->onDestory();
服务已经启动了,就不会走onCreate()方法。(除非走了onDestory方法) - bindService()绑定服务,没有启动服务自动启动------->unBindService()解绑服务
优点:不能位于后台运行。缺点:可以进行通讯。
最基本的生命周期:
onCreate()–>onBind()–>onUnBind()—>onDestory()
不能多次解绑服务会崩溃。(一般在onDestory方法中解绑)
混合启动服务(两种启动方式混合)
先startService() 启动服务 ----> 然后bindService()绑定服务—>unBindService()解绑服务----stopService绑定服务。
混合的生命周期:
onCreate()---->onStartCommand()—>onBind()—>onUnBind()–>onDestory
- 开启服务,然后绑定服务,如果不取消绑定服务,那么无法停止服务
- 开启服务以后,多次绑定—解绑服务。服务不会停止,只能通过stopService来onDestory方法 停止服务
推荐混合开启服务方式:
1.开启服务—》为了确保服务可以长期于后台运行
2.绑定服务—》为了服务可以通讯
3.调用服务内部的方法,该干嘛就干嘛。比如说,我们控制音乐的播放/停止/快进/暂停
4.退出Activity,要记得解绑服务。—》释放资源
5.如果不使用服务,要让服务停止,那么调用stopService();
服务例子音乐播放器
实际通过一些开源框架完成 ExoPlayer
类似于mvp框架来编写该程序
首先,先写UI,播放进度条seekbar和播放暂停按钮和停止按钮,把注册控件和设置点击事件(如果是mvvm框架这部分在ViewModel层与控件绑定)
然后,写接口Presenter接口和更新UI的回调接口(一般这工作在一开始就完成了),一个用于方法的播放逻辑,一个用于更新UI(播放暂停和进度条)。
在Activity中,由于我们是通过Service的混合启动服务来达到解绑服务而音乐不会关闭,所以我在服务绑定连接中获取到Presenter的Binder对象。(面向接口编程)调用Presenter的接口方法即(播放,暂停,停止等方法,还有进度条变化的方法)。第二,我们需要通知UI更新状态的回调接口。即创建一个成员变量更新UI的回调接口,并将这个接口注册到逻辑层去(在Presenter接口创建时注册),防止内存泄漏应该取消注册置空。(按钮控件的UI改变)触摸时设置进度条改变进度可能导致音乐抖动,所以将手离开屏幕时才设值(进度条UI改变)
在Service中,返回Presenter的Binder对象,在onCreate方法中创建,在onDestory方法中置为空。
实现逻辑层代码,注册UI更新回调接口(将回调接口为成员变量用于UI更新),取消注册接口(置空),播放和暂停,停止,进度条改变。播放和暂停,停止:获取当前状态,根据当前状态来处理不同逻辑。播放:处理化音乐播放器,设置资源,准备,开始播放,并将状态置为开始播放。暂停:暂停播放,并将状态置为暂停播放。停止:暂停播放,释放资源,并将状态置为停止播放。(同时要要通知UI更新,更新接口的)
进度条改变:通过seek进度条的多少*音乐播放的总长度。
由于进度要跟着音乐播放的时间改变而改变,所以创建一个Timer来获取当前播放的进度,从而通知UI更新。Timer.schedule(TimeTask,0,500);第一个为任务即要如何通知UI(获取当前播放进度/总进度)并将这进度百分比传给UI更新。此时创建开启和暂停Timer的方法。在播放时开启Timer,在停止和暂停时关闭Timer。由于Timer不在主线程中更新UI,所以要会到主线程中更新UI。(ProgressBar和surfaceView可以不在主线程中更新UI)