一直以来都想写篇关于IPC的博客,但是一直都不敢下手,究其原因感觉IPC机制里面水太深,怕自己在里面淹死,但是人嘛总不可能总呆在安逸区里面,是时候好好整理一下IPC的有关内容了,哪里不对的地方还望各位大佬在下面指出。
IPC简介
首先我们需要弄懂什么是IPC,IPC是Inter Process Communication的缩写,即进程间通信。那么什么是进程呢?所谓进程我们可以简单理解为独立的应用程序,即一个进程就是一个应用程序。在PC端的表现为
我们可以在任务管理器里面的详细信息里面找到,这里我们需要注意的是PID,那么PID又是什么呢?其实它是每个应用程序的身份ID,就和人的身份证一样,具有唯一性
那么在手机端又是如何表现的呢?
在手机上面我们可以更直白的看到进程数目,那么在手机上面有没有和PC上面一样标识身份的东西呢?答案肯定是有的,那就是UID,通过Process.myUid();就可以获取到当前进程的UID了。我们都知道,在操作系统中,线程是可以供我们调度的最小单元,同时线程是一种有限的系统资源,那么进程和线程又是什么关系呢?其实二者是包含和被包含的关系,即一个进程可以包含多个线程,在实际的开发中,我们在一个应用程序(进程中)有一个主线程(UI线程)。那么为什么要有多进程通信呢?即多进程通信的使用场景有哪些呢:
1》应用程序的某些板块由于特殊原因或者需要更大的内存空间
2》需要向其他应用获取数据
3》获取更多的内存空间从而避免OOM
开启多进程模式
在android中开启多进程的方式很简单,通过给四大组件指定android:process属性,我们就可以轻易的开启多进程模式,记住,这也是开启多进程的唯一模式
多进程模式的隐患
我们知道开启一个新的进程也就意味着重新分配了一个独立的虚拟机,而不同的虚拟机在内存分配上有不同的地址空间,这就导致在不同的虚拟机中访问一个类的对象会产生多种副本,举一个很简单的例子
如上图所示,我们定义一个静态变量,在进程1中设置为1,接着在进行2中设置为2,然后在进程1中打印变量的值,这个时候你就会发现很神奇的事情,有兴趣的同学可以自己试试,在这里我就先卖个关子。
当然,开启多进程的隐患不仅仅于此,总结起来有以下几点:
(1)静态成员和单例模式完全失效
(2)线程同步机制完全失效
(3)SharedPreferences的可靠性下降
(4)Application会多次创建
跨进程通信
虽然多进程有那么多的问题,但是我们不能因为这个而放弃它,因为系统给我们提供了很多跨进程通信的办法,虽然说不能直接地共享内存,但是通过跨进程通信我们还是可以实现数据交互。在开始讲解多进程的通信方式之前,我们首先要了解一个东西,即序列化
序列化和反序列化
我们知道对于多进程通信而言,其最终的目的还是对数据的操作,那么在数据传输的过程当中,对数据的结构有一定的要求,最基本的要求就是数据可以进行序列化。
Java中的序列化Serializable接口
Serializable是Java所提供的一个序列化接口,它是一个空接口,为对象提供标准的序列化和反序列化的操作。
public class Person implements Serializable {
// 可以不用设置,由系统自动生成,但是对于反序列化有影响
private static final long serialVersionUID = 5241058525588L;
private String mName;
private int mAge;
public Person(String name, int age) {
this.mName = name;
this.mAge = age;
}
public String getName() {
return mName;
}
public void setName(String mName) {
this.mName = mName;
}
public int getAge() {
return mAge;
}
public void setAge(int mAge) {
this.mAge = mAge;
}
}
try {
// 序列化过程
Person person = new Person("jack", 55);
ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("cache.txt"));
out.writeObject(person);
out.close();
// 反序列化过程
ObjectInputStream in = new ObjectInputStream(new FileInputStream("cache.txt"));
Person newUser = (Person) in.readObject();
in.close();
} catch (Exception e) {
e.printStackTrace();
}
一般来说,我们应该手动指定serialVersionUID的值,这样序列化和反序列化时两者的serialVersionUID是相同的,因此可以正常进行序列化。如果不手动指定serialVersionUID的值,反序列化时当前类有所变化,比如增加或者删除了某些成员变量,那么系统就会重新计算当前类的hash值并把它赋值给serialVersionUID,这个时候当前类的serialVersionUID就和序列化的数据中的serialVersionUID不一致,从而导致反序列化失败。所以我们手动指定serialVersionUID很大程度上可以避免反序列化的失败
Android中的序列化Parcelable接口
public class User implements Parcelable {
private int userId;
private String userName;
private boolean isMale;
public User(int userId, String userName, boolean isMale) {
this.userId = userId;
this.userName = userName;
this.isMale = isMale;
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeInt(userId);
dest.writeString(userName);
dest.writeInt(isMale ? 1 : 0);
}
public static final Creator<User> CREATOR = new Creator<User>() {
@Override
public User createFromParcel(Parcel in) {
return new User(in);
}
@Override
public User[] newArray(int size) {
return new User[size];
}
};
private User(Parcel in) {
userId = in.readInt();
userName = in.readString();
isMale = in.readInt() == 1;
}
}
首先我们先讲讲Parcel,Parcel内部包装了可序列化的数据,可以在Binder中自由传输。从上面可以看出,在序列化过程中需要实现的功能有序列化,反序列化和内容描述。序列化功能由writeToParcel完成,反序列化功能由CREATOR来完成,内容描述功能由describeContents完成,几乎所有情况下都应该返回0,仅当当前对象中存在文件描述符时返回1。
注意:系统已经为我们提供了许多实现了Parcelable接口的类,它们都是可以直接序列化的,比如Intent、Bundle、Bitmap等,同时List和Map也可以序列化,前提是它们里面的每个元素都是可序列化的。
那么对于Parcelable和Serializable都能实现序列化并且都可用于Intent间的数据传递,那么二者的区别是什么呢?
1》Serializable使用起来简单但是开销大
2》Parcelable使用起来稍微麻烦但是效率高,更适合在Android中使用
初始Binder
我们知道,多进程通信并不是android所特有的,任何的系统都有进程间通信,只不过各不相同罢了。作为android的底层系统Linux同样也具有自己的跨进程通信的方式,那么为什么android还出了一个binder呢?答案不言而喻,binder肯定有其独特的魅力,接下来我们就一起来看看吧!
直观来说,Binder是Android中的一个类,它实现了IBinder接口。在Android开发中,Binder主要用在Service中,包括AIDL和Messenger,那么我们首先来看看AIDL
public class Book implements Parcelable{
private int bookId;
private String bookName;
private Book(Parcel in) {
bookId = in.readInt();
bookName = in.readString();
}
public static final Creator<Book> CREATOR = new Creator<Book>() {
@Override
public Book createFromParcel(Parcel in) {
return new Book(in);
}
@Override
public Book[] newArray(int size) {
return new Book[size];
}
};
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeInt(bookId);
dest.writeString(bookName);
}
}
// Book.aidl
package com.stevences.androidipc.ipc;
parcelable Book;
// IBookManager.aidl
package com.stevences.androidipc.ipc;
// Declare any non-default types here with import statements
import com.stevences.androidipc.ipc.Book;
interface IBookManager {
List<Book> getBookList();
void addBook(in Book book);
}
这是《Android 开发艺术探索》中的一个小例子,在这里大家千万注意一点,即 Book.java和Book.aidl,IBookManager.aidl包名必须相同,否则编译不通过。当然,这个例子并不是重点,重点是系统为我们生成的IBookManager.java这个接口,这个才是我们重点分析的内容
package com.stevences.androidipc.ipc;
public interface IBookManager extends android.os.IInterface
{
/** Local-side IPC implementation stub class. */
public static abstract class Stub extends android.os.Binder implements com.stevences.androidipc.ipc.IBookManager
{
private static final java.lang.String DESCRIPTOR = "com.stevences.androidipc.ipc.IBookManager";
/** Construct the stub at attach it to the interface. */
public Stub()
{
this.attachInterface(this, DESCRIPTOR);
}
/**
* Cast an IBinder object into an com.stevences.androidipc.ipc.IBookManager interface,
* generating a proxy if needed.
*/
public static com.stevences.androidipc.ipc.IBookManager asInterface(android.os.IBinder obj)
{
if ((obj==null)) {
return null;
}
android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
if (((iin!=null)&&(iin instanceof com.stevences.androidipc.ipc.IBookManager))) {
return ((com.stevences.androidipc.ipc.IBookManager)iin);
}
return new com.stevences.androidipc.ipc.IBookManager.Stub.Proxy(obj);
}
@Override public android.os.IBinder asBinder()
{
return this;
}
@Override public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException
{
switch (code)
{
case INTERFACE_TRANSACTION:
{
reply.writeString(DESCRIPTOR);
return true;
}
case TRANSACTION_getBookList:
{
data.enforceInterface(DESCRIPTOR);
java.util.List<com.stevences.androidipc.ipc.Book> _result = this.getBookList();
reply.writeNoException();
reply.writeTypedList(_result);
return true;
}
case TRANSACTION_addBook:
{
data.enforceInterface(DESCRIPTOR);
com.stevences.androidipc.ipc.Book _arg0;
if ((0!=data.readInt())) {
_arg0 = com.stevences.androidipc.ipc.Book.CREATOR.createFromParcel(data);
}
else {
_arg0 = null;
}
this.addBook(_arg0);
reply.writeNoException();
return true;
}
}
return super.onTransact(code, data, reply, flags);
}
private static class Proxy implements com.stevences.androidipc.ipc.IBookManager
{
private android.os.IBinder mRemote;
Proxy(android.os.IBinder remote)
{
mRemote = remote;
}
@Override public android.os.IBinder asBinder()
{
return mRemote;
}
public java.lang.String getInterfaceDescriptor()
{
return DESCRIPTOR;
}
@Override public java.util.List<com.stevences.androidipc.ipc.Book> getBookList() throws android.os.RemoteException
{
android.os.Parcel _data = android.os.Parcel.obtain();
android.os.Parcel _reply = android.os.Parcel.obtain();
java.util.List<com.stevences.androidipc.ipc.Book> _result;
try {
_data.writeInterfaceToken(DESCRIPTOR);
mRemote.transact(Stub.TRANSACTION_getBookList, _data, _reply, 0);
_reply.readException();
_result = _reply.createTypedArrayList(com.stevences.androidipc.ipc.Book.CREATOR);
}
finally {
_reply.recycle();
_data.recycle();
}
return _result;
}
@Override public void addBook(com.stevences.androidipc.ipc.Book book) throws android.os.RemoteException
{
android.os.Parcel _data = android.os.Parcel.obtain();
android.os.Parcel _reply = android.os.Parcel.obtain();
try {
_data.writeInterfaceToken(DESCRIPTOR);
if ((book!=null)) {
_data.writeInt(1);
book.writeToParcel(_data, 0);
}
else {
_data.writeInt(0);
}
mRemote.transact(Stub.TRANSACTION_addBook, _data, _reply, 0);
_reply.readException();
}
finally {
_reply.recycle();
_data.recycle();
}
}
}
static final int TRANSACTION_getBookList = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
static final int TRANSACTION_addBook = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1);
}
public java.util.List<com.stevences.androidipc.ipc.Book> getBookList() throws android.os.RemoteException;
public void addBook(com.stevences.androidipc.ipc.Book book) throws android.os.RemoteException;
}
首先:IBookManager.java继承了IInterface这个接口,同时在它里面有个内部类Stub,而这个类继承自Binder同时实现了IBookManager接口,所以这个类将会是我们重点分析的对象,我们再看看其他的东西。在最底部实现了两个方法,而这两个方法是我们在IBookManager.aidl里面定义的两个方法,那么Binder的工作流程是什么样子的呢?我们用一张图来说明
现在我们以getBookList为例来说明一下binder的工作流程:
首先这个方法是在客户端的,当客户端调用这个方法的时候,内部实现原理是这样的:
1》首先创建该方法所需要的输入型对象_date和输出型对象_reply和返回值对象_result
2》把参数的对象写入到_data中
3》调用transact发起远程请求,同时当前线程挂起
4》服务端的onTransact方法会被调用,直到返回结果后该线程继续执行
5》从_reply中取出结果
6》返回结果
注意:不能在UI线程中发起远程请求,采用同步的方式去实现
既然知道了AIDL的原理,那么我们开始自己手动去写一个吧,还是以上面的例子为例
首先新建一个IBookManager,继承自IInterface,同时声明getBookList()和addBook()方法
public interface IBookManager extends IInterface {
static final String DESCRIPTOR = "com.stevences.androidipc.ipc.IBookManager";
static final int TRANSACTION_getBookList = IBinder.FIRST_CALL_TRANSACTION + 0;
static final int TRANSACTION_addBook = IBinder.FIRST_CALL_TRANSACTION + 1;
public List<Book> getBookList() throws RemoteException;
public void addBook(Book book) throws RemoteException;
}
然后写一个实现的方法BookManagerImpl
public class BookManagerImpl extends Binder implements IBookManager {
/**
* Construct the stub at attach it to the interface.
*/
public BookManagerImpl() {
this.attachInterface(this, DESCRIPTOR);
}
/**
* Cast an IBinder object into an com.stevences.androidipc.ipc.IBookManager interface,
* generating a proxy if needed.
*/
public static IBookManager asInterface(android.os.IBinder obj) {
if ((obj == null)) {
return null;
}
android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
if (((iin != null) && (iin instanceof com.stevences.androidipc.ipc.IBookManager))) {
return ((com.stevences.androidipc.ipc.IBookManager) iin);
}
return new BookManagerImpl.Proxy(obj);
}
@Override
public android.os.IBinder asBinder() {
return this;
}
@Override
public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException {
switch (code) {
case INTERFACE_TRANSACTION: {
reply.writeString(DESCRIPTOR);
return true;
}
case TRANSACTION_getBookList: {
data.enforceInterface(DESCRIPTOR);
java.util.List<com.stevences.androidipc.ipc.Book> _result = this.getBookList();
reply.writeNoException();
reply.writeTypedList(_result);
return true;
}
case TRANSACTION_addBook: {
data.enforceInterface(DESCRIPTOR);
com.stevences.androidipc.ipc.Book _arg0;
if ((0 != data.readInt())) {
_arg0 = com.stevences.androidipc.ipc.Book.CREATOR.createFromParcel(data);
} else {
_arg0 = null;
}
this.addBook(_arg0);
reply.writeNoException();
return true;
}
}
return super.onTransact(code, data, reply, flags);
}
@Override
public List<Book> getBookList() throws RemoteException {
// TODO 待实现的方法
return null;
}
@Override
public void addBook(Book book) throws RemoteException {
// TODO 待实现的方法
}
private static class Proxy implements IBookManager {
private android.os.IBinder mRemote;
Proxy(android.os.IBinder remote) {
mRemote = remote;
}
@Override
public android.os.IBinder asBinder() {
return mRemote;
}
public java.lang.String getInterfaceDescriptor() {
return DESCRIPTOR;
}
@Override
public java.util.List<com.stevences.androidipc.ipc.Book> getBookList() throws android.os.RemoteException {
android.os.Parcel _data = android.os.Parcel.obtain();
android.os.Parcel _reply = android.os.Parcel.obtain();
java.util.List<com.stevences.androidipc.ipc.Book> _result;
try {
_data.writeInterfaceToken(DESCRIPTOR);
mRemote.transact(TRANSACTION_getBookList, _data, _reply, 0);
_reply.readException();
_result = _reply.createTypedArrayList(com.stevences.androidipc.ipc.Book.CREATOR);
} finally {
_reply.recycle();
_data.recycle();
}
return _result;
}
@Override
public void addBook(com.stevences.androidipc.ipc.Book book) throws android.os.RemoteException {
android.os.Parcel _data = android.os.Parcel.obtain();
android.os.Parcel _reply = android.os.Parcel.obtain();
try {
_data.writeInterfaceToken(DESCRIPTOR);
if ((book != null)) {
_data.writeInt(1);
book.writeToParcel(_data, 0);
} else {
_data.writeInt(0);
}
mRemote.transact(TRANSACTION_addBook, _data, _reply, 0);
_reply.readException();
} finally {
_reply.recycle();
_data.recycle();
}
}
}
}
可能有人会问,你这个方法里面的大部分代码都和AIDL的代码一样,有必须去自己手写一个吗?其实,在实际的开发过程中是完全没有必要的,而且在实际的开发过程中我们也不会这样去干,我之所以这样写的目的只有一个:加深对AIDL的理解。
接下来我们讲两个比较重要的方法:
linkToDeath和unlinkToDeath,翻译过来的话就是连接死亡和未连接死亡。由于Binder运行在服务端进程,所以有可能在运行的过程中由于某些原因导致连接失败,而这个时候我们的线程并不知道已经连接失败从而会导致我们的线程一直处于一种挂起的状态。为了避免出现这种情况,系统为我们提供了linkToDeath和unlinkToDeath。通过linkToDeath我们可以给Binder设置一个死亡代理,当binder死亡的时候,我们就会收到通知,这个时候我们就可以重新发起连接请求从而恢复连接。那么linkToDeath是如何操作的呢?首先声明一个DeathRecipient对象,实现里面的binderDied方法,当binder死亡的时候,系统就会回调binderDied方法,然后我们就可以移出之前绑定的binder代理并重新绑定远程服务,具体代码如下:
private BookManagerImpl bookManager;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
try {
bookManager = new BookManagerImpl();
IBinder binder = bookManager.asBinder();
binder.linkToDeath(mDeathRecipient, 0);
} catch (RemoteException e) {
e.printStackTrace();
}
}
private IBinder.DeathRecipient mDeathRecipient = new IBinder.DeathRecipient() {
@Override
public void binderDied() {
if (bookManager == null)
return;
bookManager.asBinder().unlinkToDeath(mDeathRecipient, 0);
bookManager = null;
// 在这里重新绑定远程Service
}
};
到这里binder的知识点就讲完了,下一篇我们就进入正题 Android中的IPC方式大集锦