IPC
(Interprocess Communication) 即进程间通信,需要用到 IPC
主要有以下原因:
应用内自身原因需要采用多进程,比如,大应用模块多,需要的内存大,而 Android 对单进程内存有大小限制,所以需要多进程获取更多的内存空间;
当前应用需要获取其他应用数据。
Android 多进程模式 开启多进程模式 Android 中开启多进程有两种方式:
给四大组件在 AndroidManifest
中指定 android:process
属性
通过 JNI 在 native
层 fork
子进程(属于Linux处理方式)
1 2 3 4 5 6 7 8 9 10 11 <activity android:name =".FirstActivity" android:label ="FirstActivity" /> <activity android:name =".SecondActivity" android:label ="SecondActivity" android:process =":remote" /> <activity android:name =".ThirdActivity" android:label ="ThirdActivity" android:process ="com.github.tianma8023.ipclearn.remote" />
这里,应用程序的包名是 com.github.tianma8023.ipclearn
,其中 FirstActivity
运行在以包名为进程名的默认进程中;而SecondActivity
在启动时,会运行在名为 com.github.tianma8023.ipclearn:remote
的进程中;ThirdActivity
在启动时,会运行在名为 com.github.tianma8023.ipclearn.remote
的进程中。 以 :
开头的进程是当前应用的私有进程,其他应用程序的四大组件不会和它跑在同一个进程中;不以 :
开头的进程是全局进程,其他应用可以通过 ShareUID
的方式和其跑在同一个进程中。
多进程模式弊端 多进程模式会造成如下问题:
静态成员变量以及单例模式失效: Android系统为每个进程都分配有独立的虚拟机,不同的虚拟机有着不同的内存空间分配,在不同虚拟机下访问同一个类对象会分配到不同的地址空间,也就是属于不同的对象。
线程同步机制失效: 在不同的内存地址空间中,同步锁(无论是对象锁还是全局锁)也不一样,故没办法在不同进程间进行线程同步。
SharedPreferences
可靠性降低 SharedPreferences
底层通过读写 XML 文件来实现,在不同进程中并发读写是会产生同步问题的。
Application
会多次创建: 每个进程有独立的虚拟机,自然也有独立的 Application
对象
从以上信息可以得出:位于不同进程的四大组件之间,但凡通过内从共享数据、变量的,都会共享失败。
Android 中的 IPC 方式 Bundle Activity
, Service
, BroadcastReceiver
都支持在 Intent
中传递 Bundle
数据,而因为 Bundle
实现了 Parcelable
接口,所以可以在不同的进程间传输,也就是进行了进程间单向通信。
共享文件 既然共享内存会失效,那就通过共享文件的方式,但是也会存在并发读写的问题,所以文件共享方式适合对数据同步要求不高的进程间通信。 虽然 SharedPreferences
本质是文件读写,但由于 Android 系统对其有缓存策略,即在内存中也会持有 SharedPreferences
的缓存,因此进程间通信不宜用 SharedPreferences
。
AIDL AIDL(Android Interface Definition Language) 的进程间通信方式主要依靠 Binder
实现。
定义一个实体类 Book
:
1 2 3 4 5 6 7 8 9 10 11 package com.github.tianma8023.ipclearn.aidl;public class Book implements Parcelable { public int bookId; public String bookName; }
创建 Book.aidl
:
1 2 3 4 5 package com.github.tianma8023.ipclearn.aidl;parcelable Book;
创建 IBookManager.aidl
欲实现对 Book
的管理操作:
1 2 3 4 5 6 7 8 9 10 package com.github.tianma8023.ipclearn.aidl;import com.github.tianma8023.ipclearn.aidl.Book;interface IBookManager { List<Book> getBookList () ; void addBook (in Book book) ; }
AIDL 中仅支持一下几种数据类型:
基本数据类型(int, long, char, boolean, double 等);
String 和 CharSequence;
List:只支持 ArrayList,且 List 中的每个元素必须是 AIDL 支持的数据类型;
Map:只支持 HashMap,且 Map 中的每个元素都必须是 AIDL 支持的数据类型;
Parcelable:实现了Parcelable的对象;
AIDL:所有的 AIDL 接口本身也可以在其他 AIDL 文件中使用。
需要注意:
自定义的 Parcelable
对象 和 AIDL
对象必须显式的 import 进来,无论它们是否在同一个包内。
如果 AIDL
文件中引用了自定义的 Parcelable
对象,则该 Parcelable
对象必须创建一个与它同名的 AIDL
文件,并在其中声明它为 parcelable
类型
AIDL
文件中除了基本数据类型之外,其他类型参数都需标上:in
(输入型参数), out
(输出型参数) 或者 inout
(输入输出型参数)。因为不同标识的参数底层开销不一样,所以最好别滥用 inout
之后 build
操作,Android Studio 会在模块 /build
目录下面生成对应的 IBookManager.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 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 package com.github.tianma8023.ipclearn.aidl; public interface IBookManager extends android .os .IInterface { public static abstract class Stub extends android .os .Binder implements com .github .tianma8023 .ipclearn .aidl .IBookManager { private static final java.lang.String DESCRIPTOR = "com.github.tianma8023.ipclearn.aidl.IBookManager" ; public Stub () { } public static com.github.tianma8023.ipclearn.aidl.IBookManager asInterface (android.os.IBinader 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) { } private static class Proxy implements com .github .tianma8023 .ipclearn .aidl .IBookManager { private android.os.IBinder mRemote; Proxy(android.os.IBinder remote) { } @Override public android.os.IBinder asBinder () { return mRemote; } public java.lang.String getInterfaceDescriptor () { return DESCRIPTOR: } @Override public java.util.List<com.github.tianma8023.ipclearn.aidl.Book> getBookList() throws android.os.RemoteException { } @Override public void addBook (com.github.tianma8023.ipclearn.aidl.Book book) throws android.os.RemoteException { } } } public java.util.List<com.github.tianma8023.ipclearn.aidl.Book> getBookList() throws android.os.RemoteException; public void addBook (com.github.tianma8023.ipclearn.aidl.Book book) throws android.os.RemoteException ; }
AS 自动生成的 aidl 对应的 java 类,可以很好的了解 Binder 的工作机制,所以有必要读一读这里的源码。 这里大致介绍下各个字段及方法的含义:
Stub#DESCRIPTOR
: Binder
的唯一标识
Stub#asInterface(android.os.IBinader obj)
: 将 服务端 的 Binder 对象转化成 客户端 所需要的 AIDL 接口类型的对象。当服务端和客户端位于同一进程中时,此方法返回的就是服务端对象本身。当它们不在同一个进程中时,返回的是由系统封装后的 Stub.Proxy
对象(也就是需要转换)
Stub#asBinder()
: 返回当前 Binder 对象
Stub#onTransact(int code, Parcel data, Parcel reply, int flags)
: 该方法会运行在服务端 的 Binder 线程池中。客户端发起的跨进程请求会通过系统底层封装后交由此方法处理。服务端通过 code 参数确认接下来调用执行的目标方法。 如果该方法返回 false,则客户端的请求会失败,所以可以利用此特性进行权限认证。
Proxy#getBookList()
: 此方法运行在客户端 。客户端使用此方法进行跨进程调用时,会转化为让服务端(远端)的 Binder 执行 transact
方法,与此同时客户端线程挂起,并最终调用服务端的 onTransact
,即调用上一条的 Stub#onTransact()
方法,当服务端相应方法执行完毕后返回结果后,客户端当前线程结束挂起,继续执行,并取出服务端返回的结果。
Proxy#addBook()
:与上述的 Proxy#getBookList()
类似
Binder
的工作机制图:
当客户端发起远程请求时,客户端当前线程会被挂起,等待服务端进程返回结果,所以,如果服务端进程很耗时,则不能在 UI 线程中发起远程请求。
远程服务端实现 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 30 31 32 33 public class BookManagerService extends Service { private static final String TAG = "BookManagerService" ; private CopyOnWriteArrayList<Book> mBookList = new CopyOnWriteArrayList<Book>(); private Binder mBinder = new IBookManager.Stub() { @Override public List<Book> getBookList () throws RemoteException { return mBookList; } @Override public void addBook (Book book) throws RemoteException { mBookList.add(book); } }; public BookManagerService () { } @Override public IBinder onBind (Intent intent) { return mBinder; } @Override public void onCreate () { super .onCreate(); mBookList.add(new Book(1 , "Think In Java" )); mBookList.add(new Book(2 , "Android Programing" )); } }
在 AndroidManifest
中将 BookManagerService
置于独立进程中,实现对进程间通信的模拟:
1 2 3 <service android:name =".aidl.BookManagerService" android:process =":remote" />
因为 IBookManager.Stub
类是 Binder
的一个抽象子类,所以在 Serivce
中实现 IBookManager.Stub
即可在 onBind()
中返回相应的 Binder
对象。
考虑到 AIDL 中的方法是在服务端的 Binder
线程池中执行的,所以考虑同步就使用了 CopyOnWriteArrayList
。注意到 CopyOnWriteArrayList
并不是 ArrayList
的子类,但其实现的最基本的底层原理和 ArrayList
一致(基于数组,可以通过下标 index 进行访问等)。虽然服务端返回的是 CopyOnWriteArrayList
,但 Binder
可以通过相同的机理去访问 CopyOnWriteArrayList
中的数据并最终形成新的 ArrayList
返回给客户端。这就与之前提到的 AIDL支持的 List 只有 ArrayList
并不冲突了。
客户端跟绑定普通服务的客户端一致,比较简单:
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 public class BookManagerActivity extends Activity { private static final String TAG = "BookManagerActivity" ; private ServiceConnection mConnection = new ServiceConnection() { @Override public void onServiceConnected (ComponentName name, IBinder service) { IBookManager bookManager = IBookManager.Stub.asInterface(service); try { List<Book> bookList = bookManager.getBookList(); Log.i(TAG, "book list type: " + bookList.getClass().getCanonicalName()); Log.i(TAG, "query book list: " + bookList); } catch (RemoteException e) { e.printStackTrace(); } } public void onServiceDisconnected (ComponentName name) { } }; @Override protected void onCreate (Bundle savedInstanceState) { super .onCreate(savedInstanceState); setContentView(R.layout.activity_book_manager); Intent intent = new Intent(this , BookManagerService.class); bindService(intent, mConnection, Context.BIND_AUTO_CREATE); } @Override protected void onDestroy () { unbindService(mConnection); super .onDestroy(); } }
以上是 AIDL
的基本用法,进阶用法请参考 Android 笔记:AIDL进阶
Messenger 使用 Messenger
可以在不同的进程之间传递 Message
对象,实现进程间数据交互通信。
Messenger
的构造函数:
1 2 3 4 5 6 7 public Messenger (Handler target) { mTarget = target.getIMessenger(); } public Messenger (IBinder target) { mTarget = IMessenger.Stub.asInterface(target); }
通过第二个构造函数可以看出来,Messegner
的底层就是 AIDL
。
Messenger
对 AIDL
做了封装,由于 Messenger
机制一次只能处理一个请求,因此在服务端不需要考虑线程同步。
Messenger
远程服务端 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 30 31 32 33 34 35 public class MessengerServerService extends Service { private static final String TAG = "MessengerServerService" ; private final Messenger mMessenger = new Messenger(new MessengerHandler()); private static class MessengerHandler extends Handler { @Override public void handleMessage (Message msg) { switch (msg.what) { case TConstants.MSG_FROM_CLIENT: Log.i(TAG, "msg from client: " + msg.getData().get(TConstants.KEY_MSG)); Messenger client = msg.replyTo; Bundle replyBundle = new Bundle(); replyBundle.putString(TConstants.KEY_SERVER, "Okay, I'm server, your message has been received." ); Message replyMsg = Message.obtain(null , TConstants.MSG_FROM_SERVER); replyMsg.setData(replyBundle); try { client.send(replyMsg); } catch (RemoteException e) { e.printStackTrace(); } break ; } super .handleMessage(msg); } } @Override public IBinder onBind (Intent intent) { return mMessenger.getBinder(); } }
注册 Service
运行在单独进程中:
1 2 3 <service android:name =".messenger.MessengerServerService" android:process =":remote" />
Messenger
客户端 Activity
:
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 public class MessengerClientActivity extends Activity { private static final String TAG = "MessengerClientActivity" ; private Messenger mMessenger; private ServiceConnection mConnection = new ServiceConnection() { @Override public void onServiceConnected (ComponentName name, IBinder service) { mMessenger = new Messenger(service); Message msg = Message.obtain(null , TConstants.MSG_FROM_CLIENT); Bundle data = new Bundle(); data.putString(TConstants.KEY_MSG, "Hello, this is client." ); msg.setData(data); msg.replyTo = mGetReplyMessenger; try { mMessenger.send(msg); } catch (RemoteException e) { e.printStackTrace(); } } @Override public void onServiceDisconnected (ComponentName name) { } } private Messenger mGetReplyMessenger = new Messenger(new MessengerHandler()); private static class MessengerHandler extends Handler { @Override public void handleMessage (Message msg) { switch (msg.what) { case TConstants.MSG_FROM_SERVER: Log.i(TAG, "msg from server: " + msg.getData().getString(TConstants.KEY_REPLY)); break ; } super .handleMessage(msg); } } @Override protected void onCreate (Bundle savedInstanceState) { super .onCreate(savedInstanceState); setContentView(R.layout.activity_messenger_client); Intent intent = new Intent(this , MessengerServerService.class); bindService(intent, mConnection, Context.BIND_AUTO_CREATE); } @Override protected void onDestroy () { unbindService(mConnection); super .onDestroy(); } }
注意: Message
的 obj
字段,在 Android 2.0 以前不支持跨进程传输,在 Android 2.0 以后也仅仅在系统提供的实现了 Parcelable
接口的对象上才能支持跨进程传输。
ContentProvider ContentProvider
底层也是由 AIDL
实现,其主要有6个抽象方法:onCreate
, getType
, query
, update
, insert
, delete
, 其中除了 onCreate
由系统调用运行在主线程之外,其他的都由外界回调运行在 Binder
线程池中。
注意,ContentProvider
底层依赖的数据存储没有要求,可以用 SQLite 数据库,也可以用普通文件,亦可以用 SharedPreferences,不过通常情况下底层存储都是依赖的数据库。
在客户端要观察 ContentProvider
中的数据变化情况,可以通过 ContentResolver#registerContentObserver
方法来注册观察者,ContentResolver#unregisterContentObserver
取消注册。
Socket Socket
也可以实现进程间通信,可以凭借 TCP
或者 UDP
的套接字来实现。与一般的 Socket
编程没有太大区别,主要是在 Android 编程中需要考虑在主线程上更新UI,在子线程发起 Socket
请求即可。
各个 IPC 方式对比
名称
优点
缺点
适用场景
Bundle
简单
只能传输Bundle支持的数据
四大组件间的进程间通信
文件共享
简单易用
不适合并发场景,无法做到进程间即时通信
无并发访问的情景,交换简单的数据,实时性要求不高的场景
AIDL
功能强大,支持一对多并发通信,支持即时通信
使用稍复杂,需要处理线程同步
一对多通信,有RPC需求
Messenger
功能一般,支持一对多串行通信,支持即时通信
不适用于高并发场景,数据只能通过Message进行传输,只能传输Bundle支持的数据类型
适用于低并发的一对多的即时通信,无RPC需求,或者不需要返回结果的PRC需求
ContentProvider
在数据源访问方面功能强大,支持一对多并发数据共享,可以通过Call扩展其他方法操作
受约束的AIDL,主要提供数据源的CRUD操作
一对多进程间的数据共享
Socket
功能强大,可以通过网络传输字节流,支持一对多并发即时通信
实现起来稍微麻烦,不支持直接的RPC
网络数据交换
代码参考 IPCLearn