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

0%