Android 进程通信原理

Android系统提供了一些通用服务,比如音乐打电话发短信,WIFI,定位,输入法,传感器等。应用程序与这些通用服务运行在不同的进程中,如果应用程序想要与这些通用服务交互就要涉及到进程间通信,Binder就是为了Android进程间通信而设计的。

Binder框架

Binder是一种架构,这种架构提供了服务端接口、Binder驱动、客户端接口三个模块。

服务端
Binder服务端相当于一个Binder类对象。当创建该对象时,其内部会启动一个线程不断接收Binder驱动发送的消息。收到消息后会执行Binder.onTransact()方法。所以要实现一个Binder服务就必须重载onTransact()方法。

onTransact()方法通常用来做数据格式转换,按约定的顺序取出Binder客户端发送来的数据并转换成服务端识别的数据格式。

Binder驱动
Binder驱动运行在内核态,其所有操作都是基于内存而非硬件,客户端与服务端通信时需要Binder驱动进行中转。

当一个服务端Binder被创建时其在Binder驱动中同时会创建一个mRemote引用指向服务端。客户端要访问服务端时首先要获取服务端在Binder中的引用mRemote,获取引用后就可以通过mRemote.transact()方法向服务端发送消息。

transact()方法实现了以下几个功能:

  • 以线程间消息通信的模式向服务端发送客户端传递过来的参数。
  • 挂起当前客户端线程,等待服务端线程执行完毕后的通知(notify)
  • 接收服务端线程通知,继续执行客户端线程,并返回执行结果

客户端
这里就是指我们需要和系统服务交互的应用程序。应用程序使用startService()与应用程序Service建立连接,使用getSystemService()与系统Service建立连接,从而进行通信。

数据格式
在进行IPC通信时需要约定客户端与服务端通信的数据格式。Android使用AIDL(Android Interface Definition Language)约定数据格式。

Android提供了一个AIDL工具,可以把AIDL文件转换成一个Java类,在该Java类中同时重载了onTransact()方法和transact()方法,统一了存入和读取参数。

实现Binder服务端

1
2
3
4
5
6
7
8
9
10
11
public class MusicService extends Binder{
@Override
protected boolean onTransact(int code, Parcel data, Parcel reply, int flags) throws RemoteException{
return super.onTransact(code,data,reply, flags);
}

public void start(String filePath){

}
public void stop(){}
}

以上代码基于Binder创建了一个Service,使用startService()方法启动后就可以看到DDMS中多了一个线程。

重载onTransact(int code, Parcel data, Parcel reply, int flags)方法:

1
2
3
4
5
6
7
8
switch(code){
code 1000:
data.enforceInterface("MusicService");// 数据校验与客户端writeInterfaceToken()对应
String filePath = data.readString();//读取一个字符串
start(filePath);
// reply.writeXXX();//如果需要返回结果则写入reply中
break;
}

data中保存着客户端传递过来的参数,onTransact()方法内部需要从data中读取客户端传递的参数。参数的位置格式需要在AIDL文件中约束。
reply中保存服务端返回客户端的结果,如果需要返回则调用Parcel提供的相关方法写入结果。参数的位置格式需要在AIDL文件中约束。
code变量标识客户端希望服务端执行哪个方法,这里假定1000执行start()方法。
flags表示IPC调用模式:0代表”双向模式”,服务端在执行完指定服务后会返回数据;1代表”单向模式”,服务端在执行完指定服务后不反回任何数据。

实现Binder客户端

首先在startService(),getSystemService()中获取服务端Binder的引用,然后将参数按AIDL定义的格式写入data中并调用mRemote.transact()方法传入参数,服务端会在onTransact()方法中取出这里出入的参数:

1
2
3
4
5
6
7
8
9
10
11
IBinder mRemote = null;
String filePath = "/sdcard/music/xxx.mp3";
int code = 1000;
Parcel data = Parcel.obtain();
Parcel reply = Parcel.obtain();
data.writeInterfaceToken("MusicService");
data.writeString(filePath);
mRemote.transact(code,data,reply,0);
IBinder binder = reply.readStrongBinder();
reply.recycle();
data.recycle();

调用mRemote.transact()方法后,Binder驱动会挂起当前客户端线程,并向服务端发送一个消息,这个消息就包括客户端传递的参数。服务端接收到消息,解析数据执行完相关任务后会把执行结果写入reply中,然后向Binder驱动发送一个notify消息,Binder驱动从挂起处唤醒客户端线程继续执行。

系统Service与应用程序Service

Service也就是服务分为两种,一种是系统创建的Service,另一种是应用程序自定义的Service。这两种Service都是Binder服务端。

系统Service
系统Service在系统系统初始化时从SystemServer进程中启动。常见的有PowerManagerService(电源管理服务),VibratorService(振动传感器服务),WindowManagerService(窗口管理服务)NotificationManagerService等等,每个系统服务都是一个Binder,都运行在一个独立的线程中。

系统提供了一个ServiceManager类来管理系统服务ServiceManager本身也是一个Binder,运行在一个独立的进程中,其提供了一个全局Binder供应用程序使用,应用程序获取其他系统服务都是通过ServiceManager的全局Binder来获取的。

这设计的好处仅仅暴露一个全局Binder引用,其他系统服务则隐藏起来,从而有助于系统扩展,以及调用系统服务的安全检查。

系统Service在启动时首先向ServiceManager注册Service(注册自己),当调用getSystemService(serviceName)获取系统服务时,会间接调用到ServiceManager.getService(String name)方法。getService()实现如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
public static IBinder getService(String name){
try{
IBinder service = sCache.get(name);
if(service != null){
return service;
}else{
return getIServiceManager().getService(name);
}
} catch(RemoteException e){
Log.e(TAG,"error in get Service", e);
}
}

首先从缓存中获取,没获取到则从getIServiceManager()中获取,getIServiceManager()返回的是系统唯一ServiceManager的Binder。

应用程序Service
系统在ActivityManagerService中提供了startService()方法来启动应用程序自定义Service。客户端使用以下方法和应用程序Service建立连接:

1
2
public ComponentName startService(Intent intent)
public boolean bindService(Intent service, ServiceConnection conn,int flags)

startService用于启动一个服务,bindService用于绑定一个服务,bindService的第二个参数是获取服务端Binder的关键所在:

1
2
3
4
public interface ServiceConnection{
public void onServiceConnected(ComponentName name,IBinder service);
public void onServiceDisconnected(ComponentName name);
}

onServiceConnected()方法的第二个变量就是我们需要的服务端Binder的引用,当客户端请求AmS启动某个Service,如果该Service正常启动,那么AmS就会远程调用ActivityThread类中的ApplicationThread对象,调用的参数包括Service的Binder引用,然后在ApplicationThread中会回调bindService()方法中的conn接口。因此,在客户端中,可以在onServiceConnected()方法中将其参数Service保存为一个全局变量,从而在客户端任何地方都可以随时调用该服务。