对岸的女孩看过来——前奏篇(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正式开始交流前的准备工作就介绍完了,后面一篇开始正式讲进程间的通信方式和具体的实现。
- 对岸的女孩看过来——前奏篇(IPC)
- JDBC之 “ 对岸的女孩看过来”
- 练习项目之---jdbc对面的女孩看过来
- 谷歌眼镜秀出时尚风采:对面的女孩看过来
- 想办假证儿的看过来——Leo网上答疑(55)
- 《大腕》做售前的看过来
- 初学32的看过来
- 压力是恶魔——不想早死的程序员们看过来!
- 程序员的十层楼(1~3层)——菜鸟、大虾看过来!
- 极客看过来,开源的家庭自动化方案——openHAB
- [转载]为什么12306成了一个失败的网站——软件学院本科生看过来
- Vrml 路在何方—正在做或准备做vrml的朋友看过来(一)
- GIS开发人员看过来—Esri的web开发技术趋势
- 外包公司的员员看过来---面试篇
- 看过来,strlen()函数与sizeof的区别?????
- 2013——毕业的前奏(年度总结)
- edit_source——编译MudOS的前奏
- edit_source——编译MudOS的前奏
- IOS学习(十七)多视图管理
- openvpn 固定客户端IP地扯
- AWK 将文本按行打乱顺序
- PCA (主成分分析)详解 (写给初学者)
- jsp实现分页功能
- 对岸的女孩看过来——前奏篇(IPC)
- hibernate xxx is not mapped 错误原因及解决方法
- 高手请绕道,关于ubuntu16.04文件名乱码的尝试
- centos7 关闭firewall防火墙指令以及更换安装iptables并配置
- HTML简介
- iOS拖动按钮排序UI效果
- Semaphore笔记
- 数据挖掘笔记(1)——数据库与数据仓库,挖掘模式
- html基础回顾--html文本框常见运用技巧及示例