Android四大组件之 Service
2016-03-19 15:32
549 查看
今天我们来看一下Android的另一个重要的组件 Service
Service是一个Android应用中长时间留住且不与用户直接交互的后台服务或为另一个组件提供后台功能的应用服务。在默认情况下开启的Service服务运行在主线程中,这意味着如果在后台做一些耗时操作,应该自己启动一个单独操作的线程,比如IntentService就是一个有着自己线程处理任务的Service子类,具体我们下面会介绍。启动服务可以通过以下两种方式进行Context.startService()和 Context.bindService()
startService相当于默认在后台运行,可以处理后台的一些任务,而不是直接的和应用程序交互。
bindService是表示会暴露一些功能提供给应用程序,可以进行一种长期的连接。
关于Service我们要注意Service并不是一个独立的进程,Service对象本身并不意味着它是在自己的进程运行;除非特定指明,否则它和主程序运行在同一进程中。 下面我们来看一下Service组件的一些特性。
我们注意,在没有配置process属性时,以下方法都是在应用程序同一进程中的主线程中执行的
onCreate: 执行startService方法时,如果Service没有运行时会创建该Service并执行Service的onCreate回调方法;如果Service已经处于运行中,那么会执行startService方法而不会执行onCreate方法。也就是说多次执行startService,onCreate方法只会执行一次。我们可以在onCreate方法中完成一些Service初始化相关的操作。
onStartCommand: 通过startService方式启动Service后一定会执行Service的onStartCommand回调方法。如果多次startService,那么Service的onStartCommand方法也会多次调用。onStartCommand方法很重要,我们可以在该方法中根据传入的Intent参数进行实际的操作,比如会在此处创建一个线程用于下载数据或播放音乐等。这里我们可以注意到这个方法返回一个int类型的参数,那么这个参数是什么意思呢?其实,这个整数是一个描述系统在杀死当前Service所在的进程时,如何继续这个Service的值(虽然你能够修改这个值,但是IntentService还是为你提供了默认实现)。从onStartCommand()方法中返回的值必须是以下常量:
START_STICKY
如果service进程被kill掉,保留service的状态为开始状态,但不保留传递的intent对象。随后系统会尝试重新创建service,由于服务状态为开始状态,所以创建服务后会调用onStartCommand(Intent,int,int)方法。如果在此期间没有任何启动命令被传递到service,那么参数Intent将为null。
START_NOT_STICKY
“非粘性的“的方式,使用这个返回值时,如果在执行完onStartCommand后,服务被异常kill掉,系统不会自动重启该服务。这是最安全的选项,用来避免在不需要的时候运行你的服务。
START_REDELIVER_INTENT
重传传递Intent,使用这个返回值时,如果在执行完onStartCommand后,服务被异常kill掉,系统会自动重启该服务,并将Intent的值传入。
onBind: Service中的onBind方法是抽象方法,所以Service类本身也是抽象类,onBind方法是必须重写的,即使我们用不到。在通过startService使用Service时,我们在重写onBind方法时,只需要将其返回null即可。onBind方法主要是用于给bindService方式启动Service使用的。
onDestroy: 通过startService方式启动的Service会无限期运行,只有当调用了Context的stopService或在Service内部调用stopSelf方法时,Service才会停止运行并销毁,在销毁的时候会执行这个回调函数。
下面我们通过代码看一下这种启动方式,在主界面中会通过一个Button来调用startService来启动Service,然后点击三次看一下Log的日志信息,下面看一下Service的代码
我们来看一下运行的结果:
我们可以看到,所有在回调函数中打印出的执行线程的Name都是main,这就说明了Service中的各个回调方法是运行在主线程中的。其次我们可以发现在我们连续调用了三次startService方法之后,只触发了一次onCreate回调方法,触发了三次onStartCommand方法。
bindService启动的服务在调用者和服务之间是典型的client-server的接口,即调用者是客户端,service是服务端且只有一个,但是连接绑定到service上面的客户端client可以是一个或多个。这里特别要说明的是,这里所提到的client指的是组件,比如某个Activity。
客户端client(即调用bindService的一方,比如某个Activity)可以通过IBinder接口获取Service的实例,从而可以实现在client端直接调用Service中的方法以实现灵活的交互,并且可借助IBinder实现跨进程的client-server的交互,这在纯startService启动的Service中是无法实现的。
不同于startService启动模式下的无限期运行(可以通过Context的stopService或Service的stopSelf方法停止运行),bindService启动的服务的生命周期与其绑定的client息息相关。当client销毁的时候,client会自动与Service解除绑定,当然client也可以通过明确调用Context的unbindService方法与Service解除绑定。当没有任何client与Service绑定的时候,Service会自行销毁(通过startService启动的除外)。
startService和bindService二者执行的回调方法不同:startService启动的服务会涉及Service的的onStartCommand回调方法,而通过bindService启动的服务会涉及Service的onBind、onUnbind等回调方法。
我们看一下其中的生命周期方法
onBind:为Service返回通信通道
onUnbind:当所有的客户都与Service断开连接后会回调该方法。
onRebind:当之前所有客户端断开Service后,新的客户端连接Service后回调。
这里我们只介绍在同一个进程中的client和Service之间的交互,跨进程间如Messenger的AIDL的方式这里暂时不会涉及。在这种方式启动Service过程中我们关注这个重要的绑定过程
public boolean bindService(Intent service, ServiceConnection conn , int flags) 包含三个参数
service:这个参数通过Intent指定要启动的Service
conn:ServiceConnection 对象,该对象用于监听访问者与Service之间的连接情况。当访问者与Service之间连接成功时将会回调ServiceConnection对象的onServiceConnected(ComponentName name, IBinder service);当Service所在的宿主中止,导致Service与访问者之间断开时会回调ServiceConnection对象的onServiceDisconnected(ComponentName name)方法
flags:绑定时是否自动创建Service,0(不创建)或BIND_AUTO_CREATE(自动创建)
ServiceConnection对象的onServiceConnected方法中有一个IBinder对象,该对象即可与被绑定Service之间的通信
通信原理:在创建的Service类中必须提供一个IBinder onBind(Intent intent)方法,在绑定本地Service的情况下,onBind(Intent intent)方法所返回的IBinder对象会传给ServiceConnection对象的onServiceConnected的(ComponentName name, IBinder service)的service参数。这样访问者就可使用该IBinder对象与Service进行通信了
我们做一个例子,一个Activity中有三个按钮,分别是bindService(绑定Service)、unbindService(解绑Service)和getCount(从Service取值),下面我们还是通过代码更加直观的看一下
我们可以看到我们先点击了bindService按钮,然后再在3s和6s的时候点击了getCount按钮获得了值,最后unbindService过后解绑了Service,效果如下
我们可以从结果中看出bindService方式下的生命周期中的方法,注意在onBind后再次点击onBind没有任何效果,除非onUnbind后点击onBind又会重新onCreate和onBind。我们发现,效果图种只有serviceConnected那么serviceDisconnected哪里去了呢?
SDK上是这么说的:This is called when the connection with the service has been unexpectedly disconnected – that is, its process crashed. Because it is running in our same process, we should never see this happen.
所以说,只有在Service因异常而断开连接的时候,onServiceDisconnected方法才会用到。Android系统在同service的连接意外丢失时调用这个.比如当service崩溃了或被强杀了.当客户端解除绑定时,这个方法不会被调用.
如果一个Service又被启动又被绑定,则该Service将会一直在后台运行。并且不管如何调用,onCreate始终只会调用一次,对应startService调用多少次,Service的onStartCommand便会调用多少次。调用unbindService将不会停止Service,而必须调用stopService或 Service的 stopSelf来停止服务。当两种方式混合使用时有以下几点需要知道
如果先bindService,再startService,再调用Context.stopService(),Service的onDestroy()方法不会立即执行,因为有一个与Service绑定的Activity,但是在Activity退出的时候,会执行onDestroy,如果要立即执行stopService,就要先解除绑定,否则会报错(如果先执行startService,再执行bindService,结果是一样的)
如果Service已经启动并且接受绑定,那么当系统调用你的onUnbind方法,你可以选择返回true表示你想在客户端下一次绑定到service时接受一个onRebind()的调用(而不是一个OnBind()的调用),onRebind方法返回void,但是客户端依然在它的onServiceConnected方法回调中接收到IBinder。
如果在一个Activity的onCreate方法中,先bindService(),再startService(),退出这个Activity时,会执行onUnBind,但是再次进入这个Activity的时候不会执行onBind方法,只有在这个Service销毁后(执行onDestory),再进入这个Activity才会执行onBind。
当有两个客户端时,在第一个客户端startServie启动服务再bindService绑定服务(启动时会调用onBind方法),这时跳到第二个客户端里startService再bindService绑定服务,第二个客户端启动时就不会再调用onBind方法了,因为之前客户端已经启动并且没有onDestory销毁Service,所以在客户端第二次绑定服务时,只会返回IBinder对象给onServiceConnected,同样,当第一个客户端启动并绑定Service时,第二个客户端启动绑定这个Service再解绑时,也不会调用onUnbind方法,只有回到第一个客户端解绑时才会调用onUnbind方法,顺序反过来结果是一样的。
那么可以看出,当一个服务没被onDestory()销毁之前,只有第一个启动它的客户端能调用它的onBind和onUnbind方法。
以上就是混合使用时候需要注意和知道的一些总结,可以简单的通过Log日志看一下情况
以上我们都是Service和client在同一个进程中运行的,那如果我们想实现一个跨进程的服务比如类似守护进程模式的Service如何实现呢,下面让我们来看一下
在Android中采用的是Binder机制,这种机制其实是提供远程过程调用(RPC)功能。我们知道线程是CPU调度的最小单元,同时线程是一种有限的系统资源,而进程一般指一个执行的单元,一般一个进程中会包含一个或多个线程。Android的IPC是一个大的课题,我们在这里只是简略的说一下,实现进程通信的方式也有很多,比如通过ContentProvider、Receiver等,在这里我们只介绍一下Service在跨进程中如何进行数据交互。
如何开启一个多进程的模式呢,我们只需要简单的组件的xml文件配置中加入 android:process 属性,就可以开启一个新的进程
process属性可以自定义名字,在不设置时候其运行在默认的进程中,默认进程的进程名字是包名,在这里“:remote”表示 “包名:remote”这样一个单独的进程如”com.example.hkxlegend.mytest : remote” 。我们可以运行Service在DDMS中可以看到存在两个进程
这样就成功的创建了一个新的进程,似乎很简单,但是我们知道Android为每一个进程分配了一个独立的虚拟机,这就意味着内存的分配有着不同的地址空间,他们之间通过内存共享数据就会失败,新进程中的Service目的可以是为了处理更多的数据,我们知道Android对单个应用(进程)内存使用做了最大限制,不同机型也不同,早期一些版本是16M的,所以通过Service位于一个新进程,有时也是为了处理更多的数据,那么跨进程数据交互就变得非常重要。
在数据传输过程中,我们通常会使用Serializable和Parcelable接口来完成对象序列化,这样才可以通过Intent和Binder来传输这些数据,这里不会过多的介绍这两个接口。
说道Binder,它是实现了IBinder接口的一个类,他就是Android中的一种跨进程通信方式,可以理解为一种通信的媒介,主要就是用于Service中,包括AIDL和Messenger(底层其实也是AIDL),其中普通的Service不包含进程通信比较简单,没有涉及到Binder的核心。
IPC方式包含了很多,比如ContentProvider天生就支持跨进程共享数据,Socket也可以,Intent中通过extras来传递信息,或采用文件共享的方式。后期我们会单独介绍ContentProvide和IPC机制,这里只介绍Messenger和AIDL这两种方式。
Messenger使用方法很简单,它已经对AIDL做了封装,我们可以简便的进行进程间通信。同时,由于它一次处理一个请求,因此我们在服务端不用考虑线程同步的问题,因为服务端中不存在并发执行的情况。Messenger的主要作用是为了传递消息,有时候我们需要跨进程调用服务端的方法,这种情况Messenger是无法做到的,但是可以通过AIDL,我们先看一个Messenger的例子,实现一个完整的Messenger分为客户端和服务端两部分
1. 首先我们要在服务端创建一个Service来处理客户端的请求,同时创建一个Handler对象,并通过它创建一个Messenger对象(无论是服务端还是客户端想要接收来自对方的数据,都需要有这个Handler和Messenger对象),然后在Service的onBind中返回这个Messenger对象底层的Binder即可。在回馈客户端时,服务端需要回复一条消息给客户端,同样也放在Message中进行传输,Messenger得到的方式为msg.replyTo方法,然后再创建一个Message对象,里面放入所要传回的数据,然后通过Messenger的send方式传递数据
2. 客户端是一个Activity,它同样需要接收来自客户端的数据所以也要有一对Handler和Messenger对象,当然还需要一个向服务器传递数据的Messenger对象来通过send方法传递数据。由于存在接收来自服务器端的数据情况,我们需要把接受服务端回复的Messenger通过Message的replyTo参数传递给服务端
然后让我们看一下效果,可以看到有两个进程MyTest 运行着,调用的加法计算也是成功正确的返回给另一个进程结果
基本数据类型(int、long、char、boolean、double、float等);
String和CharSquence;
List:只支持ArrayList,里面的元素同样需要AIDL支持;
Map:只支持HashMap,里面的元素(key、value)同样需要AIDL支持;
Parcelable:所有实现Parcelable接口的对象;
AIDL:AIDL本身也是可以的
其中Parcelable对象和AIDL需要显示的通过import导入,无论是否位于同一个包内。同样,如果AIDL中用到了自定义的Parcelable对象,那么必须新建一个和他同名的AIDL文件,并在其中声明他是Parcelable类型。比如我们要传第一个Person对象作为进程间通信的信息内容,那么我们创建一个aidl包,放入Person类,然后项目中创建与它对应的AIDL文件
然后是与它对应的aidl:
在IBookManager的AIDL文件中需要导入这个Book的AIDL
注意,我们看到上面的addBook方法中有一个in,是因为AIDL中除了基本数据类型外,其他类型参数要标明方向:in(输入型参数);out(输出型参数);inout(输入输出型参数),我们要根据需求选择参数方向,不能一概用inout,因为底层实现是有开销的。而且AIDL中不支持声明静态常量。
为了方便AIDL的开发,我们可以把所有.aidl文件放在一个文件夹下,这样方便直接把整个包复制到另外一个相关的项目中去,当然Android studio中已经帮我们进行了归类,会为我们自己创建一个aidl的文件夹。同样我们要注意,AIDL包结构在服务端和客户端要一致,否则客户端在反序列化服务端中AIDL接口时无法成功。
编辑好以上aidl文件后我们将项目Rebuild一下,在app/build/generated/source/aidl/debug下会生成一个对应的aidl文件的java的Binder类,项目结构图如下
对于系统生成的Binder类我们这里不做详细介绍,后期我们会针对Binder机制作一篇分析。这样我们的传递数据类Person和aidl就准备好了,接下来我们从客户端和服务器端来看一下如何进行传递数据和方法调用。
先看一下服务端Service代码
然后看一下客户端Activity代码,首先绑定远端服务,绑定成功后返回Binder对象转换成为AIDL接口,然后就可以通过这个接口去调用服务端的代码了
最后,让我们看一下运行的效果
如何创建一个IntentService呢,其实很简单,我们只需要继承IntentService类,并重写onHandleIntent()方法即可,如下
我们在Activity的onCreate方法中,调用以下代码连续三次启动这个Service
得到结果如下:
可以看到每次只会执行一个工作线程,且不会阻塞应用程序的UI线程。
我们对于Service的相关内容就先介绍到这里,下一篇会介绍Android的另一个组件BroadCastReceiver
Service是一个Android应用中长时间留住且不与用户直接交互的后台服务或为另一个组件提供后台功能的应用服务。在默认情况下开启的Service服务运行在主线程中,这意味着如果在后台做一些耗时操作,应该自己启动一个单独操作的线程,比如IntentService就是一个有着自己线程处理任务的Service子类,具体我们下面会介绍。启动服务可以通过以下两种方式进行Context.startService()和 Context.bindService()
startService相当于默认在后台运行,可以处理后台的一些任务,而不是直接的和应用程序交互。
bindService是表示会暴露一些功能提供给应用程序,可以进行一种长期的连接。
关于Service我们要注意Service并不是一个独立的进程,Service对象本身并不意味着它是在自己的进程运行;除非特定指明,否则它和主程序运行在同一进程中。 下面我们来看一下Service组件的一些特性。
Service的生命周期
由于Service有两种启动方式,所以也有相对应的两种生命周期startService方式启动
通过调用了Context的startService方法后,我们便启动了Service,通过startService方法启动的Service会一直无限期地运行下去,只有在外部调用Context的stopService或Service内部调用stopSelf方法时,该Service才会停止运行并销毁。我们注意,在没有配置process属性时,以下方法都是在应用程序同一进程中的主线程中执行的
onCreate: 执行startService方法时,如果Service没有运行时会创建该Service并执行Service的onCreate回调方法;如果Service已经处于运行中,那么会执行startService方法而不会执行onCreate方法。也就是说多次执行startService,onCreate方法只会执行一次。我们可以在onCreate方法中完成一些Service初始化相关的操作。
onStartCommand: 通过startService方式启动Service后一定会执行Service的onStartCommand回调方法。如果多次startService,那么Service的onStartCommand方法也会多次调用。onStartCommand方法很重要,我们可以在该方法中根据传入的Intent参数进行实际的操作,比如会在此处创建一个线程用于下载数据或播放音乐等。这里我们可以注意到这个方法返回一个int类型的参数,那么这个参数是什么意思呢?其实,这个整数是一个描述系统在杀死当前Service所在的进程时,如何继续这个Service的值(虽然你能够修改这个值,但是IntentService还是为你提供了默认实现)。从onStartCommand()方法中返回的值必须是以下常量:
START_STICKY
如果service进程被kill掉,保留service的状态为开始状态,但不保留传递的intent对象。随后系统会尝试重新创建service,由于服务状态为开始状态,所以创建服务后会调用onStartCommand(Intent,int,int)方法。如果在此期间没有任何启动命令被传递到service,那么参数Intent将为null。
START_NOT_STICKY
“非粘性的“的方式,使用这个返回值时,如果在执行完onStartCommand后,服务被异常kill掉,系统不会自动重启该服务。这是最安全的选项,用来避免在不需要的时候运行你的服务。
START_REDELIVER_INTENT
重传传递Intent,使用这个返回值时,如果在执行完onStartCommand后,服务被异常kill掉,系统会自动重启该服务,并将Intent的值传入。
onBind: Service中的onBind方法是抽象方法,所以Service类本身也是抽象类,onBind方法是必须重写的,即使我们用不到。在通过startService使用Service时,我们在重写onBind方法时,只需要将其返回null即可。onBind方法主要是用于给bindService方式启动Service使用的。
onDestroy: 通过startService方式启动的Service会无限期运行,只有当调用了Context的stopService或在Service内部调用stopSelf方法时,Service才会停止运行并销毁,在销毁的时候会执行这个回调函数。
下面我们通过代码看一下这种启动方式,在主界面中会通过一个Button来调用startService来启动Service,然后点击三次看一下Log的日志信息,下面看一下Service的代码
public class MyService extends Service { private static final String TAG = "myTag"; private static int count = 0;//记录点击次数 @Override public void onCreate() { super.onCreate(); Log.v(TAG, "onCreate"+":"+Thread.currentThread().getName()); } @Override public int onStartCommand(Intent intent, int flags, int startId) { count++; Log.v(TAG, "onStartCommand" + count+":"+Thread.currentThread().getName()); if (count >= 3) { stopSelf();//当计数为3时自动销毁 } return super.onStartCommand(intent, flags, startId); } @Override public IBinder onBind(Intent intent) { return null; } @Override public void onDestroy() { super.onDestroy(); Log.v(TAG, "onDestroy"+":"+Thread.currentThread().getName()); } }
我们来看一下运行的结果:
我们可以看到,所有在回调函数中打印出的执行线程的Name都是main,这就说明了Service中的各个回调方法是运行在主线程中的。其次我们可以发现在我们连续调用了三次startService方法之后,只触发了一次onCreate回调方法,触发了三次onStartCommand方法。
bindService方式启动
相比于用startService启动的Service,bindService启动的服务具有如下特点:bindService启动的服务在调用者和服务之间是典型的client-server的接口,即调用者是客户端,service是服务端且只有一个,但是连接绑定到service上面的客户端client可以是一个或多个。这里特别要说明的是,这里所提到的client指的是组件,比如某个Activity。
客户端client(即调用bindService的一方,比如某个Activity)可以通过IBinder接口获取Service的实例,从而可以实现在client端直接调用Service中的方法以实现灵活的交互,并且可借助IBinder实现跨进程的client-server的交互,这在纯startService启动的Service中是无法实现的。
不同于startService启动模式下的无限期运行(可以通过Context的stopService或Service的stopSelf方法停止运行),bindService启动的服务的生命周期与其绑定的client息息相关。当client销毁的时候,client会自动与Service解除绑定,当然client也可以通过明确调用Context的unbindService方法与Service解除绑定。当没有任何client与Service绑定的时候,Service会自行销毁(通过startService启动的除外)。
startService和bindService二者执行的回调方法不同:startService启动的服务会涉及Service的的onStartCommand回调方法,而通过bindService启动的服务会涉及Service的onBind、onUnbind等回调方法。
我们看一下其中的生命周期方法
onBind:为Service返回通信通道
onUnbind:当所有的客户都与Service断开连接后会回调该方法。
onRebind:当之前所有客户端断开Service后,新的客户端连接Service后回调。
这里我们只介绍在同一个进程中的client和Service之间的交互,跨进程间如Messenger的AIDL的方式这里暂时不会涉及。在这种方式启动Service过程中我们关注这个重要的绑定过程
public boolean bindService(Intent service, ServiceConnection conn , int flags) 包含三个参数
service:这个参数通过Intent指定要启动的Service
conn:ServiceConnection 对象,该对象用于监听访问者与Service之间的连接情况。当访问者与Service之间连接成功时将会回调ServiceConnection对象的onServiceConnected(ComponentName name, IBinder service);当Service所在的宿主中止,导致Service与访问者之间断开时会回调ServiceConnection对象的onServiceDisconnected(ComponentName name)方法
flags:绑定时是否自动创建Service,0(不创建)或BIND_AUTO_CREATE(自动创建)
ServiceConnection对象的onServiceConnected方法中有一个IBinder对象,该对象即可与被绑定Service之间的通信
通信原理:在创建的Service类中必须提供一个IBinder onBind(Intent intent)方法,在绑定本地Service的情况下,onBind(Intent intent)方法所返回的IBinder对象会传给ServiceConnection对象的onServiceConnected的(ComponentName name, IBinder service)的service参数。这样访问者就可使用该IBinder对象与Service进行通信了
我们做一个例子,一个Activity中有三个按钮,分别是bindService(绑定Service)、unbindService(解绑Service)和getCount(从Service取值),下面我们还是通过代码更加直观的看一下
/** * 这个是Service端的代码 */ public class MyService extends Service { private static final String TAG = "myTag"; private boolean flag = true;//标记位 private static int count = 0; private MyBinder mBinder = new MyBinder(); private CountThread thread = new CountThread(); class MyBinder extends Binder { public int getCount() { return count; } } //后台线程任务类 class CountThread extends Thread { public void run() { while (flag) { { try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } count++; } } } } @Override public void onCreate() { super.onCreate(); writeTag("onCreate"); thread.start(); } @Override public int onStartCommand(Intent intent, int flags, int startId) { writeTag("onStartCommand"); return START_NOT_STICKY; } @Override public IBinder onBind(Intent intent) { writeTag("onBind"); return mBinder; } @Override public void onRebind(Intent intent) { super.onRebind(intent); Log.i(TAG, "onRebind"); } @Override public boolean onUnbind(Intent intent) { writeTag("onUnbind"); return super.onUnbind(intent); } @Override public void onDestroy() { writeTag("onDestroy"); super.onDestroy(); flag = false;//关闭线程 } public void writeTag(String s) { Log.v(TAG, s + " : " + Thread.currentThread().getName()); } }
/** * 这个是client端的Activity */ public class MainActivity extends ActionBarActivity { private MyService.MyBinder mBinder; private static final String TAG = "myTag"; private ServiceConnection con = new ServiceConnection() { @Override public void onServiceConnected(ComponentName name, IBinder service) { mBinder = (MyService.MyBinder) service; Log.v(TAG, "serviceConnected.."); } @Override public void onServiceDisconnected(ComponentName name) { Toast.makeText(MainActivity.this, "serviceDisconnected..",Toast.LENGTH_SHORT).show(); Log.v(TAG, "serviceDisconnected.."); } }; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); findViewById(R.id.onBindButton).setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { Intent intent = new Intent(MainActivity.this, MyService.class); bindService(intent, con, BIND_AUTO_CREATE); } }); findViewById(R.id.onUnbindButton).setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { unbindService(con); } }); findViewById(R.id.getCountButton).setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { Log.v(TAG, "from service :" + mBinder.getCount()); } }); } }
我们可以看到我们先点击了bindService按钮,然后再在3s和6s的时候点击了getCount按钮获得了值,最后unbindService过后解绑了Service,效果如下
我们可以从结果中看出bindService方式下的生命周期中的方法,注意在onBind后再次点击onBind没有任何效果,除非onUnbind后点击onBind又会重新onCreate和onBind。我们发现,效果图种只有serviceConnected那么serviceDisconnected哪里去了呢?
SDK上是这么说的:This is called when the connection with the service has been unexpectedly disconnected – that is, its process crashed. Because it is running in our same process, we should never see this happen.
所以说,只有在Service因异常而断开连接的时候,onServiceDisconnected方法才会用到。Android系统在同service的连接意外丢失时调用这个.比如当service崩溃了或被强杀了.当客户端解除绑定时,这个方法不会被调用.
startService结合bindService方式
下面我们看一下如果两种方式结合使用,那么他的生命周期又会有什么特点呢?如果一个Service又被启动又被绑定,则该Service将会一直在后台运行。并且不管如何调用,onCreate始终只会调用一次,对应startService调用多少次,Service的onStartCommand便会调用多少次。调用unbindService将不会停止Service,而必须调用stopService或 Service的 stopSelf来停止服务。当两种方式混合使用时有以下几点需要知道
如果先bindService,再startService,再调用Context.stopService(),Service的onDestroy()方法不会立即执行,因为有一个与Service绑定的Activity,但是在Activity退出的时候,会执行onDestroy,如果要立即执行stopService,就要先解除绑定,否则会报错(如果先执行startService,再执行bindService,结果是一样的)
如果Service已经启动并且接受绑定,那么当系统调用你的onUnbind方法,你可以选择返回true表示你想在客户端下一次绑定到service时接受一个onRebind()的调用(而不是一个OnBind()的调用),onRebind方法返回void,但是客户端依然在它的onServiceConnected方法回调中接收到IBinder。
如果在一个Activity的onCreate方法中,先bindService(),再startService(),退出这个Activity时,会执行onUnBind,但是再次进入这个Activity的时候不会执行onBind方法,只有在这个Service销毁后(执行onDestory),再进入这个Activity才会执行onBind。
当有两个客户端时,在第一个客户端startServie启动服务再bindService绑定服务(启动时会调用onBind方法),这时跳到第二个客户端里startService再bindService绑定服务,第二个客户端启动时就不会再调用onBind方法了,因为之前客户端已经启动并且没有onDestory销毁Service,所以在客户端第二次绑定服务时,只会返回IBinder对象给onServiceConnected,同样,当第一个客户端启动并绑定Service时,第二个客户端启动绑定这个Service再解绑时,也不会调用onUnbind方法,只有回到第一个客户端解绑时才会调用onUnbind方法,顺序反过来结果是一样的。
那么可以看出,当一个服务没被onDestory()销毁之前,只有第一个启动它的客户端能调用它的onBind和onUnbind方法。
以上就是混合使用时候需要注意和知道的一些总结,可以简单的通过Log日志看一下情况
以上我们都是Service和client在同一个进程中运行的,那如果我们想实现一个跨进程的服务比如类似守护进程模式的Service如何实现呢,下面让我们来看一下
Service跨进程应用
我们知道Android是基于linux内核的,在Android系统中,每一个应用程序都是由一些Activity和Service组成的,这些Activity和Service有可能运行在同一个进程中,也有可能运行在不同的进程中,在不同进程中的时候就会涉及到Unix系统进程间通信(IPC)机制。在Android中采用的是Binder机制,这种机制其实是提供远程过程调用(RPC)功能。我们知道线程是CPU调度的最小单元,同时线程是一种有限的系统资源,而进程一般指一个执行的单元,一般一个进程中会包含一个或多个线程。Android的IPC是一个大的课题,我们在这里只是简略的说一下,实现进程通信的方式也有很多,比如通过ContentProvider、Receiver等,在这里我们只介绍一下Service在跨进程中如何进行数据交互。
如何开启一个多进程的模式呢,我们只需要简单的组件的xml文件配置中加入 android:process 属性,就可以开启一个新的进程
<service android:name=".MyService" android:process=":remote"> </service>
process属性可以自定义名字,在不设置时候其运行在默认的进程中,默认进程的进程名字是包名,在这里“:remote”表示 “包名:remote”这样一个单独的进程如”com.example.hkxlegend.mytest : remote” 。我们可以运行Service在DDMS中可以看到存在两个进程
这样就成功的创建了一个新的进程,似乎很简单,但是我们知道Android为每一个进程分配了一个独立的虚拟机,这就意味着内存的分配有着不同的地址空间,他们之间通过内存共享数据就会失败,新进程中的Service目的可以是为了处理更多的数据,我们知道Android对单个应用(进程)内存使用做了最大限制,不同机型也不同,早期一些版本是16M的,所以通过Service位于一个新进程,有时也是为了处理更多的数据,那么跨进程数据交互就变得非常重要。
在数据传输过程中,我们通常会使用Serializable和Parcelable接口来完成对象序列化,这样才可以通过Intent和Binder来传输这些数据,这里不会过多的介绍这两个接口。
说道Binder,它是实现了IBinder接口的一个类,他就是Android中的一种跨进程通信方式,可以理解为一种通信的媒介,主要就是用于Service中,包括AIDL和Messenger(底层其实也是AIDL),其中普通的Service不包含进程通信比较简单,没有涉及到Binder的核心。
IPC方式包含了很多,比如ContentProvider天生就支持跨进程共享数据,Socket也可以,Intent中通过extras来传递信息,或采用文件共享的方式。后期我们会单独介绍ContentProvide和IPC机制,这里只介绍Messenger和AIDL这两种方式。
Messenger方式
Messenger的底层是AIDL,可以翻译成信使,通过他可以在不同的进程间传递Message对象。我们只需要把数据放在Message中就可以进行数据传递了,Messenger也是实现了Parcelable接口的对象。Messenger使用方法很简单,它已经对AIDL做了封装,我们可以简便的进行进程间通信。同时,由于它一次处理一个请求,因此我们在服务端不用考虑线程同步的问题,因为服务端中不存在并发执行的情况。Messenger的主要作用是为了传递消息,有时候我们需要跨进程调用服务端的方法,这种情况Messenger是无法做到的,但是可以通过AIDL,我们先看一个Messenger的例子,实现一个完整的Messenger分为客户端和服务端两部分
1. 首先我们要在服务端创建一个Service来处理客户端的请求,同时创建一个Handler对象,并通过它创建一个Messenger对象(无论是服务端还是客户端想要接收来自对方的数据,都需要有这个Handler和Messenger对象),然后在Service的onBind中返回这个Messenger对象底层的Binder即可。在回馈客户端时,服务端需要回复一条消息给客户端,同样也放在Message中进行传输,Messenger得到的方式为msg.replyTo方法,然后再创建一个Message对象,里面放入所要传回的数据,然后通过Messenger的send方式传递数据
/** * 这个是服务端Service端的代码 */ public class MyService extends Service { private final static int MESSAGE_FROM_CLIENT = 0x123;//来自客户端 private final static int MESSAGE_FROM_SERVER = 0x321;//来自服务端 private class ServerHandler extends Handler { @Override public void handleMessage(Message msg) { switch (msg.what) { case MESSAGE_FROM_CLIENT: //取数据并计算 Bundle bundle = msg.getData(); String[] strings = bundle.getStringArray("data"); int first = Integer.parseInt(strings[0]); int second = Integer.parseInt(strings[1]); int finalCount = addCount(first, second); //返回计算后的数据 Messenger serverMessenger = msg.replyTo;//返回数据的Messenger Message message = Message.obtain(null, MESSAGE_FROM_SERVER); Bundle data = new Bundle(); data.putString("count", "得到结果为: " + finalCount); message.setData(data); try { serverMessenger.send(message); } catch (RemoteException e) { e.printStackTrace(); } break; default: super.handleMessage(msg); } } } private Messenger receiveMessenger = new Messenger(new ServerHandler());//配合handler接收消息的Messenger /** * 处理客户端发送来的int做加法 * * @param i * @param j * @return */ public int addCount(int i, int j) { return i + j; } @Override public IBinder onBind(Intent intent) { return receiveMessenger.getBinder(); } }
2. 客户端是一个Activity,它同样需要接收来自客户端的数据所以也要有一对Handler和Messenger对象,当然还需要一个向服务器传递数据的Messenger对象来通过send方法传递数据。由于存在接收来自服务器端的数据情况,我们需要把接受服务端回复的Messenger通过Message的replyTo参数传递给服务端
/** * 这个是客户端的Activity */ public class MainActivity extends ActionBarActivity { private final static int MESSAGE_FROM_CLIENT = 0x123;//来自客户端 private final static int MESSAGE_FROM_SERVER = 0x321;//来自服务端 private Button addButton; private EditText firstEdit; private EditText secondEdit; private class clientHandler extends Handler { @Override public void handleMessage(Message msg) { switch (msg.what) { case MESSAGE_FROM_SERVER: //处理返回的数据 Bundle data = msg.getData(); String i = data.getString("count"); Toast.makeText(MainActivity.this, i, Toast.LENGTH_SHORT).show(); default: super.handleMessage(msg); } } } private Messenger receiveMessenger = new Messenger(new clientHandler());//配合handler接收消息的Messenger private Messenger clientMessenger;//发送消息的Messenger private ServiceConnection conn = new ServiceConnection() { @Override public void onServiceConnected(ComponentName name, IBinder service) { clientMessenger = new Messenger(service); } @Override public void onServiceDisconnected(ComponentName name) { } }; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); initView(); //绑定Service Intent intent = new Intent(MainActivity.this, MyService.class); bindService(intent, conn, BIND_AUTO_CREATE); addButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { String firstNum = firstEdit.getText().toString().trim(); String second = secondEdit.getText().toString().trim(); //简单的判断一下 if (!("".equals(firstNum) || "".equals(second))) { Message msg = Message.obtain(null, MESSAGE_FROM_CLIENT); Bundle bundle = new Bundle(); bundle.putStringArray("data", new String[]{firstNum, second}); msg.setData(bundle); msg.replyTo = receiveMessenger;//将接收消息的Messenger传递给服务端 try { clientMessenger.send(msg); } catch (RemoteException e) { e.printStackTrace(); } } else { Toast.makeText(MainActivity.this, "输入能为空", Toast.LENGTH_SHORT).show(); } } }); } private void initView() { addButton = (Button) findViewById(R.id.button_add); firstEdit = (EditText) findViewById(R.id.edittext_first); secondEdit = (EditText) findViewById(R.id.edittext_second); } }
然后让我们看一下效果,可以看到有两个进程MyTest 运行着,调用的加法计算也是成功正确的返回给另一个进程结果
AIDL方式
下面我们来一下AIDL方式,AIDL同样也是Messenger的底层实现,同样AIDL进程间通信也是分为服务端和客户端两个部分。AIDL使我们可以在客户端方便的调用服务端方法,这是一种跨进程通信方法。首先我们要学会创建一个 “.aidl”文件,AIDL文件中支持的数据有以下几种基本数据类型(int、long、char、boolean、double、float等);
String和CharSquence;
List:只支持ArrayList,里面的元素同样需要AIDL支持;
Map:只支持HashMap,里面的元素(key、value)同样需要AIDL支持;
Parcelable:所有实现Parcelable接口的对象;
AIDL:AIDL本身也是可以的
其中Parcelable对象和AIDL需要显示的通过import导入,无论是否位于同一个包内。同样,如果AIDL中用到了自定义的Parcelable对象,那么必须新建一个和他同名的AIDL文件,并在其中声明他是Parcelable类型。比如我们要传第一个Person对象作为进程间通信的信息内容,那么我们创建一个aidl包,放入Person类,然后项目中创建与它对应的AIDL文件
/** * 我们要跨进程传递这个对象,所以要实现Parcelable接口 * 实现Parcelable的对象,可以通过Intent和Binder传递 * 实现序列化:writeToParcel方法完成 * 实现反序列化:CREATOR对象完成 * 内容描述:describeContents完成,存在描述时为1,否则大多情况为0 */ public class Person implements Parcelable { public String name; public int age; public Person(String name, int age) { this.name = name; this.age = age; } public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } protected Person(Parcel in) { name = in.readString(); age = in.readInt(); } public static final Creator<Person> CREATOR = new Creator<Person>() { @Override public Person createFromParcel(Parcel in) { return new Person(in); } @Override public Person[] newArray(int size) { return new Person[size]; } }; @Override public int describeContents() { return 0; } @Override public void writeToParcel(Parcel dest, int flags) { dest.writeString(name); dest.writeInt(age); } }
然后是与它对应的aidl:
// Person.aidl package com.example.hkxlegend.mytest; parcelable Person;
在IBookManager的AIDL文件中需要导入这个Book的AIDL
// IPersonManager.aidl package com.example.hkxlegend.mytest; import com.example.hkxlegend.mytest.Person; interface IPersonManager { List<Person> getPeople(); void addPerson (in Person person); }
注意,我们看到上面的addBook方法中有一个in,是因为AIDL中除了基本数据类型外,其他类型参数要标明方向:in(输入型参数);out(输出型参数);inout(输入输出型参数),我们要根据需求选择参数方向,不能一概用inout,因为底层实现是有开销的。而且AIDL中不支持声明静态常量。
为了方便AIDL的开发,我们可以把所有.aidl文件放在一个文件夹下,这样方便直接把整个包复制到另外一个相关的项目中去,当然Android studio中已经帮我们进行了归类,会为我们自己创建一个aidl的文件夹。同样我们要注意,AIDL包结构在服务端和客户端要一致,否则客户端在反序列化服务端中AIDL接口时无法成功。
编辑好以上aidl文件后我们将项目Rebuild一下,在app/build/generated/source/aidl/debug下会生成一个对应的aidl文件的java的Binder类,项目结构图如下
对于系统生成的Binder类我们这里不做详细介绍,后期我们会针对Binder机制作一篇分析。这样我们的传递数据类Person和aidl就准备好了,接下来我们从客户端和服务器端来看一下如何进行传递数据和方法调用。
先看一下服务端Service代码
<service android:name=".MyService" android:process=":remote"> </service>
/** * 这个是服务端Service的代码 */ public class MyService extends Service { private ArrayList<Person> arrays = new ArrayList<>(); @Override public void onCreate() { super.onCreate(); //先预先添加一个信息 arrays.add(new Person("Tom", 23)); } //mBindr对象继承自IPersonManager.Stub并实现了内部的AIDL方法 //这个Stub就是一个Binder类,核心就是Stub和代理类Proxy private Binder mBinder = new IPersonManager.Stub() { @Override public List<Person> getPeople() throws RemoteException { return arrays; } @Override public void addPerson(Person person) throws RemoteException { arrays.add(person); } }; @Override public IBinder onBind(Intent intent) { return mBinder; } }
然后看一下客户端Activity代码,首先绑定远端服务,绑定成功后返回Binder对象转换成为AIDL接口,然后就可以通过这个接口去调用服务端的代码了
/** * 这个是客户端的Activity */ public class MainActivity extends ActionBarActivity implements View.OnClickListener { private Button addButton; private Button searchButton; private EditText nameEdit; private EditText ageEdit; private IPersonManager mManager; private ServiceConnection conn = new ServiceConnection() { @Override public void onServiceConnected(ComponentName name, IBinder service) { //将返回的Binder转换为AIDL接口 mManager = IPersonManager.Stub.asInterface(service); } @Override public void onServiceDisconnected(ComponentName name) { } }; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); initView(); Intent intent = new Intent(this,MyService.class); bindService(intent,conn,BIND_AUTO_CREATE); } private void initView() { addButton = (Button) findViewById(R.id.button_add); searchButton = (Button) findViewById(R.id.button_search); nameEdit = (EditText) findViewById(R.id.edittext_first); ageEdit = (EditText) findViewById(R.id.edittext_second); addButton.setOnClickListener(this); searchButton.setOnClickListener(this); } @Override protected void onDestroy() { super.onDestroy(); unbindService(conn); } @Override public void onClick(View v) { switch (v.getId()) { case R.id.button_add: String name = nameEdit.getText().toString().trim(); int age = Integer.parseInt(ageEdit.getText().toString().trim()); Person pe = new Person(name, age); try { mManager.addPerson(pe); } catch (RemoteException e) { e.printStackTrace(); } Toast.makeText(MainActivity.this, "success", Toast.LENGTH_SHORT).show(); break; case R.id.button_search: StringBuilder sb = new StringBuilder(); try { List<Person> people = mManager.getPeople(); for (Person p : people) { sb.append("name:" + p.getName() + "---" + "age:" + p.getAge() + "\n"); } Toast.makeText(MainActivity.this, sb.toString(), Toast.LENGTH_SHORT).show(); } catch (RemoteException e) { e.printStackTrace(); } break; } } }
最后,让我们看一下运行的效果
Service的一个子类IntentService
IntentService是继承于Service并处理异步请求的一个类,在IntentService内有一个工作线程来处理耗时操作,启动IntentService的方式和启动传统Service一样,同时,当任务执行完后,IntentService会自动停止,而不需要我们去手动控制。另外,可以启动IntentService多次,而每一个耗时操作会以工作队列的方式在IntentService的onHandleIntent回调方法中执行,并且,每次只会执行一个工作线程,所有请求都在一个单线程中,不会阻塞应用程序的主线程(UI Thread),同一时间只处理一个请求。那么,用IntentService有什么好处呢?首先,我们省去了在Service中手动开线程的麻烦,第二,当操作完成时,我们不用手动停止Service。如何创建一个IntentService呢,其实很简单,我们只需要继承IntentService类,并重写onHandleIntent()方法即可,如下
public class MyService extends IntentService { public MyService(){ super("myService"); } @Override protected void onHandleIntent(Intent intent) { // IntentService会使用单独的线程来执行该方法的代码 // 该方法内执行耗时任务,比如下载文件,此处只是让线程等待5秒 long endTime = System.currentTimeMillis() + 5 * 1000; System.out.println("onStart"); while (System.currentTimeMillis() < endTime) { synchronized (this) { try { wait(endTime - System.currentTimeMillis()); } catch (InterruptedException e) { e.printStackTrace(); } } } Log.v("myTag", "finished"+System.currentTimeMillis()); } }
我们在Activity的onCreate方法中,调用以下代码连续三次启动这个Service
for(int i = 0; i<3; i++){ Intent intent = new Intent(this,MyService.class); startService(intent); }
得到结果如下:
可以看到每次只会执行一个工作线程,且不会阻塞应用程序的UI线程。
我们对于Service的相关内容就先介绍到这里,下一篇会介绍Android的另一个组件BroadCastReceiver
相关文章推荐
- Activity的生命周期和启动模式
- 基础地图Android SDK
- [Android进阶]Android消息机制
- Android设置TextView字间距与行间距
- Android Binder机制(超级详尽)
- Android处理滑动与点击事件的冲突
- android listview去掉分割线
- Android记录软件每天第一次打开
- Android通过子线程和handler实现倒计时,可以开始暂停倒计时
- Android实时获得周围wifi信息(SSID,强度等)
- Android之MVC模式
- android 中常见8种开发模式
- [安装报错]Android Studio报错 'reg'不是内部或外部命令,也不是可运行的程序或批处理文件。
- android View的事件分发调用顺序
- android 编译C生成so动态链接库
- 从零开始的Android新项目2 - Gradle篇
- Mac上反编译Android-apk傻瓜式记录。
- Conversion to Dalvik format failed with error 1 in Android on export
- Android常用控件——Dialog之AlertDialog和ProgressDialog
- Android Audio系统分析1(获得最小buffer部分)