《Android开发艺术探索》读书笔记 (2) 第2章 IPC机制

2.1 Android IPC简介

(1)任何一个操做系统都须要有相应的IPC机制,Linux上能够经过命名通道、共享内存、信号量等来进行进程间通讯。Android系统不只可使用了Binder机制来实现IPC,还可使用Socket实现任意两个终端之间的通讯。java

2.2 Android中的多进程模式

(1)经过给四大组件指定android:process属性就能够开启多进程模式,默认进程的进程名是包名packageName,进程名以:开头的进程属于当前应用的私有进程,其余应用的组件不能够和它跑在同一个进程中,而进程名不以:开头的进程属于全局进程,其余应用经过ShareUID方法能够和它跑在同一个进程中。android

android:process=":xyz" //进程名是 packageName:xyz
android:process="aaa.bbb.ccc" //进程名是 aaa.bbb.ccc

 

(2)Android系统会为每一个应用分配一个惟一的UID,具备相同UID的应用才能共享数据。两个应用经过ShareUID跑在同一个进程中是有要求的,须要这两个应用有相同的ShareUID而且签名相同才能够。 在这种状况下,它们能够相互访问对方的私有数据,好比data目录、组件信息等,无论它们是否跑在同一个进程中。若是它们跑在同一个进程中,还能够共享内存数据,它们看起来就像是一个应用的两个部分。
(3)android系统会为每一个进程分配一个独立的虚拟机,不一样的虚拟机在内存分配上有不一样的地址空间,因此不一样的虚拟机中访问同一个类的对象会产生多个副本。
(4)使用多线程容易形成如下几个问题:
1.静态成员和单例模式彻底失效;
2.线程同步机制彻底失效:不管锁对象仍是锁全局对象都没法保证线程同步;
3.SharedPreferences的可靠性降低:SharedPreferences不支持并发读写;
4.Application会屡次建立:当一个组件跑在一个新的进程的时候,系统要在建立新的进程的同时分配独立的虚拟机,应用会从新启动一次,也就会建立新的Application。运行在同一个进程中的组件是属于同一个虚拟机和同一个Application。
同一个应用的不一样组件,若是它们运行在不一样进程中,那么和它们分别属于两个应用没有本质区别。git

2.3 IPC基础概念介绍

(1)Serializable接口是Java中为对象提供标准的序列化和反序列化操做的接口,而Parcelable接口是Android提供的序列化方式的接口。
(2)serialVersionUId是一串long型数字,主要是用来辅助序列化和反序列化的,原则上序列化后的数据中的serialVersionUId只有和当前类的serialVersionUId相同才可以正常地被反序列化。
serialVersionUId的详细工做机制:序列化的时候系统会把当前类的serialVersionUId写入序列化的文件中,当反序列化的时候系统会去检测文件中的serialVersionUId,看它是否和当前类的serialVersionUId一致,若是一致就说明序列化的类的版本和当前类的版本是相同的,这个时候能够成功反序列化;不然说明版本不一致没法正常反序列化。通常来讲,咱们应该手动指定serialVersionUId的值。
1.静态成员变量属于类不属于对象,因此不参与序列化过程;
2.声明为transient的成员变量不参与序列化过程。
(3)Parcelable接口内部包装了可序列化的数据,能够在Binder中自由传输,Parcelable主要用在内存序列化上,能够直接序列化的有Intent、Bundle、Bitmap以及List和Map等等,下面是一个实现了Parcelable接口的示例github

public class Book implements Parcelable {
public int bookId;
public String bookName;
public Book() {
}

public Book(int bookId, String bookName) {
this.bookId = bookId;
this.bookName = bookName;
}

//“内容描述”,若是含有文件描述符返回1,不然返回0,几乎全部状况下都是返回0
public int describeContents() {
return 0;
}

//实现序列化操做,flags标识只有0和1,1表示标识当前对象须要做为返回值返回,不能当即释放资源,几乎全部状况都为0
public void writeToParcel(Parcel out, int flags) {
out.writeInt(bookId);
out.writeString(bookName);
}

//实现反序列化操做
public static final Parcelable.Creator<Book> CREATOR = new Parcelable.Creator<Book>() {
//从序列化后的对象中建立原始对象
public Book createFromParcel(Parcel in) {
return new Book(in);
}
public Book[] newArray(int size) {//建立指定长度的原始对象数组
return new Book[size];
}
};

private Book(Parcel in) {
bookId = in.readInt();
bookName = in.readString();
}

}

 

(4)Binder是Android中的一个类,它实现了IBinder接口。从IPC角度看,Binder是Android中一种跨进程通讯的方式;Binder还能够理解为虚拟的物理设备,它的设备驱动是/dev/binder;从Framework层角度看,Binder是ServiceManager链接各类Manager和相应的ManagerService的桥梁;从Android应用层来讲,Binder是客户端和服务端进行通讯的媒介,当bindService的时候,服务端会返回一个包含了服务端业务调用的Binder对象,经过这个Binder对象,客户端就能够获取服务端提供的服务或者数据,这里的服务包括普通服务和基于AIDL的服务。
在Android开发中,Binder主要用在Service中,包括AIDL和Messenger,其中普通Service中的Binder不涉及进程间通讯,较为简单;而Messenger的底层实际上是AIDL,正是Binder的核心工做机制。
(5)aidl工具根据aidl文件自动生成的java接口的解析:首先,它声明了几个接口方法,同时还声明了几个整型的id用于标识这些方法,id用于标识在transact过程当中客户端所请求的究竟是哪一个方法;接着,它声明了一个内部类Stub,这个Stub就是一个Binder类,当客户端和服务端都位于同一个进程时,方法调用不会走跨进程的transact过程,而当二者位于不一样进程时,方法调用须要走transact过程,这个逻辑由Stub内部的代理类Proxy来完成。
因此,这个接口的核心就是它的内部类Stub和Stub内部的代理类Proxy。 下面分析其中的方法:
1.asInterface(android.os.IBinder obj):用于将服务端的Binder对象转换成客户端所需的AIDL接口类型的对象,这种转换过程是区分进程的,若是客户端和服务端是在同一个进程中,那么这个方法返回的是服务端的Stub对象自己,不然返回的是系统封装的Stub.Proxy对象。
2.asBinder:返回当前Binder对象。
3.onTransact:这个方法运行在服务端中的Binder线程池中,当客户端发起跨进程请求时,远程请求会经过系统底层封装后交由此方法来处理。
这个方法的原型是public Boolean onTransact(int code, Parcelable data, Parcelable reply, int flags)
服务端经过code能够知道客户端请求的目标方法,接着从data中取出所需的参数,而后执行目标方法,执行完毕以后,将结果写入到reply中。若是此方法返回false,说明客户端的请求失败,利用这个特性能够作权限验证(即验证是否有权限调用该服务)。
4.Proxy#[Method]:代理类中的接口方法,这些方法运行在客户端,当客户端远程调用此方法时,它的内部实现是:首先建立该方法所须要的参数,而后把方法的参数信息写入到_data中,接着调用transact方法来发起RPC请求,同时当前线程挂起;而后服务端的onTransact方法会被调用,直到RPC过程返回后,当前线程继续执行,并从_reply中取出RPC过程的返回结果,最后返回_reply中的数据。数组

若是搞清楚了自动生成的接口文件的结构和做用以后,实际上是能够不用经过AIDL而直接实现Binder的,主席写的示例代码缓存

(6)Binder的两个重要方法linkToDeathunlinkToDeath
Binder运行在服务端,若是因为某种缘由服务端异常终止了的话会致使客户端的远程调用失败,因此Binder提供了两个配对的方法linkToDeathunlinkToDeath,经过linkToDeath方法能够给Binder设置一个死亡代理,当Binder死亡的时候客户端就会收到通知,而后就能够从新发起链接请求从而恢复链接了。
如何给Binder设置死亡代理呢?
1.声明一个DeathRecipient对象,DeathRecipient是一个接口,其内部只有一个方法bindeDied,实现这个方法就能够在Binder死亡的时候收到通知了。网络

private IBinder.DeathRecipient mDeathRecipient = new IBinder.DeathRecipient() {
@Override
public void binderDied() {
if (mRemoteBookManager == null) return;
mRemoteBookManager.asBinder().unlinkToDeath(mDeathRecipient, 0);
mRemoteBookManager = null;
// TODO:这里从新绑定远程Service
}
};

 

2.在客户端绑定远程服务成功以后,给binder设置死亡代理多线程

mRemoteBookManager.asBinder().linkToDeath(mDeathRecipient, 0);

 

2.4 Android中的IPC方式

(1)使用Bundle:Bundle实现了Parcelable接口,Activity、Service和Receiver都支持在Intent中传递Bundle数据。并发

(2)使用文件共享:这种方式简单,适合在对数据同步要求不高的进程之间进行通讯,而且要妥善处理并发读写的问题。
SharedPreferences是一个特例,虽然它也是文件的一种,可是因为系统对它的读写有必定的缓存策略,即在内存中会有一份SharedPreferences文件的缓存,所以在多进程模式下,系统对它的读写就变得不可靠,当面对高并发读写访问的时候,有很大概率会丢失数据,所以,不建议在进程间通讯中使用SharedPreferences。ide

(3)使用Messenger:Messenger是一种轻量级的IPC方案,它的底层实现就是AIDL。Messenger是以串行的方式处理请求的,即服务端只能一个个处理,不存在并发执行的情形,详细的示例见原书。

(4)使用AIDL
大体流程:首先建一个Service和一个AIDL接口,接着建立一个类继承自AIDL接口中的Stub类并实现Stub类中的抽象方法,在Service的onBind方法中返回这个类的对象,而后客户端就能够绑定服务端Service,创建链接后就能够访问远程服务端的方法了。
1.AIDL支持的数据类型:基本数据类型、StringCharSequenceArrayListHashMapParcelable以及AIDL
2.某些类即便和AIDL文件在同一个包中也要显式import进来;
3.AIDL中除了基本数据类,其余类型的参数都要标上方向:inout或者inout
4.AIDL接口中支持方法,不支持声明静态变量;
5.为了方便AIDL的开发,建议把全部和AIDL相关的类和文件所有放入同一个包中,这样作的好处是,当客户端是另外一个应用的时候,能够直接把整个包复制到客户端工程中。
6.RemoteCallbackList是系统专门提供的用于删除跨进程Listener的接口。RemoteCallbackList是一个泛型,支持管理任意的AIDL接口,由于全部的AIDL接口都继承自IInterface接口。

(5)使用ContentProvider
1.ContentProvider主要以表格的形式来组织数据,而且能够包含多个表;
2.ContentProvider还支持文件数据,好比图片、视频等,系统提供的MediaStore就是文件类型的ContentProvider;
3.ContentProvider对底层的数据存储方式没有任何要求,能够是SQLite、文件,甚至是内存中的一个对象都行;
4.要观察ContentProvider中的数据变化状况,能够经过ContentResolverregisterContentObserver方法来注册观察者;

(6)使用Socket
Socket是网络通讯中“套接字”的概念,分为流式套接字和用户数据包套接字两种,分别对应网络的传输控制层的TCP和UDP协议。

2.5 Binder链接池

(1)当项目规模很大的时候,建立不少个Service是不对的作法,由于service是系统资源,太多的service会使得应用看起来很重,因此最好是将全部的AIDL放在同一个Service中去管理。整个工做机制是:每一个业务模块建立本身的AIDL接口并实现此接口,这个时候不一样业务模块之间是不能有耦合的,全部实现细节咱们要单独开来,而后向服务端提供本身的惟一标识和其对应的Binder对象;对于服务端来讲,只须要一个Service,服务端提供一个queryBinder接口,这个接口可以根据业务模块的特征来返回相应的Binder对象给它们,不一样的业务模块拿到所需的Binder对象后就能够进行远程方法调用了。
Binder链接池的主要做用就是将每一个业务模块的Binder请求统一转发到远程Service去执行,从而避免了重复建立Service的过程。
(2)做者实现的Binder链接池BinderPool的实现源码,建议在AIDL开发工做中引入BinderPool机制。

2.6 选用合适的IPC方式

img

OK,本章结束,谢谢阅读。

相关文章
相关标签/搜索