0%

Android笔记:IPC

IPC (Interprocess Communication) 即进程间通信,需要用到 IPC 主要有以下原因:

  • 应用内自身原因需要采用多进程,比如,大应用模块多,需要的内存大,而 Android 对单进程内存有大小限制,所以需要多进程获取更多的内存空间;
  • 当前应用需要获取其他应用数据。

Android 多进程模式

开启多进程模式

Android 中开启多进程有两种方式:

  1. 给四大组件在 AndroidManifest 中指定 android:process 属性
  2. 通过 JNI 在 nativefork 子进程(属于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 的方式和其跑在同一个进程中。

多进程模式弊端

多进程模式会造成如下问题:

  1. 静态成员变量以及单例模式失效:
    Android系统为每个进程都分配有独立的虚拟机,不同的虚拟机有着不同的内存空间分配,在不同虚拟机下访问同一个类对象会分配到不同的地址空间,也就是属于不同的对象。
  2. 线程同步机制失效:
    在不同的内存地址空间中,同步锁(无论是对象锁还是全局锁)也不一样,故没办法在不同进程间进行线程同步。
  3. SharedPreferences 可靠性降低
    SharedPreferences 底层通过读写 XML 文件来实现,在不同进程中并发读写是会产生同步问题的。
  4. 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
// Book.java
package com.github.tianma8023.ipclearn.aidl;

public class Book implements Parcelable {

public int bookId;
public String bookName;

// constructor ...
// implements Parcelable ...
}

创建 Book.aidl

1
2
3
4
5
// Book.aidl
package com.github.tianma8023.ipclearn.aidl;

// 声明自定义的Parcelable对象
parcelable Book;

创建 IBookManager.aidl 欲实现对 Book 的管理操作:

1
2
3
4
5
6
7
8
9
10
// 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 中仅支持一下几种数据类型:

  1. 基本数据类型(int, long, char, boolean, double 等);
  2. String 和 CharSequence;
  3. List:只支持 ArrayList,且 List 中的每个元素必须是 AIDL 支持的数据类型;
  4. Map:只支持 HashMap,且 Map 中的每个元素都必须是 AIDL 支持的数据类型;
  5. Parcelable:实现了Parcelable的对象;
  6. 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
// 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 的工作机制图:

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 {
// 服务端的 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 的构造函数:

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

MessengerAIDL 做了封装,由于 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";

// 客户端向服务端发送消息的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();
}
}

注意: Messageobj 字段,在 Android 2.0 以前不支持跨进程传输,在 Android 2.0 以后也仅仅在系统提供的实现了 Parcelable 接口的对象上才能支持跨进程传输。

Messenger 原理

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