Binder学习四利用AIDL跟Messenger实现IPC

概述

在利用Binder进行IPC的时候,会经常需要创建一个Server端,Android中通常的实现是利用Service来实现,所以再进行IPC之前先了解之前先复习一下Service:

启动方式

startService
1
2
3
Intent intent = new Intent();
intent.setClass(this, MyService.class);
startService(intent);
bindService
1
2
3
4
5
6
7
8
9
10
11
12
13
 Intent intent = new Intent();
intent.setClass(this, MyService.class);
bindService(intent, new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
//绑定成功的回调
}

@Override
public void onServiceDisconnected(ComponentName componentName) {
//断开连接的回调
}
}, Context.BIND_AUTO_CREATE);

生命周期

Service生命周期

常见方法回调时机

onCreate()

在每个service的生命周期中这个方法会且仅会调用一次,并且它的调用在onStartCommand()以及onBind()之前,我们可以在这个方法中进行一些一次性的初始化工作。

onStartCommand()

当其他组件通过startService()方法启动service时,此方法将会被调用。

onBind()

当其他组件通过bindService()方法与service相绑定之后,此方法将会被调用。这个方法有一个IBinder的返回值,这意味着在重写它的时候必须返回一个IBinder对象,它是用来支撑其他组件与service之间的通信的——另外,如果你不想让这个service被其他组件所绑定,可以通过在这个方法返回一个null值来实现。

onUnbind()

当调用UnBindService的时候 ,此方法会被调用

onDestroy

这是service一生中调用的最后一个方法,当这个方法被调用之后,service就会被销毁。所以我们应当在这个方法里面进行一些资源的清理,比如注册的一些监听器什么的。

销毁方式

startService方式启动

startService()启动,stopService()销毁的生命周期如下:
->onCreate()->onStartCommand()->Service running-> onDestroy()

bindService方式启动

bindService()启动,unbindService()销毁的生命周期如下:
->onCreate()->onStartCommand()->Service running-> onDestroy()

先startService再bindService
  • 如果结束只调用unbindService(),那么只会执行到onUnbind(),将不会执行onDestroy():->onCreate()->onStartCommand()->onBind()->Service running-> onUnbind()。
  • 如果在unbindService后,在调用stopService(),那么:->onCreate()->onStartCommand()->onBind()->Service running-> onUnbind()->onDestroy() 。

正文

startService

其实starService跟Binder机制没有太大关系,通过此方式虽然可以启动一个本地或者远程的Service,但是我们拿不到Binder对象,不能直接通过AIDL的方式进行远程通信,只能通过其它的IPC方式进行通信,即时如此,这种方式启动的Service还是有一个方法需要注意一下,就是onStartCommand。

onStartCommand
1
2
3
@Override public int onStartCommand(Intent intent, int flags, int startId) {
return super.onStartCommand(intent, flags, startId);
}

调用时机

启动service的时候,onCreate方法只有第一次会调用,onStartCommand每次都被调用。onStartCommand会告诉系统如何重启服务,如判断是否异常终止后重新启动,在何种情况下异常终止 启动服务时依次执行onCreate,onStartCommand;如果在系统显示调用stopService和stopSelf之前终止服务,service再次重启,onStartCommand会被调用,重启服务时依次执行onStartCommand。

返回值

我们发现onStartCommand这个方法有个int类型返回值,实际上有四种类型,都是定义在Service中的静态常量:

1
2
3
4
public static final int START_NOT_STICKY = 2;
public static final int START_REDELIVER_INTENT = 3;
public static final int START_STICKY = 1;
public static final int START_STICKY_COMPATIBILITY = 0;

下面依次解释下这几种返回值的含义:

  • 1):START_STICKY:如果service进程被kill掉,保留service的状态为开始状态,但不保留递送的intent对象。随后系统会尝试重新创建service,由于服务状态为开始状态,所以创建服务后一定会调用onStartCommand(Intent,int,int)方法。如果在此期间没有任何启动命令被传递到service,那么参数Intent将为null。
  • 2):START_NOT_STICKY:“非粘性的”。使用这个返回值时,如果在执行完onStartCommand后,服务被异常kill掉,系统不会自动重启该服务
  • 3):START_REDELIVER_INTENT:重传Intent。使用这个返回值时,如果在执行完onStartCommand后,服务被异常kill掉,系统会自动重启该服务,并将Intent的值传入。
  • 4):START_STICKY_COMPATIBILITY:START_STICKY的兼容版本,但不保证服务被kill后一定能重启。

flags

flags表示启动服务的方式:通过startService启动时为0;onStartCommand返回为START_FLAG_REDELIVERY, or START_FLAG_RETRY.

  • START_FLAG_RETRY:表示服务之前被设为START_STICKY,则会被传入这个标记。
  • START_FLAG_REDELIVERY:如果你实现onStartCommand()来安排异步工作或者在另一个线程中工作, 那么你可能需要使用START_FLAG_REDELIVERY来让系统重新发送一个intent。这样如果你的服务在处理它的时候被Kill掉, Intent不会丢失.

bindService

前面说了那么多,bindService才是重点,通过bindService启动的Service会调用onBind方法,我们现在 分析一下如何拿到我们想要的Binder对象,因为不管是Client还是Server,想要通过AIDL进行IPC通信,就必须拿到一个Binder对象,但是如果通过Messenger的话就不需要了,因为Messenger底层自己封装了AIDL。
其实通过前面的分析,很容易看出Client在进行bindService的时候传入了一个ServiceConnection,当跟Server端连接成功的时候会在onServiceConnected中返回一个Binder对象,那么这个Binder对象是从哪儿来的呢?结合Server端的onBind方法,就很明显了,这个Binder对象就是服务端传递过来的。其实通过之前的AIDL分析,也很容易能够判断出来,只要是通过Binder机制进行IPC通信的,无论是Client还是Server端,都会涉及到Binder,我们只要找到了Binder,整个流程就很清晰了,下面分别描述一下Client跟Server中关于Binder的两个方法

CLient的bindService

ServiceConnection

1
2
3
4
5
6
7
8
9
10
mServiceConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {

}
@Override
public void onServiceDisconnected(ComponentName name) {

}
};

Client通过onServiceConnected可以拿到服务端返回的Binder对象,因为Binder实现了IBinder接口,返回的是IBinder。

Server的onBind
1
2
3
4
@Override
public IBinder onBind(Intent intent) {
return null;
}

Server通过onBind返回IBinder对象,默认的而实现为null,我们有多种方式可以实现Binder,通过继承Binder类,AIDL以及Messenger都可以做到,下面简要说明一下自定义Binder,Messenger跟AIDL下面会重点进行讲解:

继承Binder

1
2
3
4
5
6
7
8
9
10
11
12
// Binder given to clients
private final IBinder mBinder = new LocalBinder();
public class LocalBinder extends Binder {
LocalService getService() {
return LocalService.this;
}
}

@Override
public IBinder onBind(Intent intent) {
return mBinder;
}

比较简单,这种方式一般用在Service跟Activity进行通信的过程中,进行方法调用,大多数是在同一个进程中,我们看到在LocalBinder 中定义了getService方法,可以获取到Service的实例,比如我在项目中的定位是放在Service中的,但是拿到定位数据之后需要在Activity中显示地域切换的对话框,所以Service就需要跟Activity进行交互,Service可以调Activity的方法,同样由于Activity在Binder中拿到了Service的引用,也可以调用Service的中的方法。

使用AIDL进行IPC

Client端代码

ServiceConnection

1
2
3
4
5
6
7
8
9
10
11
12
13
 private ServiceConnection mServiceConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
mPeopleManager = PeopleManager.Stub.asInterface(service);
mBound = true;

}

@Override
public void onServiceDisconnected(ComponentName name) {
mBound = false;
}
};

开始连接

1
2
3
4
5
private void attemptToBindService() {
Intent intent = new Intent();
intent.setClass(this, AIDLService.class);
bindService(intent, mServiceConnection, Context.BIND_AUTO_CREATE);
}

addPeople

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public void addBook(View view) {
//如果与服务端的连接处于未连接状态,则尝试连接
if (!mBound) {
attemptToBindService();
return;
}
if (mPeopleManager == null)
return;
People people = new People();
people.setAge(18);
people.setGender("male");
people.setHobby("travel");
try {
mPeopleManager.addPeople(people);
Log.e(getLocalClassName(), people.toString());
} catch (RemoteException e) {
e.printStackTrace();
}
}

getPeople

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public void getPeople(View view) {
//如果与服务端的连接处于未连接状态,则尝试连接
if (!mBound) {
attemptToBindService();
Log.d("client-->", "正在连接Server");
return;
}
if (mPeopleManager == null)
return;
try {
Toast.makeText(this, String.valueOf(mPeopleManager.getPeople().size()), Toast.LENGTH_SHORT).show();
} catch (RemoteException e) {
e.printStackTrace();
}
}

Server端代码

创建一个Stub对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
//由AIDL文件生成的PeopleManager
private final PeopleManager.Stub mPeopleManager = new PeopleManager.Stub() {
@Override
public List<People> getPeople() throws RemoteException {
synchronized (this) {
if (mPeoples != null) {
return mPeoples;
}
return new ArrayList<>();
}
}
@Override
public void addPeople(People book) throws RemoteException {
synchronized (this) {
if (mPeoples == null) {
mPeoples = new ArrayList<>();
}
if (book == null) {
Log.e(TAG, "People is null in In");
}
mPeoples.add(book);
}
}
};

onBind进行返回

1
2
3
4
@Override
public IBinder onBind(Intent intent) {
return mPeopleManager;
}

开启远程线程

1
2
3
4
5
6
<service
android:name=".AIDLService"
android:enabled="true"
android:exported="true"
android:process=":server">
</service>

测试

AIDL进行IPC

使用Messenger进行IPC

Messenger通信原理

Client端代码

创建一个Handler

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class ClientHandler extends Handler {
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
switch (msg.what) {
case MESS_FROM_SERVER:
//接收服务器传过来的值
Bundle bundle = msg.getData();
int peopleSize = bundle.getInt("server");
Toast.makeText(MessengerActivity.this, String.valueOf(peopleSize), Toast.LENGTH_SHORT).show();
break;
}
}
}

创建一个Messenger

1
Messenger messenger = new Messenger(new ClientHandler());

ServiceConnection

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

private ServiceConnection connection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
isBound = true;
mService = new Messenger(service);

}

@Override
public void onServiceDisconnected(ComponentName name) {
mService = null;
isBound = false;
}
};

开始连接

1
2
3
4
5
private void attemptToBindService() {
Intent intent = new Intent();
intent.setClass(this, MessengerService.class);
bindService(intent, connection, Context.BIND_AUTO_CREATE);
}

addPeople

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
  public void addPeople(View view) {
//如果与服务端的连接处于未连接状态,则尝试连接
if (!isBound) {
attemptToBindService();
return;
}
People people = new People();
people.setAge(18);
people.setGender("male");
people.setHobby("travel");
Message message = Message.obtain(null, MESS_ADD_PEOPLE);
Bundle bundle = new Bundle();
bundle.putParcelable("people", people);
message.setData(bundle);
//Messenger用来接收服务端发来的信息
message.replyTo = messenger;
try {
//将Message发送到服务端
mService.send(message);
} catch (RemoteException e) {
e.printStackTrace();
}
}

getPeople

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public void getPeople(View view) {
//如果与服务端的连接处于未连接状态,则尝试连接
if (!isBound) {
attemptToBindService();
Log.d("client-->", "正在连接Server");
return;
}
Message message = Message.obtain(null, MESS_GET_PEOPLE);
//Messenger用来接收服务端发来的信息
message.replyTo = messenger;
try {
//将Message发送到服务端
mService.send(message);
} catch (RemoteException e) {
e.printStackTrace();
}
}

Server端代码

创建一个Handler
用来收发消息,由于我们是在一个应用里面的开启的多进程,所以这边接收到Client的请求,不能直接将其转化为People对象,因为People对象属于应用进程,而MessengerService属于另外一个进程,是不能共享这个数据的,这里采用了收到消息后,用一个int 类型的数据来模拟集合的数量

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
  mHandler = new Handler() {
@Override
public void handleMessage(Message msgFromClient) {
super.handleMessage(msgFromClient);
//获取一个新消息
Message replyToClient = Message.obtain(msgFromClient);
switch (msgFromClient.what) {
//根据Message.what来判断执行服务端的哪段代码
case MESS_ADD_PEOPLE:
size += 2;
break;
case MESS_GET_PEOPLE:
replyToClient.what = MESS_FROM_SERVER;
Bundle serverBundle = new Bundle();
serverBundle.putInt("server", size);
replyToClient.setData(serverBundle);
try {
msgFromClient.replyTo.send(replyToClient);
} catch (RemoteException e) {
e.printStackTrace();
}
break;
}
}
};

创建一个Messenger

1
messenger = new Messenger(mHandler);

onBind进行返回

1
2
3
4
@Override
public IBinder onBind(Intent intent) {
return messenger.getBinder();
}

开启远程线程

1
2
3
<service
android:process=":server"
android:name=".MessengerService"/>

测试结果
使用Messenger进行IPC

###总结

通过利用AIDL跟Messenger来实现Android应用层的IPC,可以更加方便的帮助我们理解Binder机制,当然Android系统还可以利用其它的方式来进行IPC,例如通过文件共享,intent传值等,下面简单就这些方式做一个对比:

进程间通信的方式
| 名称 | 优点 | 缺点 | 适用场景 |
| ————— | ——————— | ————————- | ——————– |
| Intent | 简单易用 | 只能传输Bundle所支持的数据类型 | 四大组件间的进程间通信 |
| 文件共享 | 简单易用 | 不适合高并发 | 简单的数据共享,无高并发场景 |
| AIDL | 功能强大,支持一对多并发实时通信 | 使用稍微复杂,需要注意线程同步 | 复杂的进程间调用,Android中最常用 |
| Messenger | 比AIDL稍微简单易用些 | 比AIDL功能弱,只支持一对多串行实时通信 | 简单的进程间通信 |
| ContentProvider | 强大的数据共享能力,可通过call方法扩展 | 受约束的AIDL,主要对外提供数据线的CRUD操作 | 进程间的大量数据共享 |
| RemoteViews | 在跨进程访问UI方面有奇效 | 比较小众的通信方式 | 某些特殊的场景 |
| Socket | 跨主机,通信范围广 | 只能传输原始的字节流 | 常用于网络通信中 |

源码下载地址

参考资料

Android开发艺术探索