Android Service

来源:互联网 发布:淘宝直播间网址 编辑:程序博客网 时间:2024/06/06 05:05

Android Service

所谓的service有Local Service和Remote Service区分:

LocalService:就是client和Service在同一个进程当中。

RemoteService:就是client 和Service在不同的进程中。

我们通常的用法是自己建一个.java文件中通过继承Service来实现一个子Service。然后在通过其他的Activity来连接实现的那个Service就有点像网络编程中的链接服务器一样。但是这里的Service不是独立的一个服务器端,它可以说是手机app的一个实现模块。所以不是像传统的网络编程一样,首先启动服务器,然后在从client去访问。android中的Service要通过访问端通过传递消息(Intent)去开启Service。通常有两种方法去启动一个Service。

一)context.startService()启动方式

public abstract ComponentName startService(Intent service)

通过函数的原型我们可以看出是一个Component去调用的,参数就是一个Intent,在该Intent中去指定一些规则和消息来启动符合条件的Service。

The Intent can either contain the complete class name of a specific service implementation to start, or an abstract definition through the action and other fields of the kind of service to start(by gg doc).

此时我们明确了client端发出去Intent去启动Service的过程,但是Service端是怎么来响应的?然后怎么启动你实现的子Service的呢?

Every call to this method(startService(Intent intent)) will result in a corresponding call to the target service's 

onStartCommand(Intent, int, int)(by gg doc)就是each component调用startService(Intent intent)来启动Service的时候,service端作出的响应就是call onStartCommand(Intent,int,int)函数

public int onStartCommand (Intent intent, int flags, int startId)

当调用该函数的时候intent对象就是startService(Intent intent)红传递过来的intent对象。

flags:start request的额外请求,可以是:0, START_FLAG_REDELIVERY,START_FLAG_RETRY.

startId:an unique integer 来展示这次请求

目前gg doc给出了四种返回值类型。

START_STICKY:如果service进程被kill掉,保留service的状态为开始状态,但不保留递送的intent对象。随后系统会尝试重新创建service,由于服务状态为开始状态,所以创建服务后一定会调用onStartCommand(Intent,int,int)方法。如果在此期间没有任何启动命令被传递到service,那么参数Intent将为null。

START_NOT_STICKY:返回该返回值时,如果在执行完onStartCommand后,Service被一场kill掉后,系统不会重启该Service。

START_REDELIVER_INTENT:但返回这个返回值的时候,从startService传过来的intent对象将保存它的数据,当Service被异常kill掉的后,Service会重启,然后再将Intent传入。

START_STICKY_COMPATIBILITY:START_STICKY的兼容版本,但不保证服务被kill后一定能重启

这个函数不能人为的去直接调用,而是由Android system在Service的main thread中有系统去调用。

所以一般情况下继承Service的子类都要重写Service类的:OnCreate(),onStartCommand(),onBinder(),onStart(),onDestory(),onRebind(),onUnbind()方法,然后在编写其他自己要是想的功能实现函数。

所以通过context.startService(Intent intent)来启动Service时将是一个:

client端 Service端

startService()---------------->onCreate()--->onStart().当stopService的时候Service端就会调用onDestroy()方法来Destroy

stopService()----------------->onDestory()--->Service stop

如果是调用者自己直接退出而没有调用stopService的话,Service会一直在后台运行。下次调用者再起来可以stopService

二)context.bindService()启动方式


模型说明:
client端 Service端
bindService()------------------->onCreate()-->onBind()-->Service running
onUnBind()---------------------->onDestroy()-->Service stop

函数原型:

public boolean bindService (Intent service, ServiceConnection conn, int flags)

参数说明:intent:就是client端定义的参数,和startService中的intent作用一样
conn是一个ServiceConnection实例,ServiceConnection是一个public interface,一个用于监视app service状态的Interface,必须去实现ServiceConnection的两个抽象函数。
onServiceConnected(ComponentName name, IBinder service)和onServiceDisconnected(ComponentName name)这两个函数就是决定
链接和断开Service。
flags:绑定操作选项
返回值:绑定成功返回true,失败false。

public abstractIBinderonBind (Intent intent) 

intent:该参数由bindService()函数传递过来,用于绑定指定的Service。
所以调用bindService的生命周期为:onCreate --> onBind(只一次,不可多次绑定) --> onUnbind --> onDestory。
在Service每一次的开启关闭过程中,只有onStart可被多次调用(通过多次startService调用),其他onCreate,onBind,onUnbind,
onDestory在一个生命周期中只能被调用一次。

绑定一个运行的Service
可以创建一个即是运行的又是绑定的service,也就是通过startService()来让这个Service运行,然后client通过bindService()来绑定到Service。那么当service启动后,系统不会在所有客户端解除绑定unBindService()后销毁service,而是,你必须通过调用stopSelf()stopService()显示地停止这个service。
多个client可以同时连接到一个service,当地一个Client绑定到Service的时候Service的onBinder()方法才接受到IBinder,之后Service将把同一个IBinder传递给其他的client,之后其他Client的连接service时service就不再会调用自身的onBind()方法了。
当最后一个client取消绑定到Service时,系统就会销毁这个Service

进程间通信

每一个进程都有自己独立的内存,一个进程的1000地址也许是另一个进程的100000地址,所以如何在进程间通信就变得复杂了,对于基本类型的数据比如整数直接传递过去就行了,但是要是字符串的话就要先目的进程空间中分配一快能容纳该字符串内存,然后在复制到内存中。再如果要传递一个类的实例,也是先为类分配内存,然后复制一份再传递可以吗?我认为不可以,我至少可以找到一个理由:类中成员除了属性还有方法,即使属性能完整传过去,但还有方法呢?方法是独立于类对象存在的,所以到另一个进程中再引用同一个方法就要出错了,还是因为独立地址空间的原因。Android中通常要在Activity之间专递数据,而根据Android的设计,即使同一个Activity都不一定运行在同一个进程中,所以就要进程间传递数据了,那么到底如何在进程之间传递类对象呢?简单来说可以这样做:在进程A中把类中的非默认值的属性和类的唯一标志打成包(这就叫序列化),把这个包传递到进程B,进程B接收到包后,跟据类的唯一标志把类创建出来,然后把传来的属性更新到类对象中,这样进程A和进程B中就包含了两个完全一样的类对象。

android 中的Parcel是一个容器,主要作用于存储序列化数据,然后通过Binder在进程间传递这些数据。Parcel可以包含原始数据类型(用各种对应的方法写入,比如writeInt(),writeFloat()等),可以包含Parcelable对象,它还包含了一个活动的IBinder对象的引用,这个引用导致另一端接收到一个指向这个IBinder的代理IBinder。
注:Parcel不是一般目的的序列化机制。这个类被设计用于高性能的IPC传输。因此不适合把Parcel写入永久化存储中,因为Parcel中的数据类型的实现的改变会导致旧版的数据不可读。

Bundles类
Bundles是一种类型安全的Map容器,可用于存储任何不同类型的数据。它具有很多对讀写数据的性能优化,并且它的类型安全机制避免了当把它的数据封送到Parcel中时由于类型错误引起的BUG的调试的麻烦,可以使用的方法为: writeBundle(Bundle), readBundle(), and readBundle(ClassLoader)。

活动对象

Parcel的一个非同寻常的特性是读写活对象的能力。对于活动对象,它们的内容实际上并没有写入,而是仅写入了一个令牌来引用这个对象。当从Parcel中读取这个对象时,你不会获取一个新的对象实例,而是直接得到那个写入的对象。有两种活动对象可操作:

Binder对象

它是Android跨进程通讯的基础。这种对象可被写入Parcel,并在读取时你将得到原始的对象或一个代理对象(可以想象:在进程内时得到原始的对象,在进程间时得到代理对象)。可以使用的方法们是: writeStrongBinder(IBinder), writeStrongInterface(IInterface), readStrongBinder(), writeBinderArray(IBinder[]), readBinderArray(IBinder[]), createBinderArray(), writeBinderList(List), readBinderList(List), createBinderArrayList()。

FileDescriptor对象

它代表了原始的Linux文件描述符,它可以被写入Parcel并在读取时返回一个ParcelFileDescriptor对象用于操作原始的文件描述符。ParcelFileDescriptor是原始描述符的一个复制:对象和fd不同,但是都操作于同一文件流,使用同一个文件位置指针,等等。可以使用的方法是:writeFileDescriptor(FileDescriptor), readFileDescriptor()。

无类型容器类

一类final方法,用于读写标准的java容器类。这些方法们是:writeArray(Object[]), readArray(ClassLoader), writeList(List), readList(List, ClassLoader), readArrayList(ClassLoader), writeMap(Map), readMap(Map, ClassLoader), writeSparseArray(SparseArray), readSparseArray(ClassLoader)。

IBinder

Android的远程调用就是通过IBinder来实现的,远程调用的基本接口,还可以用于进程内调用,该接口定义了与远程对象交互的协议,不可以直接实现这个接口,要派生实现。

主要API:

transact():向远端的IBinder对象发送发出调用。onTransact():是远程的对象能够响应另一端发送过来的调用请求。这两个API都是同步执行的。transact()方法要一直阻塞到onTransact()方法调用完成后才返回。

transact()发送的数据类型是Pracel类型的数据,是一种一般的缓冲区,除了有数据外还有一些描述他内容的元数据。元数据用于管理IBinder对象的引用,这样就能在缓冲区从一个进程移动到另一个进程时保存这些引用。这样就保证了当一个IBinder被写入到Parcel并发送到另一个进程中,如果另一个进程把同一个IBinder的引用回发到原来的进程,那么这个原来的进程就能接收到发出的那个IBinder的引用。这种机制使IBinder和Binder像唯一标志符那样在进程间管理。


系统为每个进程维护一个存放交互线程的线程池。这些交互线程用于派送所有从另外进程发来的IPC调用。例如:当一个IPC从进程A发到进程B,A中那个发出调用的线程(这个应该不在线程池中)就阻塞在transact()中了。进程B中的交互线程池中的一个线程接收了这个调用,它调用Binder.onTransact(),完成后用一个Parcel来做为结果返回。然后进程A中的那个等待的线程在收到返回的Parcel后得以继续执行。实际上,另一个进程看起来就像是当前进程的一个线程,但不是当前进程创建的。

Binder机制还支持进程间的递归调用。例如,进程A执行自己的IBinder的transact()调用进程B的Binder,而进程B在其Binder.onTransact()中又用transact()向进程A发起调用,那么进程A在等待它发出的调用返回的同时,还会用Binder.onTransact()响应进程B的transact()。总之Binder造成的结果就是让我们感觉到跨进程的调用与进程内的调用没什么区别。

当操作远程对象时,你经常需要查看它们是否有效,有三种方法可以使用: 
1 transact()方法将在IBinder所在的进程不存在时抛出RemoteException异常。 
2 如果目标进程不存在,那么调用pingBinder()时返回false。 
3 可以用linkToDeath()方法向IBinder注册一个IBinder.DeathRecipient,在IBinder代表的进程退出时被调用。

用法:

要实现IBinder来支持远程调用,应从Binder类派生一个类。Binder实现了IBinder接口。但是一般不需要直接实现此类,而是跟据你的需要由开发包中的工具生成,这个工具叫aidi。你通过aidi语言定义远程对象的方法,然后用aidi工具生成Binder的派生类,然后就可使用之。当然,你也可以直接从Binder类派生以实现自定义的RPC调用,或只是实例化一个原始的Binder对象直接作为进程间共享的令牌来使用。


Remote Service

通常每个应用程序都在它自己的进程内运行,但有时需要在进程间传递对象,你可以通过应用程序UI的方式写个运行在一个不同的进程中的service。在android平台中,一个进程通常不能访问其他进程中的内存区域。所以,他们需要把对象拆分成操作系统能理解的简单形式,以便伪装成对象跨越边界访问。
AIDL(Android接口定义语言)执行把对象分解为操作系统能够理解并能跨进程封送的基本体以执行IPC的所有的工作
过程:
1,创建xx.aidl接口文件,然后在gen目录下生成一个对应XX.java实现类这是AIDL工具自己生成的文件不可编辑。在该文件中
定义的接口继承自IInterface来实现,在实现的内部有一个内部类Stub。类中实现了定义的方法。
2,实现你在xx.aidl文件中定义的方法,实现类继承自AIDL工具自动生成的实现文件中定义的Stub内部类。
public class xxImpl extends XX.Stub {
实现定义的方法
}
总结:在远程调用的过程中是在不同主机(或则相同)中不同进程间进行通信,所以就产生了通信的载体,通信的数据,但是在各个平台或则进程中数据的表达形式不一定是统一的,所以这就要求我们必须定义一种特殊的通信载体数据,让通信双方都能统一的解析。在Android中这里的AIDL就是通信的载体,而Stub类就是将这个载体解析成通信双方都能读懂的数据格式的一个功能类。所以也就解释了为什么在自己实现aidl文件的时候一定要继承自AIDL自动生成的实现类的内部类Stub类了。
如果知道RPC的分布式编程知识就能很好的理解为什么要定义一个特殊的接口,实现的时候要继承自Stub类了。

Messenger实现远程进程通信:
使用方法:
  • service实现一个接收从客户端的每个调用引起的回调的Handler

  • Handler被用来创建一个Messenger对象(它是Handler的一个引用)

  • Messenger创建一个从serviceonBind()返回给客户端的IBinder

  • 客户端使用IBinder来实例化这个Messenger(它引用到serviceHandler),客户端用它来service发送Message

  • service在它的Handler中接收每个消息—具体的,是在handleMessage()方法中.

这此方式下,service中没有能让客户端调用的方法,客户端传送的是service在它的Handler中接收的"消息"(Message对象)

应用组件(客户端)可以调用bindService()绑定到一个serviceAndroid系统之后调用serviceonBind()方法,它返回一个用来与service交互的IBinder

绑定是异步的.bindService()会立即返回,它不会返回IBinder给客户端.要接收IBinder,客户端必须创建一个ServiceConnection的实例并传给bindService()ServiceConnection包含一个回调方法,系统调用这个方法来传递要返回的IBinder

注:只有activities,services,contentproviders可以绑定到一个service—你不能从一个broadcastreceiver绑定到service


原创粉丝点击