这是我参与11月更文挑战的第27天,活动详情查看:2021最后一次更文挑战
IPC Inter-Process Communication
进程间通信或者跨进程通信,指两个进程之间数据交换过程。
IPC方式
Bundle
在Intent
中可以使用Bundle
进行数据传递。例如支持序列化Serializable
和Parcellable
的对象以及Bundle
支持的基本类型都能实现跨进程通信。另外在一般情况下Bundle
是附加在Intent
中进行传递,所以在四大组件中的三大组件(Activity
,Service
,Receiver
)使用广泛。
文件共享
文件共享也是另外一种进程间通信方式。两个进程间通过读写同一个文件来实现数据共享。例如A进程
对文件进行写操作,B进程
对文件进行读操作,从而实现两个进程间的数据交换。但文件共享存在一个致命问题,当两个进程对同一个文件进行并发操作时可能会存在数据不同步的情况。所以在并发情况下文件共享会造成获取的数据非最新状态,从而影响跨进程通信。因此当用文件共享作为进程通信方式需要格外注意并发的情况。同样的Sharepreferences
同样不适合在高并发读写访问中使用,每份内存中都缓存一份Sharepreferencse
文件。
Messenger
Messenger
作为信使,可以在不同进程中传递Message
对象。Messenger
是一种轻量级IPC
方案,底层实现AIDL
。从Messenger
的构造函数可以看出端倪,无论是IMessenger.Stub.asInterface
还是target.getIMessenger
,都能看到AIDL
的影子。
public Messenger(IBinder target) {
mTarget = IMessenger.Stub.asInterface(target);
}
public Messenger(Handler target) {
mTarget = target.getIMessenger();
}
复制代码
其实Messenger
是对AIDL
的封装,从而能够让开发者更方便简单的使用进程间通信。由于它只支持串行处理模式,所以不用考虑线程同步问题,因此也不存在并发问题。
- 服务端代码
创建服务端Service
服务,新建Messenger
对象,创建MessengerHandler
接收客户端发送的消息,Service
绑定Messenger
的IBinder
。这里的IBinder
是Handler
中IMessenger.asBinder()
对象。另外客户端发送的msg.replyTo
保存是客户端的Messenger
对象,通过replyTo
可以实现服务端向客户端进行通信,具体再看下面的客户端代码。
<service
android:name=".MessengerExercise.MessengerService"
android:process=":messenger"
/>
public class MessengerService extends Service{
private static class MessengerHandler extends Handler{
@Override
public void handleMessage(Message msg) {
switch (msg.what){
case 0:
LogUtils.printI(getClass().getName(),"服务端收到消息 receive msg from Client:" + msg.getData().getString("msg"));
//回复客户端消息
Messenger client = msg.replyTo;
Message relpyMessage = Message.obtain(null,0);
Bundle bundle = new Bundle();
bundle.putString("msg","Service Msg 服务端回值,我收到了谢谢");
relpyMessage.setData(bundle);
try{
client.send(relpyMessage);
}catch (RemoteException e){
e.printStackTrace();
}
break;
default:
super.handleMessage(msg);
}
}
}
private final Messenger messenger = new Messenger(new MessengerHandler());
@Nullable
@Override
public IBinder onBind(Intent intent) {
return messenger.getBinder();
}
}
复制代码
- 客户端代码
首先创建ServiceConnection
,在onServiceConnected
方法中实现客户端向服务端发送Message
,通过在Message
中附加Bundle
跨进程传递数据,并把Message
的replyTo
赋值为客户端接收Messenger
对象,创建意图Intent
,bindService
绑定跨进程服务传递数据。这里需要注意的是Message
中有个Object
对象它并不支持跨进程传递数据,它支持系统默认的Parceable
对象而不支持自定义Parceable
,好在Message
可以附加Bundle
。其次再看客户端Messenger
对象,其实和服务端Messenger
实现方式相同,新建Messenger
对象,创建MessengerHandler
接收服务端发送的message
消息。
public class MessengerClientActivity extends BaseToolBarTitleActivity {
private Messenger messenger;
private static class MessengerHandler extends Handler {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case 0:
LogUtils.printI(getClass().getName(), "客户端消息收到消息 receviced msg from MessegerService: " + msg.getData().getString("msg"));
break;
default:
super.handleMessage(msg);
}
}
}
private Messenger mGetReplyMessenger = new Messenger(new MessengerHandler());
private ServiceConnection connection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
messenger = new Messenger(service);
Message message = Message.obtain(null, 0);
Bundle bundle = new Bundle();
bundle.putString("msg", "Client Msg 客户端消息");
message.setData(bundle);
message.replyTo = mGetReplyMessenger;
try {
messenger.send(message);
} catch (RemoteException e) {
e.printStackTrace();
}
}
@Override
public void onServiceDisconnected(ComponentName name) {
}
};
.....省略部分无关联关系代码
@Override
protected void initDate() {
Intent intent = new Intent(this, MessengerService.class);
bindService(intent, connection, Context.BIND_AUTO_CREATE);
}
@Override
protected void onDestroy() {
super.onDestroy();
unbindService(connection);
}
复制代码
以上就是Messenger
的跨进程通信方式,其实就是客户端和服务端这两端各自存在一个Messenger
用于接收或者发送跨进程消息,并在Handler
中接收相应消息。Messenger
算是简易的串行跨进程通信,若需要实现大量并发请求通信时,Messenger
可能就不太合适了,以及有的时候可能需要我们去跨进程调用服务端的方法,Messenger
也无法做到。前面提到过Messenger
的底层实现方式就是AIDL
,它是对AIDL
的封装。若想实现更多跨进程方法,我们就需要理解和开发底层AIDL
了。
AIDL
在介绍AIDL使用之前先了解AIDL文件所支持的数据类型:
- 基本数据类型(int、long、char、boolean、double等)
- String和CharSequence
- List:只支持ArrayList
- Map: 只支持HashMap
- Parcelable
- 所有AIDL接口本身
比起Messenger
跨进程通信AIDL其实稍微复杂和自定义一些,包括AIDL文件需要自己编写接口以及服务绑定过程中一些操作需要自行设置。
- 编写AIDL文件
Android
项目创建aidl文件夹在aidl文件夹下编写aidl文件,需要注意的是编写的aidl文件还需要编译后才能引用,所以在Android Studio
平台下rebuild project后才能在项目中拿到编译后的文件加以引用。
Book.java
public class Book implements Parcelable {
public int bookId;
public String bookName;
public Book() {}
public Book(int bookId, String bookName) {
this.bookId = bookId;
this.bookName = bookName;
}
......
}
复制代码
Book.aidl
parcelable Book;
复制代码
IBookManager.aidl
import com.julyyu.arsenal.beta.Book;
import com.julyyu.arsenal.beta.IOnNewBookArrivedListener;
interface IBookManager {
List<Book> getBookList();
void addBook(in Book book);
void registerListener(IOnNewBookArrivedListener listener);
void unregisterListener(IOnNewBookArrivedListener listener);
}
复制代码
IOnNewBookArrivedListener.aidl
interface IOnNewBookArrivedListener {
void onNewBookArrived(in Book newBook);
}
复制代码
- 编写服务端代码
和之前一样继承Service
创建服务,不同的是IBinder
使用的是如下IBookManage.Stub()
对象。该对象是由我们编写的aidl文件编译后生成的。实例化对象中实现具体接口方法,在此不做详情介绍。
private Binder mBinder = new IBookManager.Stub() {
......
//具体实现IBookMananger接口方法以及权限判断等操作。
}
复制代码
- 编写客户端代码
与之前Messenger
创建服务类似,创建Intent
执行bindService
绑定ServiceConnection
。从绑定的IBinder
中获取IBookManager
接口实现从服务端拉取相应的数据。另外也能实现注册服务端监听器,从而实现在服务端特定情况下主动向客户端发送数据。所有的操作由开发者自行设置实现,类比Messenger
是不是觉得AIDL
自由度更高一些。
public void onServiceConnected(ComponentName className, IBinder service) {
IBookManager bookManager = IBookManager.Stub.asInterface(service);
mRemoteBookManager = bookManager;
try {
mRemoteBookManager.asBinder().linkToDeath(mDeathRecipient, 0);
List<Book> list = bookManager.getBookList();
bookManager.registerListener(mOnNewBookArrivedListener);
} catch (RemoteException e) {
e.printStackTrace();
}
}
复制代码
由于AIDL篇幅太大,多数代码就不一一展示。具体内容参考开发艺术探索的讲义代码
ContentProvider
它是Android
提供的专门用于不同应用间进行数据共享的方式,适合进程间通信。同理Messenger
,ContentProvider
底层也是Binder,也是对底层做了封装方便开发者使用,无需关心底层细节实现IPC
。除了系统预置了许多ContentProvider
,我们也能自定义ContentProvider
供自己使用,其操作方式与SQLite
类似,例如CRUD
常规操作。这里也不做详细介绍,可以自行参考代码实践。
Socket
Socket
也是实现进程间通信的方式之一。Socket
俗称套接字,分为流式套接字(TCP)和用户数据报套接字(UDP)。TCP
面向连接的协议,提供稳定双向通信功能.UDP
是无连接的,提供不稳定的单向通信功能。 Socket
具体实现进程间的通信代码如下参考
总结
名称 | 优点 | 缺点 | 使用场景 |
---|---|---|---|
Bundle | 简单易用 | 只能传输Bundle支持的数据类型 | 四大组件间的进程间通信 |
文件共享 | 简单易用 | 不适合高并发场景,并且无法做到进程间的即时通 | 无法并发访问情形, 交换简单的数据实时性不高的场景 |
AIDL | 功能强大 | 使用稍复杂,需要处理好线程同步 | 一对多通信且有RPC需求 |
ContentProvider | 在数据源访问方面功能强大,支持一对多并发数据共享 | 可以理解为受约束的AIDL,主要提供数据源的CRUD操作 | 一对多的进程间的数据共享 |
Messenger | 功能一般, 支持一对多串行通信,支持实时通信 | 不能很好处理高并发,不支持RPC,数据通过Message进行传输, 因此只能传输Bundle支持的数据类型 | 低并发的一对多即时通信,无RPC需求,或者无需要返回结果的RPC需求 |
Socket | 功能强大,可以通过网络传输字节流,支持一对多并发实时通信 | 实现细节稍微有点繁琐,不支持直接的RPC | 网络数据交换 |
参考
- # Binder系列—开篇
- 《Android开发艺术探索》