简介
IPC,即进程间通信。常见的IPC场景有两种,一种是单个应用开启多个进程,这些进程间需要通信;另外一种是不同应用间的进程间通信。
单个应用开启多个进程并不复杂,只需要为四大组件声明一个android:process
属性,这个组件便会运行在该声明的进程上。而这个属性的声明方式有两种:
以:号开头,比如
android:process=":remote"
,这时这个组件便运行在package_name:remote
这个进程上。这种方式称为私有进程,虽然名为私有进程,但是可以为其再声明一个android:exported="true"
属性,然后增加intent-filter
属性(如果没有intent-filter属性,只能在本应用内使用),其他应用也可以关联到这个进程。以单个.号分割,比如
android:process="com.leelit"
,这时这个组件便会运行在com.leelit
这个进程上。这种方式称为全局进程,假设有多个应用都有声明了com.leelit
全局进程的组件,也会产生多个名字相同但PID并不相同的进程,所以全局进程并非全局唯一的意思。全局进程的作用是,其他应用可以通过ShareUID的方式和这个全局进程跑在同一个进程上,从而共享资源。
IPC的基础是数据结构的序列化与反序列化。Java平台简单地实现Serializable,通过ObjectOutputStream以及ObjectInputStream即可完成对象的序列与反序列化。而Android平台上新增了一种效率更高的方式,Parcelable,其实现方式比较固定,所以也有一些插件可以直接生成代码。如果说Serializable和Parcelable是实现IPC的“原料”,那么Binder则可以视为“桥梁”,Binder是客户端和服务端进行通信的媒介,当bindService时,客户端可以得到一个可以“操纵”服务端的Binder对象。
Android平台上实现IPC的方式有很多种,比如常见的:
- Intent和bundle
- 文件共享
- Messenger
- AIDL
- ContentProvider
- Socket
这篇文章主要关注:Messenger和AIDL
Messenger
Messenger是系统为我们封装好的一套IPC方案,它的底层是基于AIDL与Handler。如果了解Handler,Looper那一套东西就会知道,Handler每次只会处理一个Message,使用Messenger是不需要也无法考虑并发的情况的。
Messenger的构造方法有两个:
public Messenger(Handler target) {
mTarget = target.getIMessenger();
}
public Messenger(IBinder target) {
mTarget = IMessenger.Stub.asInterface(target);
}
由构造方法也能看出它基于AIDL和Handler。
使用套路:
1、Service服务端实例一个Messenger来接收客户端Messenger发来的message,onBind方法返回这个Messenger的Binder。如果有需要还可以通过客户端发来的message携带的客户端Messenger接收对象,返回message给客户端。
2、客户端bind到服务端的Service后,实例一个Messenger对象,便可以通过这个Messenger对象发送message到服务端。如果有需要还可以实例另外一个Messenger对象来接收服务端返回的message。
按照这个套路一共需要实例三个Messenger,客户端两个,一个用于发送给服务端,一个用于接收服务端;服务端一个用于接收客户端,而返回给客户端的Messenger在客户端发来的message中携带。
服务端进程:
public class MessengerService extends Service {
private static final String Messenger_TAG = "Messenger";
private static class ServerMessengerHandler extends Handler {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case 0:
// 接收到客户端的信息
Log.i(Messenger_TAG, msg.getData().getString("client-data") + " " + Thread.currentThread().toString());
// 接收服务端返回信息的客户端Messenger
Messenger replyMessenger = msg.replyTo;
// 返回信息给客户端
Message message = Message.obtain();
message.what = 1;
Bundle data = new Bundle();
data.putString("server-data", "hello client");
message.setData(data);
try {
replyMessenger.send(message);
} catch (RemoteException e) {
e.printStackTrace();
}
break;
default:
super.handleMessage(msg);
}
}
}
// 服务端Messenger
private Messenger serverMessenger = new Messenger(new ServerMessengerHandler());
@Nullable
@Override
public IBinder onBind(Intent intent) {
// 返回两个Messenger沟通的Binder桥梁
return serverMessenger.getBinder();
}
}
客户端进程:
public class MainActivity extends AppCompatActivity {
private static final String Messenger_TAG = "Messenger";
private Messenger clientMessenger;
private Button button;
// 接收服务端返回信息的Messenger
private Messenger replyMessenger = new Messenger(new ClientMessengerHandler());
private static class ClientMessengerHandler extends Handler {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case 1:
// 接收到服务端的信息
Log.i(Messenger_TAG, msg.getData().getString("server-data") + " " + Thread.currentThread().toString());
break;
default:
super.handleMessage(msg);
}
}
}
private ServiceConnection serviceConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
// 客户端Messenger
clientMessenger = new Messenger(service);
}
@Override
public void onServiceDisconnected(ComponentName name) {
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
button = (Button) findViewById(R.id.messenger);
// bind服务端Service
Intent intent = new Intent(this, MessengerService.class);
bindService(intent, serviceConnection, BIND_AUTO_CREATE);
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
// 发送消息到服务端
Message message = Message.obtain();
message.what = 0;
Bundle data = new Bundle();
data.putString("client-data", "hello server");
message.setData(data);
// 指定接收服务端返回信息的Messenger
message.replyTo = replyMessenger;
try {
clientMessenger.send(message);
} catch (RemoteException e) {
e.printStackTrace();
}
}
});
}
@Override
protected void onDestroy() {
super.onDestroy();
unbindService(serviceConnection);
}
}
点击Button后打印的信息如下:
10-21 21:44:28.557 29650-29650/com.example.kenjxli.ipc:messenger I/Messenger: hello server Thread[main,5,main]
10-21 21:44:28.567 29615-29615/com.example.kenjxli.ipc I/Messenger: hello client Thread[main,5,main]
处理的线程是Handler实例时所在的线程,所以两个进程的Message处理都是在主线程。
这里补充两点bindService相关的内容:
- bindService()是异步调用,会立即返回;
- 系统会在与服务连接上时回调onServiceConnected()方法,并传递服务的onBind() 方法返回的 IBinder;与服务的连接
意外中断
时(例如当服务崩溃或被终止时)回调onServiceDisconnected()方法。当客户端主动取消绑定时,系统“绝对不会”调用该方法。
AIDL
根据官方文档,可以看出,底层AIDL和经过封装的Messenger相比,最大的不同就是,AIDL是具备多线程处理能力的。
Messenger 会在单一线程中创建包含所有客户端请求的队列,以便服务一次接收一个请求。不过,如果您想让服务同时处理多个请求,则可直接使用 AIDL。 在此情况下,您的服务必须具备多线程处理能力,并采用线程安全式设计。
使用AIDL的基本步骤:
1、确定服务端提供的服务,定义AIDL接口;
2、编写服务端Service,onBind方法返回Binder;
3、客户端bindService,在回调处将Binder转化为AIDL接口,后续即可使用这个接口调用服务端的方法。
可以参考之前的一篇文章,链接。
这里再补充几个内容。
- AIDL传递对象;
- 服务端回调客户端;
- 调用时同步异步状态及所在线程;
AIDL传递对象
如果使用AIDL传递基本数据类型,就比较简单,只需要服务端定义好AIDL接口文件,并在Service中实现AIDL.Stub接口,返回binder。客户端bindService后将binder重新转化为AIDL接口,即可IPC调用。但是要传递对象,就需要额外的功夫。
需要用AIDL传递对象时,除了实际使用的AIDL接口之外,还需要该对象的类实现Parcelable接口,在同一个包内声明该类的AIDL文件,并且AIDL接口中必须import该类,即便处在同一个包。
比如服务端AIDL接口,需要返回一个User对象。
interface MyAidl {
User getUser();
}
首先需要创建一个实现Parcelable的User类;
然后在同一个包,新建一个AIDL文件;
// User.aidl
package com.example.kenjxli.ipc.aidl;
parcelable User;
最后需要在AIDL接口处import该类,就算处在同一个包也必须import。
// MyAidl.aidl
package com.example.kenjxli.ipc.aidl;
import com.example.kenjxli.ipc.aidl.User; //必须import
interface MyAidl {
User getUser();
}
以上步骤缺一不可,否则编译都将失败。
另外,AIDL接口中的方法有一些非基本类型的参数,需要指定其“方向”。
All non-primitive parameters require a directional tag indicating which way the data goes. Either in, out, or inout (see the example below).
Primitives are in by default, and cannot be otherwise.
假设有一个AIDL接口如下:
interface MyAidl {
void inUser(in User user);
void outUser(out User user);
void inOutUser(inout User user);
}
则三个参数的含义如下:
in:客户端输入参数,此时服务端可以得到这个参数对象的内容,但是修改后无法同步回给客户端。
out:客户端输入参数,即便这个对象是有内容的,到了服务端也会变成空值,服务端修改后能同步回给客户端。
inout:则是上面两者的集合,服务端既能得到客户端对象的内容,修改后也能同步回给客户端。
服务端回调
AIDL一般是客户端进程调用服务端进程,但是有些时候是可能需要服务端回调客户端,这时需要使用一个系统接口,RemoteCallbackList<E extends IInterface>
。
具体使用步骤如下:
1、定义一个客户端AIDL回调接口
2、在原有的AIDL接口增添注册回调接口的方法
3、客户端注册时,将该接口添加到callbackList中;
4、回调时使用固定的代码格式。
部分代码如下:
1、定义一个AIDL回调接口
// OnServerCallBack.aidl
package com.example.kenjxli.ipc.aidl;
import com.example.kenjxli.ipc.aidl.User;
// Declare any non-default types here with import statements
interface OnServerCallBack {
void onServerCallBack(in User user); // 此时我们的客户端相当于是服务端的服务端了!
}
2、原有AIDL接口增添注册回调的方法
// MyAidl.aidl
package com.example.kenjxli.ipc.aidl;
import com.example.kenjxli.ipc.aidl.User;
import com.example.kenjxli.ipc.aidl.OnServerCallBack;
// Declare any non-default types here with import statements
interface MyAidl {
User getUser();
void inUser(in User user);
void outUser(out User user);
void inOutUser(inout User user);
void registerCallBack(in OnServerCallBack callbcak);
}
并在服务端Service中的AIDL.Stub中实现该方法
@Override
public void registerCallBack(final OnServerCallBack callbcak) throws RemoteException {
// 这里可以添加一个cookie对象,方便回调时识别是哪个回调对象
callbackList.register(callbcak, "cookie1");
}
3、客户端注册
myAidl.registerCallBack(new OnServerCallBack.Stub() {
@Override
public void onServerCallBack(User user) throws RemoteException {
Log.e("tag", "call back " + user.toString());
}
});
4、服务端回调代码
private void callback() {
int size = callbackList.beginBroadcast();
for (int i = 0; i < size; i++) {
OnServerCallBack callBack = callbackList.getBroadcastItem(i);
if (callBack != null) {
try {
Log.e("tag", (String) callbackList.getBroadcastCookie(i));
callBack.onServerCallBack(new User("callback user", 10000));
} catch (RemoteException e) {
e.printStackTrace();
}
}
}
callbackList.finishBroadcast();
}
这样就是一个完整的服务端回调客户端的流程了。上面没有涉及到反注册,其实也是大同小异的!值得一提的是,如果客户端进程退出后,服务端会自动移除这个回调对象。
调用时同步异步状态及所在线程
这一小节直接说明结论:
1、ServiceConnection的回调线程始终是在主线程;
2、AIDL调用是在Binder线程池,不管是客户端调用服务端,还是服务端回调客户端,方法体执行的地方都是在Binder线程池中;
3、AIDL调用是同步调用,不管是客户端调用服务端,还是服务端回调客户端。当调用某个远程方法后,本地进程当前的线程会挂起,直到远程方法返回后,本地进程的线程才能唤醒。