第二章 IPC机制

来源:互联网 发布:java面向对象关键字 编辑:程序博客网 时间:2024/04/28 22:42

2.1 Android IPC简介

IPC是Inter-Process Communication的缩写,含义为进程间通信或者跨进程通信,是指两个进城之间进行数据交换的过程。
进程和线程的区别:线程是CPU调度的最小单元,同时线程是一种有限的系统资源。而进程一般是指一个执行单元,在PC和移动设备上指一个程序或者一个应用。
Android中多进程的两种情况:第一种情况是一个应用因为某些原因自身需要采用多进程模式来实现,例如需要更多的内存,webview崩溃不能影响主应用。另外一种情况是当前应用需要向其他应用获取数据。

2.2 Android中的多进程模式

  1. 在Android中使用多进程只有一种方法,那就是给四大组件在AndroidMenifest中指定android:process属性。ps:还有一种特殊的办法是,通过JNI在native层去fork一个新的进程
  2. 进程名以“:”开头的进程属于当前应用的私有进程,其他应用的组件不可以和它跑在同一个进程中,而进程名不以“:”开头的进程属于全局进程,其他应用通过ShareUID方式可以和它跑在同一个进程中。
  3. Android系统回味每个应用非配一个唯一的UID,具有相同的UID的应用才能共享数据。只有两个应用有相同的ShareUID并且签名相同才可以通过ShareUID跑在同一个进程中。在这种情况下,它们可以互相访问对方的私有数据,比如data目录、组件信息。
  4. 每个进程都分配一个独立的虚拟机,不同的虚拟机在内存分配上有不同的地址空间,这就导致在不同的虚拟机中访问同一个类的对象会产生多份副本。
  5. 使用多进程会造成如下几方面的问题:
    • 静态成员和单例模式完全失效
    • 线程同步机制完全失效
    • SharedPreferences的可靠性下降
    • Application会多次创建

2.3 IPC基础概念介绍

(1). Serializable接口时Java中为对象提供标准的序列化和反序列化操作的接口,而Parcelable接口时Android提供的序列化方式接口。
(2). serialVersionUID是一串long型数字,主要是用来辅助序列化和反序列化的,原则上序列化后的数据中的serialVersionUID只有和当前类的serialVersionUID相同才能够正常地被反序列化。
(3). serialVersionUID的详细工作机制:序列化的时候系统会把当前类的serialVersionUID写入序列化的文件中(也可能是其他的中介),当反序列化的时候系统会检测文件中的serialVersionUID,看它是否和当前的serialVersionUID一致,如果一致,则可以正常反序列化,否则不可以反序列化。一般来说,我们应该手动指定serialVersionUID的值这里我们需要注意两点,第一,静态成员变量属于类不属于对象,所以不参与序列化过程。第二,声明为transient的成员变量不参与序列化的过程
(4). Parcelable接口内部包装了可序列化的数据,可以再Binder中自由传输,Parcelable主要用在内存序列化上,可以直接序列化的由Intent、Bundle、Bitmap以及List和Map等等,下面是一个实现了Parcelable接口的示例

 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();    }}

(5). Binder是Android中的一个类,它实现了IBinder接口。从IPC角度看,Binder是Android中一种跨进程通信的方式;Binder还可以理解为一种虚拟的物理设备,它的设备驱动是/dev/binder;从Framework角度来说,Binder是ServiceManager连接各种Manager(ActivityManager、WindowManager,等等)和相应ManagerService的桥梁;从Android应用层来说,Binder是客户端和服务端进行通信的媒介,当bindService的时候,服务端会返回一个包含了服务端业务调用的Binder对象,通过这个Binder对象,客户端就可以获取服务端提供的服务或者数据,这里的服务包括普通的服务和基于AIDL的服务。
在Android开发中,Binder主要用在Service中,包括AIDL和Mesaenger,其中普通Service中的BInder不涉及进程通信,较为简单;而Messenger的底层其实是AIDL,正是Binder的核心工作机制。
(6). aidl工具根据aidl文件自动生成java接口的解析:首先,它声明了几个接口方法,同时还声明了几个整形的id用于标识这些方法,id用于标识在transact过程中客户端所请求的到底是哪个方法;接着,它声明了一个内部类Stub,这个Stub就是一个Binder类,当前客户端和服务端都位于同一个进程时,方法调用不会走跨进程的transact过程,而当两者位于不同进程时,方法调用需要走transact过程,这个逻辑由Stub内部代理类Proxy来完成。
所以,这个借口的核心就是它的内部类Stub和Stub内部的代理类Proxy下面分析其中的方法:

  • asInterface(android.os.Ibinder obj):用于将服务端的Binder对象转换成客户端所需要的AIDL借口类型的对象,这种转换过程是区分进程的,如果客户端和服务端是在同一个进程中,那么这个方法返回的服务端Stub对象本身,否则返回的是系统封装的Stub.Proxy对象
  • asBinder:返回当前Binder对象。
  • onTransact:这个方法运行在服务端中的Binder线程池中,当客户端发起跨进程请求时,远程请求会通过系统底层封装后交由此方法来处理。这个方法的原型是public Boolean onTracsact(int code, Parcelable data, Parcelable reply, int flags)服务端通过code可以知道客户端请求的目标方法,接着从data中取出所需要的参数,然后执行目标方法,接着从data中取出所需要的参数,然后执行目标方法,执行完毕之后,将结果写入到reply中。如果此方法返回false,说明客户端的请求失败,利用这个特性可以做权限验证(即验证是否有权限调用该服务)。
  • Proxy#[Method]:代理类中的接口方法,这些方法运行在客户端,当客户端远程调用此方法时,它的内部实现是:首先创建该方法所需要的参数,然后把方法的参数信息写入到 _ data中,接着调用transact方法来发起RPC(远程过程调用)请求,同时当前线程挂起;然后服务端的onTransact方法会被调用,知道RPC过程返回后,当前线程继续执行,并从 _ reply中取出RPC过程的返回结果,最后返回 _ reply 中的数据。
    这里需要注意两点:首先当客户端发起远程请求时,由于当前线程会被挂起直至服务端进程返回数据,所以如果一个远程方法是很耗时的,那么不能再UI县城中发起此远程请求;其次,由于服务端Binder方法运行在BInder的线程中,所以BInder方法不管是否耗时都应该采用同步的方法去实现,因为它已经运行在一个线程中了。

(7). Binder的两个重要方法linkToDeathunlinkToDeath
Binder运行在服务端,如果由于某种原因服务端异常终止了的话会导致客户端的远程调用失败,所以Binder提供了两个配对的方法linkToDeathunlinkToDeath,通过linkToDeath方法可以给Binder设置一个死亡代理,当Binder死亡的时候客户端就会受到通知,然后就可以重新发起连接请求从而恢复连接。
给Binder设置死亡代理:
1. 声明一个DeathRecipient对象,DeathRecipient是一个接口,其内部只有一个方法binderDied,实现方法就可以在Binder死亡的时候收到通知了。

private Ibinder.DeathRecipient mDeathRecipient = new IBinder.DeathRecipient(){        @Override        public void binderDied() {                if (mRemoteBookManager == null) return;                mRemoteBookManager.adBinder().unlinkToDeath(mDeathRecipient, 0);                mRemoteBookManager = null;                // TODO:这里是重新绑定远程Service        }}
  1. 在客户端绑定远程服务成功后,给binder设置死亡代理
mRemoteBookManager.asBinder().linkToDeath(mDeathRecipient, 0);

2.4 Android中的IPC方式

(1). 使用Bundle:Bundle实现了Parcelable接口,Activity、Service和Receiver都支持在Intent中传递Bundle数据。
(2). 使用文件共享:这种方式简单,适合在对数据同步要求不高的进程间进行通信,并且要妥善处理并发读写的问题。SharedPreferences是一个特例,虽然它也是文件的一种,但是由于系统对它的读写有一定的缓存策略,即在内存中会有一份SharedPreferences文件的缓存,因此在多进程模式下,系统对它的读写就变得不可靠,当面对高并发读写的访问时候,有很大几率会丢失市局,因此不建议在进程通信中使用SharedPreferences。
(3). 使用MessengerMessenger是一种轻量级的IPC方案,它的底层实现就是AIDL。Messenger是以串行的方式处理请求的,即服务端只能一个个处理,因此在服务端我们不用考虑线程同步的问题。
(4). 使用AIDL大致流程:首先创建一个Service和一个AIDL接口,接着创建一个类集成自AIDL接口中的Stub类并实现Stub类中的抽象方法,在Service的onBind方法中返回这个类的对象,然后客户端就可以绑定服务端Service,建立连接后就可以访问远程服务端的方法了。

  1. AIDL支持的数据类型:基本数据类型、String和CharSequence、ArrayList、HashMap、Parcelable以及AIDL。
  2. 某些类即时和AIDL文件在同一个包镇南关也要显示import进来;
  3. AIDL除了基本数据类,其他类型的参数都要标上方法:in、out或者inout;
  4. AIDL接口中支持方法,不支持声明静态变量。
  5. 为了方便AIDL的开发,建议把所有和AIDL相关的类和文件全部放入同一个包中,这样做的好处是,当客户端是另一个应用的时候,可以直接把整个包复制到客户端工程中。
  6. RemoteCallbackList是系统专门提供的用于删除跨进程Listener的接口。RemoteCallbackList是一个泛型,支持管理人已的AIDL接口,因为所有的AIDL接口都继承自IInterface接口。
    使用AIDL需要注意两点:第一点,由于客户端的onServiceConnectedonServiceDisconnected方法都运行在UI线程中,所以不可以在它们里面直接低啊用服务端的耗时方法。第二点,由于服务端的方法本身就运行在服务端的Binder线程池中,所以服务端方法本身就可以执行大量耗时的操作

(5). 使用ContentProvider 1. ContentProvider主要以表格的形式来组织数据,并且可以包含多个表;2. ContentProvider还支持文件数据,比如图片、视频等,系统提供的MediaStore就是文件类型的ContenProvider;3. ContentProvider对底层的数据存储方式没有任何要求,可以使SQLite、文件,甚至是内存中的一个对象都行;4. 要观察ContentProvider中的数据变化情况,可以通过ContentResolver的registerContentObserver方法来注册观察者。
(6). 使用Socket:Socket也称为“套接字”,是网络通信中的概念,它分为流式套接字和用户数据报套接字两种,分别对应于网络控制层中的TCP和UDP协议。TCP和UDP的区别:TCP协议是面向连接的协议,提供稳定的双向通信功能,TCP连接的建立余姚经过“三次握手”才能完成,为了提供稳定的数据传输功能,其本省提供了超时重传机制,因此具有很高的稳定性;而UDP是无连接的,提供不稳定的单向通信功能,当然UDP也可以实现双向通信功能。在性能上,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方式

名称 优点 缺点 适用场景 Bundle 简单易用 只能传输Bundle 支持的数据类型 文件共享 简单易用 不适合高并发场景,并且无法做到进程间的即时通信 无并发访问 AIDL 功能强大,支持一对多并发通信,支持实时通信 使用稍复杂,需要处理好线程同步 一对多且有RPC需求 Messenger 功能一般,支持一对多串行通信 不能很好处理高并发情形,不支持RPC,数据通过Message进行传输,因此只能传输Bundle支持的数据类型 地并发的一对多即时通信,无RPC需求,或者无须要返回结果的RPC需求 ContentProvider 在数据源访问方面功能强大,支持一对多并发数据共享,可通过Call方法扩展其他错做 可以理解为受约束的AIDL,主要提供数据源的CRUD操作 一对多的进程间数据共享 Socket 功能强大,可以通过网络传输字节流,支持一对多并发实时通信 实现细节稍微有点繁琐,不支持直接的RPC 网络数据交换
0 0
原创粉丝点击