Android IPC机制(二) IPC基础介绍
主要包含三方面内容:Serialzable接口,Parcelable接口以及Binder,只有熟悉这三方面的内容后,我们才能更好的理解跨进程通信的各种方式,Serializable和Parcelable接口可以完成对象的序列化过程,当我们需要通过Intent和Binder传输数据时就需要使用Parcelable和Serializable,还有的时候我们需要把对象持久化到储存设备上或者通过网络传输给其他的客户端,这个时候也需要使用Serializable来完成对象的持久化。
Serializable接口
Serializable是Java所提供的一个序列化接口,它是一个空接口,对对象提供标准的序列号和反序列化操作。使用Serializable来实现序列化相当简单,只需要在类的声明中指定一个类似下面的标识·即可自动实现默认的序列化过程
在Android 中也提供了新的序列号方式,就是Parcelable接口,使用Parcelable来实现对象的序列号,其过程要稍微复杂一些,想让一个对象实现序列化,只需要这个类实现Serializable接口并声明一个serialVersionUID即可,甚至这个serialVersionUID也不是必须的,我们不声明这个UID同样也可以实现序列号,但是这将会对反序列化过程产生影响,User类就是一个实现了Serialiable接口的类,它是可以被序列号和反序列化的
通过Serializable方式来实现对象的序列化,实现起来很简单,几乎所有的工作都被系统自动完成了,如何进程对象的序列化和反序列化也非常简单,只需要采用ObjectOutputStream和ObjecrInputStream即可实现
只需要把实现了Serializable接口的User对象写到文件中就可以快速恢复了,恢复后的对象newUser和user的内容完全一样,但是两者并不是同一个对象
刚开始提到,即使不指定serialVersionUID也可以实现序列化,到底要不要制定呢,如果指定的话,serialVersionUID后面那一长串数字又是什么含义呢,我们要明白,系统既然提供了这个serialVersionUID,那么它必须是有用的,这个serialVersionUID是用来辅助序列化和反序列化过程的,原则上序列化后的数据中的serialVersionUID只有和当前类的serialVersionUID相同才能正常的被反序列化。serialVersionUID的详细工作机制是这样的:序列化的时候系统会把当前类的serialVersionUID写入序列化的文件中,当反序列化的时候系统会去检测文件中的serialVersionUID,看它是否和当前类的serialVersionUID一致,如果一致就说明序列化的类的版本和当前类的版本是相同的,这个时候可以成功反序列化,否则就说明当前类和序列化的类相比发生了某些变换,这个时候是无法正常反序列化的
一般来说,我们应该手动指定serialVersionUID的值,比如1L,也可以让Eclipse根据当前类的结构自定去生成它的hash值,这个时候序列化和反序列化时两者的serialVersionUID是相同的,因此可以正常的反序列化,如果不手动只当serialVersionUID的值,反序列化时类有所改变,那么系统就会重新计算当前类的hash值并把它赋值给serialVersionUID,这个时候当前列的UID和序列化的不一致,于是反序列化失败,程序就会出现crash,
静态成员遍历属于类不属于对象,所以不会参与序列号过程,其次用transient关键字标识的成员遍历不参与序列化过程。
系统默认序列化过程也是可以改变的,通过实现如下两个方法即可重写系统的序列化和反系列化过程,大部分情况下我们需要重写这两个方法
Parcelable接口
Percelable也是一个接口,只要实现这个接口,一个类的对象就可以实现序列化并可以通过Intent和BInder传递
Parcel内部包装了可序列化的数据,可以在Binder中自由传输,从上述代码中可以看出,在序列化过程中需要实现的功能有序列化,反序列化和内容描述。序列化功能由writeToParcel来完成,最终是通过Parcel中的一系列write方法来完成的,但序列化功能由CREATOR来完成,其内部标明了如何创建序列化对象和数组,并提供Parcel的一系列read方法来完成反序列化过程:内容描述功能由describeContents方法来完成,几乎在所有情况下这个方法都应该返回0,仅当当前对象中存在文件描述时,此方法返回1 ,需要注意的是,在User方法中,由于book是另一个可序列化对象,所以它的反序列化过程需要传递当前线程的上下文类加载器。否则会报无法找到类的错误
系统为我们提供了许多实现了Parcelable接口的方法,他们都是可以直接序列化的,比如Intent,Bundle,Bitmap等,同时List和Map也可以序列化,前提是他们里面的每个元素都是可序列化的
既然Parcelable和Serializable都能实现序列化并且可用于Intent间的数据传递,那么二者该如何选取呢?Serializable是Java中的序列化接口,其使用起来简单但是开销很大,序列化和反序列化过程中需要大量I/O操作,而Parcelable 是Android 中的序列化方式,因此更适合用在Android平台上,它的缺点就是使用起来稍微麻烦点,但是它的销量很高,这是Android 推荐的序列化方式,因此我们首选Parcelable。Parcelable主要用在内存序列化上,通过Parcelable将对象序列化到存储设备中或者将对象序列化后通过网络传输也都是可以的,但是这个过程会稍显复杂,因此在这两种情况下建议使用Serializable。
Binder
Binder是Android中的一个类,它实现了IBinder接口,从IPC角度来说,Binder是Android 中的一种跨进程通信方式,Binder还可以理解为一种虚拟的物理设备,它的设备驱动式dev/binder,该通信方式在Linux中没有,从Android Framework角度来说,Binder是ServiceManager连接各种Manager(ActivityManager,WindowManager等)和相应ManagerService的桥梁,从Android 应用层来说,Binder是客户端和服务端进行通信的媒介,当bindService的时候,服务端会返回一个包含了服务端业务调用的Binder对象,通过这个Binder对象,客户端就可以获取服务端提供的服务或者数据,这里的服务包括普通服务和基于AIDL的服务
Android开发中,Binder主要用于Service中,包括AIDL和Messenger,其中普通Service中的Binder不涉及进程间通信,所以较为简单,无法触及Binder的核心,而Messenger的底层其实是AIDL,所以这个选择用AIDL来分析Binder的工作机制,为了分析Binder的工作机制,我们需要新建一个AIDL示例,SDK会自动为我们生产AIDL所对应的Binder类,然后我们就可以分析Binder的工作过程
Book.java是一个表示图书信息的类,它实现了Parcelable接口。Book.aidl是Book类在AIDL种的声明。IBookManager.aidl是我们定义的一个接口,里面有两个方法:getBookList和addBook,其中getBookList用于从远程服务端获取图书列表,而addBook用于往图书列表种添加一本书,当然这两个方法主要是示例用,不一定有实际意思,我们可以看到,尽管Book类已经和IBookManager位于相同的包中,但是在IBookManager.aidl中仍然要导入Boo类,这就是AIDL的特殊之处
在gen目录下,可以看到根据IBookManager.adil系统为我们生成了IBookManager.java这个类,它继承了IInterface这个接口,同时它自己也还是个接口,所有可以在Binder中传输的接口都需要继承IInterface接口。通过它我们可以看清楚了了解到Binder的工作机制
DESCRIPTOR
Binder的唯一标识,一般用当前Binder的类命表示,例如com.example.bling.myapplication.IBookManager
asInterface(android.os.IBinder obj)
用于将服务端的Binder对象转换为客户端所需的AIDL接口类型的对象,这种转换过程是区分进程的,如果客户端和服务端位于统一进程,那么此方法返回的就是服务端的Stub对象本身,否则返回的是系统封装后的Stub.proxy对象
asBinder
此方法用于返回当前Binder对象
onTransact
这个方法运行在服务端中的Binder线程池中,当客户端发起跨进程请求时,远程请求会通过系统底层封装后交由此方法来处理,服务端通过code可以确定客户端所请求的目标方法是什么,接着从data中取出目标方法所需的参数,然后执行目标方法,当目标方法执行完毕后,就向reply中写入返回值,onTransact方法的执行过程就是这样的,需要注意的是,如果此方法返回false,那么客户端的请求会失败,因此我们可以利用这个特性来做权限验证,毕竟我们也不希望随便一个进程都能远程调用我们的服务
Proxy#getBookList
这个方法运行在客户端,当客户端远程调用此方法时,它的内部实现是这样的:首先创建该方法所需要的输入型Parcel对象_data,输出型Parcel对象_relpy和返回值对象List;然后把该方法的参数信息写入_data中,接着调用transact方法来发起RPC请求,同时当前线程挂起,然后服务端的onTransact方法会被调用,直到RPC过程返回后,当前线程继续执行,并从relpy中取出RPC过程的返回结构
Proxy#addBook
这个方法运行在客户端,它的执行过程和getBookList是一样的,addBook没有返回值,所以它不需要从_reply中取出返回值
当客户端发起请求时,由于当前线程会被挂起直至服务端进程返回数据,所以如果一个远程方法是耗时的,那么不能在UI线程中发起远程请求,其次,由于服务端的Binder方法运行在Binder的线程池中,所以Binder方法不管是否耗时都应该采用同步的方法去实现,因为它已经运行在一个线程中了
我们完全可以不提供AIDL文件即可实现Binder,之所以提供AIDL文件,是为了方便系统为我们生成代码,系统根据AIDL文件生成java文件的格式是固定的,我们可以抛开AIDL文件直接写一个Binde出来