IPC的用法的学习
这篇文章来小结一下自己学的IPC的知识,包括Messenger和AIDL,然后我在总结一下自己最近学的关于Binder的知识。
在了解IPC前先说明一下什么是进程什么是线程,按照操作系统中的描述,线程是CPU调度的最小单元,同时线程是一种有限的系统资源,而进程一般指一个执行单元,在android指一个应用。一个进程可以包含多个线程,因此进程和线程是包含与被包含的关系,在最简单的情况下,一个进程中可以只有一个线程即主线程,在android中主线程也叫UI线程,在UI线程里面才能操作界面元素。IPC(进程间通信)是主要用于两个进程,有时候我们需要在应用间(即两个进程间)交换数据,这时候就需要IPC。实现进程间通信有很多方式,我这里就只介绍Messenger和AIDL,至于其它的方式我就不介绍了。
Messenger
Messenger就是对Binder做了一个简单的封装,我们在做一些简单的跨进程通信的时候可以使用Messenger,比AILDL简单。首先看一下构造方法
Messenger(Handler target);
//用一个Handler创造一个Messenger实例,当有一个新的Message发送到这个Messenger的时候,这个Messenger就回调用它的handler.sendMessage(),这个时候这个Message就会被这个Handler所处理。里面具体的怎么跨进程的就留到总结Binder的时候再细说,这里就需要理解到这个时候就会调用远程进程的那个Handler。
Messenger(IBinder target);
//用一个IBinder对象来构建一个Messenger实例,这个Messnegr就会关联到这个IBinder对象的Handler,向这个Messenger发送 Message的时候就会让对应的Handler来处理,这个主要用来在客户端构造服务端的Messenger对象时调用。
下面来个简单的例子:
上代码:
//服务端
public class MessengerService extends Service {
public static final int SAY_HELLO =1;
class MessengerHandler extends Handler{
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
switch (msg.what){
case SAY_HELLO:
Bundle data = msg.getData();
String str = data.getString("data");
Log.e("TAG","MessengerService Received say_hello: "+str);
break;
}
}
}
private Messenger mMessenger = new Messenger(new MessengerHandler());
@Override
public IBinder onBind(Intent intent) {
// TODO: Return the communication channel to the service.
return mMessenger.getBinder();
}
}
//客户端
private ServiceConnection mConn = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
mMessenger = new Messenger(service);
Message msg = Message.obtain(null,MessengerService.SAY_HELLO);
Bundle data = new Bundle();
data.putString("data","Hello from Client");
msg.setData(data);
try {
mMessenger.send(msg);
} catch (RemoteException e) {
e.printStackTrace();
}
mBond = true;
}
@Override
public void onServiceDisconnected(ComponentName name) {
mBond = false;
}
};
看一下服务端的打印
上面只是客户端向服务端传递数据,那么服务端向客户端传递数据的原理是一样的,首先需要创建一个客户端的Messenger的实例,然后丢给发送到服务端的Message的replyTo字段,服务端去的这个字端就是拿到了客户端的Messenger,就可以向客户端发送数据了。下面看一下更改后的代码:
//客户端
class ClientHandler extends Handler{
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
switch (msg.what){
case 1:
Bundle data = msg.getData();
String str = data.getString("data");
Log.e("TAG","客户端已经收到服务端的信息"+str);
break;
}
}
}
private Messenger mClient = new Messenger(new ClientHandler());
private ServiceConnection mConn = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
mMessenger = new Messenger(service);
Message msg = Message.obtain(null,MessengerService.SAY_HELLO);
Bundle data = new Bundle();
data.putString("data","Hello from Client");
msg.setData(data);
msg.replyTo = mClient;
try {
mMessenger.send(msg);
} catch (RemoteException e) {
e.printStackTrace();
}
mBond = true;
}
@Override
public void onServiceDisconnected(ComponentName name) {
mBond = false;
}
};
//服务端的代码
public class MessengerService extends Service {
public static final int SAY_HELLO =1;
class MessengerHandler extends Handler{
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
switch (msg.what){
case SAY_HELLO:
Bundle data = msg.getData();
String str = data.getString("data");
Log.e("TAG","MessengerService Received say_hello: "+str);
Messenger mClient = msg.replyTo;
Message mMsgClient = new Message();
mMsgClient.what = 1;
Bundle bun = new Bundle();
bun.putString("data","服务端已经收到你的消息,稍后会回复你的....");
mMsgClient.setData(bun);
try {
mClient.send(mMsgClient);
} catch (RemoteException e) {
e.printStackTrace();
}
break;
}
}
}
private Messenger mMessenger = new Messenger(new MessengerHandler());
@Override
public IBinder onBind(Intent intent) {
// TODO: Return the communication channel to the service.
return mMessenger.getBinder();
}
}
AIDL(Android 接口定义语言)
-
定义 AIDL 接口
当每次新建一个aidl文件时,Android SDK 工具都会生成一个基于该 .aidl 文件的 IBinder 接口,并将其保存在项目的 gen/ 目录中。服务必须视情况实现 IBinder 接口。然后客户端应用便可绑定到该服务,并调用 IBinder 中的方法来执行 IPC。一般创建一个aidl服务执行下面的步骤:
1.创建 .aidl 文件
2.实现接口
Android SDK 工具基于您的 .aidl 文件,使用 Java 编程语言生成一个接口。此接口具有一个名为 Stub 的内部抽象类,用于扩展 Binder 类并实现 AIDL 接口中的方法。您必须扩展 Stub 类并实现方法。3.向客户端公开该接口
实现 Service 并重写 onBind() 以返回 Stub 类的实现。
上代码:
//新建AIDL文件
// First.aidl
package com.hq.demo.aidltest;
// Declare any non-default types here with import statements
interface First {
/**
* Demonstrates some basic types that you can use as parameters
* and return values in AIDL.
*/
void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat,
double aDouble, String aString);
String getStringFromClient(in String str);
}
//服务端的代码,实现其Stub类
public class FirstAidlService extends Service {
private FirstBinder mBinder;
@Override
public void onCreate() {
super.onCreate();
mBinder = new FirstBinder();
}
public FirstAidlService() {
}
@Override
public IBinder onBind(Intent intent) {
// TODO: Return the communication channel to the service.
return mBinder;
}
class FirstBinder extends First.Stub{
@Override
public void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat, double aDouble, String aString) throws RemoteException {
}
@Override
public String getStringFromClient(String str) throws RemoteException {
return "String from Server "+str ;
}
}
}
//客户端绑定服务时需要的ServiceConnection
private ServiceConnection mConn = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
mBinder = First.Stub.asInterface(service);//获得服务端的Binder对象,并将其转换成为First类,此时客户端就可以调用服务端的代码了,实现跨进程访问
mBond = true;
try {
String str = mBinder.getStringFromClient("Client to Server");
Log.e("TAG","Client : "+str);
} catch (RemoteException e) {
e.printStackTrace();
}
}
@Override
public void onServiceDisconnected(ComponentName name) {
mBond = false;
}
};
看下日志:
客户端和服务端实现了跨进程调用,这是最简单的跨进程调用,我们客户端只是向服务端传递了一个字符串,并且服务端返回了一个字符串,下面就了解一下AIDL跨进程调用能够传递哪些数据。
- 通过 IPC 传递对象
默认情况下,AIDL 支持下列数据类型:
Java 编程语言中的所有原语类型(如 int、long、char、boolean 等等),String,CharSequence
List
List 中的所有元素都必须是以上列表中支持的数据类型、其他 AIDL 生成的接口或您声明的可打包类型。 可选择将 List 用作“通用”类(例如,List<String>)。
另一端实际接收的具体类始终是 ArrayList,但生成的方法使用的是 List 接口。
Map
Map 中的所有元素都必须是以上列表中支持的数据类型、其他 AIDL 生成的接口或您声明的可打包类型。 不支持通用 Map(如 Map<String,Integer> 形式的 Map)。 另一端实际接收的具体类始终是 HashMap,但生成的方法使用的是 Map 接口。
上面是基本的类型,通过 IPC 接口把某个类从一个进程发送到另一个进程是可以实现的。 不过,您必须确保该类的代码对 IPC 通道的另一端可用,并且该类必须支持 Parcelable 接口。因为是跨进程这个对象在两个进程中的包名都必须是一样的,因为在不同的进程,就是不同的对象,程序需要把另一个进程传递过来的对象,实例化为本进程的可识别的对象。把需要传递的对象实现Parcelable接口,并且要定义成.aidl接口的形式,在写aidl文件的时候必须为每个附加类型加入一个import语句,即使这些类型与你定义的aidl的接口在同一个包中。还有一点就是在写aidl方法时,如果方法的参数类型时非源语,那么就必须要加上指示数据走向的方向标记,可以是in,out,inout,原语默认是in,不能是其它方向
注意:您应该将方向限定为真正需要的方向,因为编组参数的开销极大。
上代码:
// First.aidl
package com.hq.demo.aidltest;
import com.hq.demo.aidltest.Book;
interface First {
/**
* Demonstrates some basic types that you can use as parameters
* and return values in AIDL.
*/
void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat,
double aDouble, String aString);
String getStringFromClient(String str);
Book addBook(in Book book);
List<Book> getBookList();
}
//服务端
public class FirstAidlService extends Service {
private FirstBinder mBinder;
//因为通过Binder类调用服务端的方法,改方法会执行在Binder线程池里面,不是
//主线程,所以需要关注线程间的同步问题。而CopyOnWriteArrayList已经帮我
//们实现了线程同步的问题。
private CopyOnWriteArrayList<Book> mData;
@Override
public void onCreate() {
super.onCreate();
mData = new CopyOnWriteArrayList<>();
Book b1 = new Book();
b1.setmBookName("book1");
b1.setmPrice(1.0f);
Book b2 = new Book();
b2.setmBookName("book2");
b2.setmPrice(2.0f);
mData.add(b1);
mData.add(b2);
mBinder = new FirstBinder();
}
public FirstAidlService() {
}
@Override
public IBinder onBind(Intent intent) {
// TODO: Return the communication channel to the service.
return mBinder;
}
//实现First.Stub 接口,实现里面的方法,在onBind()里面返回相应的Binder类,
class FirstBinder extends First.Stub{
@Override
public void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat, double aDouble, String aString) throws RemoteException {
}
@Override
public String getStringFromClient(String str) throws RemoteException {
return "String from Server "+str ;
}
@Override
public Book addBook(Book book){
Book book1 = new Book();
Log.e("TAG","Server addBook from Client Book:"+book);
book1.setmBookName("ServerBookName");
book1.setmPrice(10.2f);
mData.add(book);
return book1;
}
@Override
public List<Book> getBookList() throws RemoteException {
return mData;
}
}
}
// 客户端代码
private ServiceConnection mConn = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
mBinder = First.Stub.asInterface(service);//获得服务端的Binder接口
mBond = true;
try {
String str = mBinder.getStringFromClient("Client to Server");
Log.e("TAG","Client : "+str);
Book b = new Book();
b.setmBookName("Client Book");
b.setmPrice(9.1f);
//通过Binder调用服务端的方法
Book book = mBinder.addBook(b);
Log.e("TAG", "AIDL Base addBook(): " + book);
List<Book> books = mBinder.getBookList();
Log.e("TAG",books.toString());
} catch (RemoteException e) {
e.printStackTrace();
}
}
@Override
public void onServiceDisconnected(ComponentName name) {
mBond = false;
}
}
接下来,简单介绍两个很重要的方法,linkToDeath()和unlinkToDeath();我们知道,Binder运行在服务端的进程中,如果服务端进程由于某种原因异常终止,这个时候如果我们还在调用服务端的方法,这就会导致调用失败,这个时候急需要用到linkToDeath();当Binder死亡的时候我们就会收到相应的通知。
上代码:
private IBinder.DeathRecipient mRecipient = new IBinder.DeathRecipient() {
@Override
public void binderDied() {
Log.e("TAG","mRecipient binderDied");
if(mBinder == null)
return;
mBinder.asBinder().unlinkToDeath(mRecipient,0);
mBinder = null;
//下面可以做重新绑定的操作
}
};
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
mBinder = First.Stub.asInterface(service);
mBond = true;
try {
........
service.linkToDeath(mRecipient,0);
} catch (RemoteException e) {
e.printStackTrace();
}
}
上面就实现了当服务端的进程异常终止时,所要回调的方法,并且可以执行重新绑定服务的操作。
这篇文章就到这吧,太长了,接下来还会继续介绍AIDL和Binder相关的知识点。