IPC
(Interprocess Communication) 即进程间通信,需要用到 IPC
主要有以下原因:
- 应用内自身原因需要采用多进程,比如,大应用模块多,需要的内存大,而 Android 对单进程内存有大小限制,所以需要多进程获取更多的内存空间;
- 当前应用需要获取其他应用数据。
Android 多进程模式
开启多进程模式
Android 中开启多进程有两种方式:
- 给四大组件在
AndroidManifest
中指定android:process
属性 - 通过 JNI 在
native
层fork
子进程(属于Linux处理方式)
<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
:
// Book.java
package com.github.tianma8023.ipclearn.aidl;
public class Book implements Parcelable {
public int bookId;
public String bookName;
// constructor ...
// implements Parcelable ...
}
创建 Book.aidl
:
// Book.aidl
package com.github.tianma8023.ipclearn.aidl;
// 声明自定义的Parcelable对象
parcelable Book;
创建 IBookManager.aidl
欲实现对 Book
的管理操作:
// IBookManager.aidl
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
文件,大致内容如下:
// IBookManager.java
/*
* This file is auto-generated. DO NOT MODIFY.
* Original file: F:\\AndroidStudio\\IPCLearn\\app\\src\\main\\aidl\\com\\github\\tianma8023\\ipclearn\\aidl\\IBookManager.aidl
*/
package com.github.tianma8023.ipclearn.aidl;
public interface IBookManager extends android.os.IInterface {
/** Local-side IPC implementation stub class. */
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";
/** Construct the stub at attach it to the interface. */
public Stub() {
// ...
}
/**
* Cast an IBinder object into an com.github.tianma8023.ipclearn.aidl.IBookManager interface,
* generating a proxy if needed.
*/
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
:
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
置于独立进程中,实现对进程间通信的模拟:
<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
并不冲突了。
客户端跟绑定普通服务的客户端一致,比较简单:
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 {
// 服务端的 getBookList() 方法可能会比较耗时,所以需要酌情考虑是否在子线程中进行访问
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
的构造函数:
public Messenger(Handler target) {
mTarget = target.getIMessenger();
}
public Messenger(IBinder target) {
mTarget = IMessenger.Stub.asInterface(target);
}
通过第二个构造函数可以看出来,Messegner
的底层就是 AIDL
。
Messenger
对 AIDL
做了封装,由于 Messenger
机制一次只能处理一个请求,因此在服务端不需要考虑线程同步。
Messenger
远程服务端 Service
:
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
运行在单独进程中:
<service
android:name=".messenger.MessengerServerService"
android:process=":remote" />
Messenger
客户端 Activity
:
public class MessengerClientActivity extends Activity {
private static final String TAG = "MessengerClientActivity";
// 客户端向服务端发送消息的Messenger
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);
// 指定message的replyTo为Messenger对象
msg.replyTo = mGetReplyMessenger;
try {
mMessenger.send(msg);
} catch (RemoteException e) {
e.printStackTrace();
}
}
@Override
public void onServiceDisconnected(ComponentName name) {
// do nothing
}
}
// 客户端接收服务端消息的Messenger
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 | 网络数据交换 |