Android中的IPC机制即跨进程通信技术(Inter-Process-Comunicate),主要技术是 Android 的序列化机制和Binder。 Android 跨进程通信常见的几种方式;Bundle、文件共享、AIDL、Messenger、ContentProVider和Socket等。其中AIDL、Messenger、ContentProvider底层属于基于 Android Binder 机制实现的。
Android 多进程模式 关于开启多进程模式注意,android:process=":remote"
与android:precess="com.xxx.xxx.remote"
指定Android 多进程方式的区别,进程名以”:”开头的进程属于当前应用的私有进程,其他应用的组件不可以和它跑在同一进程,而进程名不以”:”开头的进程属于全局进程,其他应用通过 ShareUID 方式(签名也需相同)可以和它跑在同一个进程中。Android系统为每个应用分配一个唯一的 UUID,具有相同的 UUID 的应用才能共享数据。
多进程模式下会造成如下几方面的问题:
静态成员和单例模式完全失效
线程同步机制完全失效
SharedPrefence 的可靠性下降(SharedPrefence 本质上是读写 xml 文件进行存储,并发操作会导致混乱)
Application 多次创建
运行在同一个进程中的组件是属于同一个虚拟机和同一个 Application 的,同理,运行在不同进程中的组件是属于两个不同的虚拟机和 Application 的。
IPC基础概念介绍 IPC基本概念主要包含三方面内容:Serializable 和Parcelable 接口以及 Binder。Serializable 和Parcelable 接口可以完成对象的序列化过程。通过序列化保证对象的持久化,可以使用 Intent 、Bundle 以及网络传递对象。
Serializable序列化 Serializable 实现序列化的过程主要通过下面的表示实现:
1 private static final long serialVersionUID = 8711368828010083044L
序列化标识并不是必须的,系统会自动生成这个标识,但是当实现 Serializable 序列化接口的对象实体类结构发生变化,序列化标识也会重新赋值,这将对与反序列化过程有影响。
1 2 3 4 5 6 7 8 9 10 User user = new User("0" ,"jake" ,true ); ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("cache.txt" )); out.writeObject(user); out.close(); ObjectInputStream in = new ObjectInputStream(new FileInputStream("cache.txt" )); User newUser = (User)int .readObject(); in.close();
需要注意的是 user 和 newUser 的内容相同,但不是同一个对象。关于 serialVersionUID 这个标识,只有序列化之前的对象的 serialVersionUID 值和序列化之后 serialVersionUID 值一致,才可以正常反序列化。实体类中成员数量和类型发生变化, serialVersionUID 值都会改变。一般来说我们手动指定 serialVersionUID 值,这样反序列化过程不会受到影响。如果不手动指定 serialVersionUID ,反序列化是当前类有所改变,比如增加或者删除了某些成员变量,那么系统就会重新计算当前类结的 hash 值并把它赋值给 serialVersionUID,这个时候当前类的serialVersionUID 就和序列化后的数据中的 serialVersionUID 不一致,于是反序列化失败。当我们手动指定了它以后,就可以很大程度上避免反序列化过程的失败,比如删除了某些成员变量或者增加了某些成员变量,数据仍然可以正常反序列化。不过如果类结构发生非常规性改变,比如修改类名,变量类型或者名称,即便指定了serialVersionUID,依然会反序列化失败。
Parcelable序列化 Parcelable 是 Android 平台上提供的序列化方案,相对于 Serialable 大量的 IO 操作,拥有更好的性能。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 public class Book implements Parcelable { private int bookId; private String bookName; public Book () {} public Book (int bookId, String bookName) { this .bookId = bookId; this .bookName = bookName; } public int getBookId () { return bookId; } public void setBookId (int bookId) { this .bookId = bookId; } public String getBookName () { return bookName; } public void setBookName (String bookName) { this .bookName = bookName; } protected Book (Parcel in) { bookId = in.readInt(); bookName = in.readString(); } public void readFromParcel (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); } }
Android进程通信之Binder机制 Messenger-进程间通信的信使 Messenger 根据 Handler 生成实例构造方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 public Messenger (Handler target) { mTarget = target.getIMessenger(); } public void send (Message message) throws RemoteException { mTarget.send(message); }
通过 Handler 对象创建 Messenger,通过 Messenger#send(Message)
方法就可以跨进程传递信息。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 private Messenger client = new Messenger(new MessengerHandler()); private ServiceConnection serviceConnection = new ServiceConnection() { @Override public void onServiceConnected (ComponentName name, IBinder service) { MainActivity.this .service = new Messenger(service); Message msg = Message.obtain(null , Contanstants.MSG_FROM_CLIENT); Bundle bundle = new Bundle(); bundle.putString("msg" , "hello, this is client" ); msg.setData(bundle); msg.replyTo = client; try { MainActivity.this .service.send(msg); } catch (RemoteException e) { e.printStackTrace(); } } @Override public void onServiceDisconnected (ComponentName name) { } };
MainActivity 通过调用bindservice()
方法,获取IBinder 对象。看一下 Messenger 关于 IBinder 参数的构造方法:
1 2 3 4 5 6 7 8 9 public Messenger (IBinder target) { mTarget = IMessenger.Stub.asInterface(target); }
上面是 clien 端向 service 端发送信息,再看一下关于 service 端处理 client 端的数据,并回复 client 端。上述代码我们在 Message 传递信息中指定了回复信使,看一下 service 端代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 private Messenger mMessenger = new Messenger(new MessengerHandler());private static class MessengerHandler extends Handler { @Override public void handleMessage (Message msg) { if (msg.what == Contanstants.MSG_FROM_CLIENT) { Bundle bundle = msg.getData(); Log.d(TAG, bundle.getString("msg" )); Messenger clientMessenger = msg.replyTo; Message replyMsg = Message.obtain(null , Contanstants.MSG_FROM_SERVER); bundle.putString("msg" , "this is server" ); replyMsg.setData(bundle); try { clientMessenger.send(replyMsg); } catch (RemoteException e) { e.printStackTrace(); } } else { super .handleMessage(msg); } } } @Nullable @Override public IBinder onBind (Intent intent) { return mMessenger.getBinder(); } }
AIDL(android interface define launage) 关于基于 Binder 机制的 AIDL 以及 Messenger 概述:AIDL 文件在编译过程中会在 build 文件中生成对应的java文件,所以我们也可以通过写java文件来实现进程间通信。这里简单介绍一样 AIDL 文件对应java文件的结构以便于理解后续的概念:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 public interface IBookManager extends android .os .IInterface { public static abstract class Stub extends android .os .Binder implements com .markzl .android .androidipc .entity .IBookManager { private static final java.lang.String DESCRIPTOR = "com.markzl.android.androidipc.entity.IBookManager" ; public Stub () { this .attachInterface(this , DESCRIPTOR); } public static com.markzl.android.androidipc.entity.IBookManager asInterface (android.os.IBinder obj) { if ((obj == null )) { return null ; } android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR); if (((iin != null ) && (iin instanceof com.markzl.android.androidipc.entity.IBookManager))) { return ((com.markzl.android.androidipc.entity.IBookManager) iin); } return new com.markzl.android.androidipc.entity.IBookManager.Stub.Proxy(obj); } @Override public android.os.IBinder asBinder () { return this ; } private static class Proxy implements com .markzl .android .androidipc .entity .IBookManager { } } public void addBookIn (com.markzl.android.androidipc.entity.Book book) throws android.os.RemoteException ; public void addBookOut (com.markzl.android.androidipc.entity.Book book) throws android.os.RemoteException ; public void addBookInOut (com.markzl.android.androidipc.entity.Book book) throws android.os.RemoteException ; }
可以看到 client 端调用的IBookManager.Stub#asInterface()
方法中,会先通过 binder 对象查询当前进程中的IBookManager 对象是否存在,不存在就走 Proxy 代理类。关于 Proxy 中如何处理跨进程通信,下面会结合AIDL定向标签来介绍。
关于 AIDL 中定向标签 in,out,inout 通过分析定向标签,我们同样可以学习到 AIDL 的基本原理。
1 2 3 4 5 6 7 8 9 10 package com.markzl.android.androidipc.entity;import com.markzl.android.androidipc.entity.Book;import com.markzl.android.androidipc.IOnNewBookAddedListener;interface IBookManager { void addBookIn (in Book book) ; void addBookOut (out Book book) ; void addBookInOut (inout Book book) ; }
生成的 java 文件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 public interface IBookManager extends android .os .IInterface { public static abstract class Stub extends android .os .Binder implements com .markzl .android .androidipc .entity .IBookManager { @Override public boolean onTransact (int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException { java.lang.String descriptor = DESCRIPTOR; switch (code) { case TRANSACTION_addBookIn: break ; case TRANSACTION_addBookOut: break ; case TRANSACTION_addBookInOut: break ; } } private static class Proxy implements com .markzl .android .androidipc .entity .IBookManager { static final int TRANSACTION_addBookIn = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0 ); static final int TRANSACTION_addBookOut = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1 ); static final int TRANSACTION_addBookInOut = (android.os.IBinder.FIRST_CALL_TRANSACTION + 2 ); } } public void addBookIn (com.markzl.android.androidipc.entity.Book book) throws android.os.RemoteException ; public void addBookOut (com.markzl.android.androidipc.entity.Book book) throws android.os.RemoteException ; public void addBookInOut (com.markzl.android.androidipc.entity.Book book) throws android.os.RemoteException ; }
in 标签 in 标签 client 端向 service 端写入数据,我们看一下生成的对应的 IBookManager.java 文件,其中用于跨进程处理数据的 IBookManager.Stub 内部私有 Proxy 类:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 private static class Proxy implements com .markzl .android .androidipc .entity .IBookManager { private android.os.IBinder mRemote; Proxy(android.os.IBinder remote) { mRemote = remote; } @Override public android.os.IBinder asBinder () { return mRemote; } @Override public void addBookIn (com.markzl.android.androidipc.entity.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_addBookIn, _data, _reply, 0 ); _reply.readException(); } finally { _reply.recycle(); _data.recycle(); } } }
可以看到 Proxy 中类将我们传入的实体序列化后,交给 mRemote#transact
方法处理,从 Proxy 类中可以看出,mRemote
是一个 IBinder 对象,在调用 Proxy 的构造方法时传入的是 Binder 的对象。 transact 方法是在IBinder 接口类中定义的,其实现类 Binder#transact
方法如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 public final boolean transact (int code, @NonNull Parcel data, @Nullable Parcel reply, int flags) throws RemoteException { if (false ) Log.v("Binder" , "Transact: " + code + " to " + this ); if (data != null ) { data.setDataPosition(0 ); } boolean r = onTransact(code, data, reply, flags); if (reply != null ) { reply.setDataPosition(0 ); } return r; }
可以看到最终调用的方法是onTransact(code, data, reply, flags)
,接下来我们看一下 onTransact 中方法如何处理序列化后的数据:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 @Override public boolean onTransact (int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException { switch (code) { case TRANSACTION_addBookIn: { data.enforceInterface(descriptor); com.markzl.android.androidipc.entity.Book _arg0; if ((0 != data.readInt())) { _arg0 = com.markzl.android.androidipc.entity.Book.CREATOR.createFromParcel(data); } else { _arg0 = null ; } this .addBookIn(_arg0); reply.writeNoException(); return true ; } } }
onTransact()
方法中将序列化的数据重新反序列化成一个新的Book对象,然后调用service端的addBookIn()
方法添加Book对象。注意这个过程是生成一个新的Book对象,具体为什么要注意生成新对象后面会讲到 。这里总结一下in标签的作用向service传递数据但没有处理service的reply结果。
out标签 同样首先看Proxy类中生成的addBookOut()方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 @Override public void addBookOut (com.markzl.android.androidipc.entity.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); mRemote.transact(Stub.TRANSACTION_addBookOut, _data, _reply, 0 ); _reply.readException(); if ((0 != _reply.readInt())) { book.readFromParcel(_reply); } } finally { _reply.recycle(); _data.recycle(); } }
可以看到这里我们并没有处理 Book 对象,_data
始终没有写入 Book 对象,而且调用transact()
方法之后的_reply.readInt() 不等于0的时候,会重新从 _reply 中读取 book 实体。我们再来看一下调用的 onTransact() 方法中如何处理out标签:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 case TRANSACTION_addBookOut: { data.enforceInterface(descriptor); com.markzl.android.androidipc.entity.Book _arg0; _arg0 = new com.markzl.android.androidipc.entity.Book(); this .addBookOut(_arg0); reply.writeNoException(); if ((_arg0 != null )) { reply.writeInt(1 ); _arg0.writeToParcel(reply, android.os.Parcelable.PARCELABLE_WRITE_RETURN_VALUE); } else { reply.writeInt(0 ); } return true ; }
out定向标签 中是重新生成了一个新的 Book 对象,没有写入任何值。然后调用了addBook(Book book)
方法,由此可知 service 并没有真正添加 client 传来的 Book 对象。
inout标签 先看 Proxy#addBookInOut()
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 @Override public void addBookInOut (com.markzl.android.androidipc.entity.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_addBookInOut, _data, _reply, 0 ); _reply.readException(); if ((0 != _reply.readInt())) { book.readFromParcel(_reply); } } finally { _reply.recycle(); _data.recycle(); } }
在 _data
传入 book 序列化数据时和in标签同样的处理, _reply
的处理和 out 标签时保持一致。接着我们看一下 addBookInOut()
方法标签对应的 onTransact() 方法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 case TRANSACTION_addBookInOut: { data.enforceInterface(descriptor); com.markzl.android.androidipc.entity.Book _arg0; if ((0 != data.readInt())) { _arg0 = com.markzl.android.androidipc.entity.Book.CREATOR.createFromParcel(data); } else { _arg0 = null ; } this .addBookInOut(_arg0); reply.writeNoException(); if ((_arg0 != null )) { reply.writeInt(1 ); _arg0.writeToParcel(reply, android.os.Parcelable.PARCELABLE_WRITE_RETURN_VALUE); } else { reply.writeInt(0 ); } return true ; }
调用IBookManager#Stub
实例对象通过 addBookInOut 方法添加由data反序列化后得到的Book对象。inout标签在某种意义上算得上是in和out标签的“并集”,目前并没有看出out和inout标签中的 _reply
值存在的意义。_reply
在处理有返回值的方法使用如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 @Override public java.util.List<com.markzl.android.androidipc.entity.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.markzl.android.androidipc.entity.Book> _result; try { _data.writeInterfaceToken(DESCRIPTOR); mRemote.transact(Stub.TRANSACTION_getBookList, _data, _reply, 0 ); _reply.readException(); _result = _reply.createTypedArrayList(com.markzl.android.androidipc.entity.Book.CREATOR); } finally { _reply.recycle(); _data.recycle(); } return _result; }
onTransact()中对于_reply
赋值如下:
1 2 3 4 5 6 7 8 case TRANSACTION_getBookList: { data.enforceInterface(descriptor); java.util.List<com.markzl.android.androidipc.entity.Book> _result = this .getBookList(); reply.writeNoException(); reply.writeTypedList(_result); return true ; }
关于AIDL各种方法生成的详细内容都可以在build文件夹下生成对应java文件中看到。
service端数据改动时如何主动响应client端 service数据有改动如何主动响应client端?这种情况类似View的setOnClickListener设置监听事件方法,当View被单击调用OnClickListener#onClick
方法来做出相应处理,即事件回调。在之前的IBookManager添加如下接口:
1 2 3 4 5 6 interface IBookManager { void setOnBookAddedListener (OnBookAddedListener listener) ; void removeOnBookAddedListener (OnBookAddedListener listener) ; }
创建接口类OnBookAddedListener:
1 2 3 4 interface OnBookAddedListener { void onNewBookAdded () ; }
然后在service中实现如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 public class BookService extends Service { private List<Book> mBookList = new ArrayList<>(); private List<OnBookAddedListener> mListenerList = new ArrayList<>(); IBookManager bookManager = new IBookManager.Stub() { @Override public void setOnBookAddedListener (OnBookAddedListener listener) throws RemoteException { mListenerList.add(listener); } @Override public void removeOnBookAddedListener (OnBookAddedListener listener) throws RemoteException { mListenerList.remove(listener); } }; @Nullable @Override public IBinder onBind (Intent intent) { new Thread(new AddBookWorker()).start(); return bookManager.asBinder(); } }
在client端与ServiceConnection#onServiceConnected()注册监听方法:
1 2 3 4 5 6 7 8 9 10 @Override public void onServiceConnected (ComponentName name, IBinder service) { bookManager = IBookManager.Stub.asInterface(service); try { bookManager.registerOnNewBookAddListener(listener); } catch (RemoteException e) { e.printStackTrace(); } }
在bindService()
方法之后,当service端线程不断添加新书,我们可以正常打印新书添加日志信息,但是当我们调用removeOnBookAddedListener()
方法时,打印日志信息并没有像我们期望的那样停止。这是因为client端进程中的OnBookAddedListener对象和service端的OnBookAddedListener对象不是同一个对象,正如我们在3.3.1末尾提到的一样。那么我们该如何在serice端和client操作同一个对象。这里系统为我们提供了RemoteCallbackList类,原理是通过键值对的形式保存OnBookAddedListener对象,使得我们添加和移除的是同一个对象。关于RemoteCallbackList的介绍:
1 2 remoteCallbackList.register(listener); remoteCallbackList.unregister(listener);
其中RemoteCallbackList#register() 简单的概述如下(详情请阅读源码):
1 2 3 4 IBinder binder = listener.asBinder(); Callback cb = new Callback(listener, cookie); mCallbacks.put(binder, cb);
可以看出利用的是两个进程使用的是同一个Binder对象来作为key,通过client 端和service端相同key(binder)删除相应的listener。
关于Binder线程池的使用 // 待续……