对岸的女孩看过来——前奏篇(IPC)

来源:互联网 发布:广州市网络大学堂 编辑:程序博客网 时间:2024/04/29 16:30

IPC含义为进程间通信或跨进程通信,是指两个进程之间进行数据交换的过程。就好比两个人谈恋爱一样,情意绵绵,你来我往(Binder、信号量、管道、Socket等),相互付出,彼此感受恋爱带来的乐趣(Parcelable、Serializable系列化与反系列化携带的数据)。瞎扯莫怪,下面进入主题
Android中的多进程启动方式
在Android中使用多进程只有一种方式,那就是在AndroidMenifest中给四大组件指定android:process属性(在native层fork一个新进程除外)。在指定android:process属性时用两种方式如下:

    <activity android:name="com.xlzs.FirstActivity"        android:label="@string/activity_splash" />    <activity android:name="com.xlzs.SecondActivity"        android:process="**:remote**"        android:label="@string/activity_main" />      <activity android:name="com.xlzs.ThirdActivity"        android:process="setting"        android:label="@string/activity_setting" />

它们的区别在于以“:”开头的进程属于当前应用的私有进程,其他应用的组件不可以和它跑在同一个进程中,而进程名不以“:”开头的进程属于全局进程,其它应用通过ShareUID(Android为每一个应用分配唯一的UID)方式可以和它跑在同一个进程中。没有指明android:process的表示运行在以包名为进程名的进程中。
虽然只是简简单单的在AndroidMenifest文件中添加一个属性就实现多进程了,但实际开发中多进程间数据传送、通信中会出现很多问题,一般会有如下几方面:
1. 静态成员和单例模式完全失效(不是同一块内存,不是同一个对象)
2. 线程同步机制完全失效(不是同一块内存,不是同一个对象)
3. SharedPreferences的可靠性下降(其本质是对xml文件的读写)
4. Application会多次创建(由于android会为每一个进程分配一个独立的虚拟机,相当于系统又把这个应用重启了一遍,那么自然会创建新的Application)

虽然在恋爱中会出现各种各样莫名的问题,但是恋爱还是得谈的。如果两个陌生人想进一步发展,那么也许需要外力的帮助,媒婆(Binder)。在媒婆引荐两人认识前,可能需要互换信件(Parcelable、Serializable)来传递信息博得好感。(比喻描述的不好大伙勿喷,只是为了好理解)在两人深入认识之前,我们先来了解下媒婆、以及信件的起到的作用:
Serializabe接口
Serializable是Java所提供的一个序列化接口,它是个空接口,为对象提供标准的系列化和反系列化操作。使用Serializable来实现序列化相当简单,只需要这个类实现Serializable接口并声明一个serialVersionUID 即可,有时甚至不需要这个serialVersionUID 也可以实现序列化(系统会自动生成hash值),但是这会影响反系列化结果,原则上系列化后的数据中的serialVersionUID只有和当前类的serialVersionUID相同时才能够正常的被反系列化。serialVersionUID的详细工作机制:系列化的时候系统会把当前的serialVersionUID写入系列化的文件中(也可能是中介),当反系列化的时候系统会去检测文件中的serialVersionUID,看它是否和当前类的serialVersionUID一致,如果一致就说明系列化的类的版本和当前的版本是相同的,这个时候反系列化成功;否则就说明当前类和系列化的类发生了某种变换,无法正常反系列化,会报错。当指定serialVersionUID后,它就可以很大程度上避免反系列化失败,比如当前版本升级后,我们可能删除了某个成员变量也可能增加了一些成员变量,这个时候反系列化过程任然可以成功(除了修改类名,字段类型等),相反,如果没有指定serialVersionUID的话,程序就会挂掉。

Parcelable接口
Parcelable也是个接口,只要实现了这个接口,一个类的对象就可以实现系列化并且可以通过Intent和Bingder传递。下面是个典型的用法:

 public class User implements Parcelable {    private int userId;    private String userName;    private Book book;    public User(int userId, String userName) {    this.userId = userId;    this.userName = userName;}public User(Parcel source) {    //这里的顺序与writeToParcel的顺序保持一致    userId = source.readInt();    userName = source.readString();    //因为Book本身是一个可系列化的对象,所以他的反系列化过程需要传递当前线程的上下文类加载器    book = source.readParcelable(Thread.currentThread().getContextClassLoader());}//当前内容描述,有内容描述符返回1,否则返回0,几乎所有都返回0@Overridepublic int describeContents() {    return 0;}//将当前对象写入系列化结构中,其中flag有两种值0或1,1标识当前对象需要作为返回值返回,//不能立即释放资源,几乎所有情况都为0@Overridepublic void writeToParcel(Parcel des, int flags) {    des.writeInt(userId);    des.writeString(userName);    des.writeParcelable(book, 0);}public static final Parcelable.Creator<User> CREATOR = new Parcelable.Creator<User>(){    //从系列化后的对象中创建原始对象    @Override    public User createFromParcel(Parcel source) {        //Parcel内部包装了可系列化的数据,可以在Binder中自由传输        return new User(source);    }    //创建指定长度的原始对象数组    @Override    public User[] newArray(int size) {        return new User[size];    }   };}

Parcelable与Serializable区别:Serializable是Java中的系列化接口,使用简单但开销大,适用于系列化到存储设备中或者将对象系列化后通过网络传输。Parcelable是Android中的系列化过程,缺点麻烦但效率高,主要用作内存系列化中,进程通信传递,android推荐首选。

Binder(C++中对应BBinder)
从IPC角度来说,Binder是Android中的一种夸进程通信方式,Binder是一种虚拟的物理设备(是所以说是虚拟,因为并未存在这个设备,只是软件层面上的一种实现),它的设备驱动是/dev/binder。从Android Framework角度说,Binder是ServiceManage连接各种Manage(ActivityManager、WindowManage等)和相应的ManageService的桥梁(媒婆)。从应用层来说,Binder是客户端和服务端进行通信的媒介,当bindService的时候,服务端会返回一个包含了服务端业务调用的Binder对象,通过这个Binder对象,客户端就可以获取服务端提供的服务或数据,这个服务通常指的是AIDL的服务(其实系统的各种Service都是通过这个机制实现的)。下面就来简单的介绍这个 AIDL工作机制,后面部分还会更加深入的介绍,首先新建三个文件Book.java、Book.aidl和IBookManager.aidl代码如下:

//Book.java

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;}public int describeContents() {    return 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();}@Overridepublic String toString() {    return String.format("[bookId:%s, bookName:%s]", bookId, bookName);}

}
//Book.aidl

parcelable Book;

//IBookManager.aidl这里可以看到即使是同一个文件也必须导入Book类这是 AIDL的特性

import com.xlzs.aidl.Book;import com.xlzs.aidl.IOnNewBookArrivedListener;interface IBookManager { List<Book> getBookList(); void addBook(in Book book);}

Book.java是一个表示图书信息的类,它实现了Parcelable接口。Book.aidl是Book类在AIDL中的声明。IBookManager.aidl是我们定义的一个接口,里面有两个方法,其中getBookList用于远程服务端获取图书列表,而addBook用于添加一本书,这两个方法主要为了方便简单举的例子,并没有什么实际意义。下面来看下系统为我们自动生成的IBookManager.aidl对应的Binder类,在gen目录下xx.xx.aidl包中,代码就不贴了都是自动生成的。接着我们根据这个系统生成的Binder类来分析Binder的工作原理。
DESCRIPTOR
Binder的唯一标识,一般用当前Binder的类名表示。
asInterface(android.os.IBinder obj)
用于将服务端的Binder对象转换成客户端所需要的AIDL接口类型的对象
,这种转换过程是区分进程的,如果客户端和服务端位于同一个进程,那么此方法返回的就是服务端的Stub对象本身,否则返回的是系统封装后的Stub.proxy对象
asBinder
此方法用于返回当前的Binder对象
onTransact
这个方法运行在服务端中的Binder线程池中(所以远程处理耗时操作是就不用再创建子线程来执行了,因为本身就在线程中工作),当客户端发起夸进程请求时,远程请求会通过系统底层封装后交由此方法来处理(如果是耗时操作,客户端要开启子线程来执行后面会有详细说明)。该方法的原型为public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) 。服务端通code可以确定客户端请求的目标方法是什么,接着从data中取出目标方法所需的参数(如果目标方法有参数)然后执行目标方法。当目标方法执行完毕后,就向reply中写入返回值(如果目标方法有返回值)onTransact方法的执行过程就这样。如果此方法返回false表示客户端请求失败,因此我们可以利用这个特性做权限验证。
Proxy#getBookList与Proxy#addBook
这两个方法都是运行在客户端,当客户端远程调用这两个方法是会将数据输入到Parcel对象_data中(有参数的话),接着调用transact方法来发送请求,同时线程挂起(耗时操作小心);然后服务端的onTransact方法会被调用(如上说明的),最后通过_reply返回调用结果。
从上述分析过程大概清楚了Binder的工作机制了,接下来,我们介绍Binder的两个重要的方法linkToDeath和unlinkToDeath。我们知道Binder运行在服务端进程,如果服务端进程由于某种原因异常终止,这个时候我们调用服务端的Binder服务就会失败。关键的是我们不知道Binder连接已经断开,那么客户端的功能就会受影响,为了解决这个问题,Binder中提供了两个配对的方法,通linkToDeath我们可以给Binder设置一个死亡代理,当Binder死亡时,我们就会收到通知,这个时候我们就可以重新发起连接请求从而恢复连接。下面我们就用代码来实现:

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

在客户端绑定远程服务成功后,给binder设置死亡代理:

mService = IMessageBoxManager.Stub.asInterface(binder); binder.linkToDeath(mDeathRecipient,0);

此外,Binder的方法isBinderAlive也可以判断Binder是否死亡。至此,在IPC正式开始交流前的准备工作就介绍完了,后面一篇开始正式讲进程间的通信方式和具体的实现。

0 0
原创粉丝点击