四大组件之一Service


前言

本文主要通过学习官方文档的服务,通过代码和一些简单的实例来说明服务是是什么,为什么会用到服务。

什么是服务

Service 是一种可在后台执行长时间运行操作而不提供界面的应用组件。服务可由其他应用组件启动,而且即使用户切换到其他应用,服务仍将在后台继续运行。此外,组件可通过绑定到服务与之进行交互,甚至是执行进程间通信 (IPC)。例如,服务可在后台处理网络事务、播放音乐,执行文件 I/O 或与内容提供程序进行交互。

简而言之:长期处于后台运行的程序。(是一个组件,用于长期运行的任务,没有用户交互。)

为什么使用服务

服务可在后台处理网络事务、播放音乐,执行文件 I/O 或与内容提供程序进行交互。即:我们有时候需要没有界面,但希望程序仍然运行。
另外,当内存不足时,我们会销毁 空进程>后台进程>服务进程>可见进程>前台进程

  1. 前台进程:直接与用户交互的界面,操作Activity界面。
  2. 可见进程:可以看见,不可操作的界面。弹出对话框时,对话框为前台进程,而当前Activity为可见进程。
  3. 服务进程:正在工作的后台进程。
  4. 后台进程:不可见的,不工作的进程。
  5. 空进程:不工作,没有任何东西在进程上,仅作缓存作用。(按返回键)

音乐,后台下载

服务的生命周期

继承与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();

基本使用

  1. 定义一个内部类class继承Binder。
    在Binder类中,暴露一个方法sayhello();
  2. 在Service中onBinder方法中返回一个IBinder对象(即:继承Binder类)
  3. 绑定服务:bindService(intent,ServiceConnection,flags);(要绑定的Service,Service连接器用于数据之间的传递,标识符BIND_AUTO_CREATE);标识符:如果没有创建,绑定服务,会自动创建。如果有创建,就不会创建,配合开启服务和绑定服务联合使用
  4. new ServiceConnection的类,回调接口类,实现两个方法。一个是正在连接,一个是取消连接服务
  5. 在ServiceConnection正在连接服务方法,返回IBinder对象强转为内部Binder类对象,置为成员变量。
  6. 在取消连接服务时,将IBinder对象置为空
  7. 内部Binder类对象调用sayhello()方法
  8. 解绑服务:unbindService(ServiceConnection);

接口实现方法私有化(接口暴露方法)

当我们把内部方法类方法私有化(为了更加安全),这时我们不能够直接调用该内部方法。这时我们可以通过一个接口来暴露私有化内部类的方法。

  1. 定义一个接口,接口方法。
  2. 内部Binder实现接口方法。
  3. 在Service Connection中返回IBinder对象强转成定义接口的对象。
  4. 用接口对象 调用接口方法。

我们把内部Binder类私有化后,在onBinder方法中返回了内部Binder类对象。在ServiceConnection即连接成功时返回了该对象强转成了接口对象。(由于所有对现象都能在编译时转成接口,但在运行时会根据返回的对象是否实现类接口。如果,没实现接口的话会报错。)所以,返回的内部Binder类可以接口强转,在运行时,它会调用私有化内部类中该接口类的接口方法。

此方法,十分方便我们分工合作。(面向接口编程)

例子(面向接口编程)

面向接口编程:一部分负责实现,一部分负责调用接口方法

银行服务(对应功能):
存钱,取钱,贷款(普通用户)
查询用户信用,冻结用户账号(银行工作人员)
修改账号金额(银行行长)

  1. 首先声明银行服务接口
  2. 实现接口(分工开发) 实现接口方法,由于银行服务要通讯所以应该继承Binder
  3. 调用接口方法(分工开发)
  4. 通过隐式意图注册服务 exported暴露服务 设置action和category。
  5. 根据不同意图action来返回不同IBinder对象(即不同银行服务对象)。
  6. UI设计,当有相同的UI的时候可以include
  7. 绑定服务 bindservice(intent,ServiceConnection,BIND_AUTO_CREATE);intent为上面的隐式意图。
  8. 通过ServiceConnection接收IBinder对象置为成员变量
  9. 通过IBinder接口调用方法
  10. 解绑服务 释放资源

android 5.0以后服务启用隐式意图要设置package:

开启服务和绑定服务的区别

开启服务:长期位于后台运行。而绑定服务:不能位于后台运行。

开启服务:不能够进行通讯。而绑定服务:可以进行通讯

跨进程通讯AIDL

AIDL:android interface definition language安卓接口定义语言
framwork层用比较多。支付宝,支付成功和失败。通过AIDL来通讯的。(通过SDK封装AIDL)

系统开发,会经常用AIDL。管理对象,保存对象。
跨应用,跨进程用 AIDL。

基本使用

  1. 创建AIDL文件 接口(定义接口方法)
  2. 接口方法若要输入值必须在类型前面加 in。
  3. 点击make project。生成 .java文件 (继承Binder的一个类)
  4. 继承AIDL文件名.Stub。
  5. 之前的IBinder返回对象可以用该类代替(由于继承Binder类)。
  6. 通过AIDL文件名.Stub.asInterface(IBinder)来转化对象。

AIDL的一些使用例子

模仿支付宝(返回支付成功)

提高程序稳定性:app测试和稳定代码能力

根据支付结果,修改UI控件

  1. PayService继承于Service
  2. 注册,给第三方exported ,intent-filter action和category
  3. 根据action返回IBinder对象 return 第三方支付Binder对象
  4. 第三方支付使用AIDL接口 定义发起支付:orderinfo,paymoney,CallBack(AIDL回调接口) CallBack方法(支付成功,支付失败(错误码,错误信息))
  5. 继承第三方支付AIDL的类 并返回Binder对象
  6. 创建支付界面,显示支付信息(支付信息,支付金额,支付密码,确定支付)
  7. 接口方法中,直接打开支付页面并传递支付信息过去。 设置不同栈中,intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
  8. 在接口方法,创建回调接口方法给外部调用
  9. 在支付页面,绑定服务,获取别的界面传递的支付信息。将信息加载到控件上。设置点击事件。
  10. 定义继承于Binder的支付方法和用户取消支付(支付失败)。 (用于支付信息上传服务器,对比密码等一系列逻辑的服务类)
  11. 创建第三方应用:显示充值操作,绑定服务。

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

  1. 开启服务,然后绑定服务,如果不取消绑定服务,那么无法停止服务
  2. 开启服务以后,多次绑定—解绑服务。服务不会停止,只能通过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)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值