标签:
android IPC通信(上)-sharedUserId&&Messenger
android IPC通信(中)-ContentProvider&&Socket
这篇我们将会着重介绍AIDL的使用方式和原理,要介绍AIDL先要简单介绍一下Binder,而且Messenger,ContentProvider和AIDL的最底层都是使用的Binder。
直观来说,Binder是Android中的一个类,它实现了IBinder接口。从IPC角度来说,Binder是Android中的一种跨进程通信方式,Binder还可以理解为一种虚拟的物理设备,它的设备驱动是/dev/binder,该通信方式在Linux中没有;从Android Framework角度来说,Binder是ServiceManager连接各种Manager(ActivityManager,WindowManager,等等)和相应ManagerService的桥梁;从Android应用层来说,Binder是客户端和服务端进行通信的媒介,当bindService的时候,服务端会返回一个包含了服务端业务调用的Binder对象,通过这个Binder对象,客户端就可以获取服务端提供的服务或者数据,这里的服务包括普通服务和基于AIDL的服务。
还有两点需要提到,第一点就是当客户端发起远程请求时,由于当前线程会被挂起直至服务端进程返回数据,所以如果一个远程方法是耗时的,那么不能在UI线程中发起此远程请求;其次,由于服务端的Binder方法运行在Binder的线程池中,所以Binder方法不管是否耗时都应该采用同步的方法去实现,因为他已经运行在一个线程中了。下图为Binder的工作机制图:
可以看到Client客户端会block直到方法返回。Binder的介绍就到此为止了,需要详细了解Binder的可以看看老罗的文章:
http://blog.csdn.net/luoshengyang/article/details/6618363
AIDL的全称是Android Interface definition language,一看就明白,它是一种android内部进程通信接口的描述语言,通过它我们可以定义进程间的通信接口,用处当然就是用来进程间的通信和方法调用了(我在IPC通信上篇中介绍过也可以使用Messenger加上反射机制来进行跨应用的方法调用,但是前提是让两个应用在一个进程中,局限性比AIDL大)。先介绍一下AIDL进程间通信的流程:
第一步创建几个相关的AIDL文件,特别需要注意的是在AS中,先要在app_name/src/main/文件夹下创建一个aidl文件夹,接下来在该文件夹下去创建相关的package用来放置这些AIDL文件,基本结构如下图所示:
不这么做是无法使用的。接着我们就来仔细分析这几个AIDL文件:
// IWeatherManager.aidl
package com.android.aidl;
import com.android.aidl.Weather;
import com.android.aidl.listener.IWeatherChangeListener;
interface IWeatherManager {
List<Weather> getWeather();
void addWeather(in Weather weather);
void addListener(in IWeatherChangeListener listener);
void removeListener(in IWeatherChangeListener listener);
}
这个IWeatherManager.aidl文件是连接客户端和服务端的核心文件,我们可以看到这个aidl文件中需要引用两个类:Weather和IWeatherChangeListener,看看这两个aidl文件的代码:
//Weather.aidl
package com.android.aidl;
parcelable Weather;
// IWeatherChangeListener.aidl
package com.android.aidl.listener;
import com.android.aidl.Weather;
interface IWeatherChangeListener {
void onWeatherChange(in Weather newWeather);
}
详细介绍一下这几个文件的要点:第一点是如果AIDL文件中用到了自定义的Parcelable对象,那么必须新建一个和它同名的AIDL文件,并在其中声明它为Parcelable类型。在IWeatherManager.aidl文件中用到了Weather这个Parcelable类,所以我们必须要创建Weather.aidl文件,要不然只有一个Weather.java文件是无法识别的,并且非常重要的是Weather.aidl和Weather.java两个文件的包名必须要一致,比如demo中的都为com.android.aidl,不一致也会导致Weather类无法识别;第二点是AIDL中除了基本数据类型,其他类型的参数必须标上方向:in,out或者inout, in表示输入型参数,out表示输出型参数,inout表示输入输出型参数。我们要根据实际需要去指定参数类型,不能一概使用out或者inout,因为这在底层实现是有开销的;第三点是AIDL接口中只支持方法,不支持声明静态常量,这一点区别于传统的接口。
在这个demo中,我们仍然是在一个应用中创建两个进程进行通信,和在两个应用中的两个进程之间进行通信是很类似的,差异方面就以这个demo来说第一个需要在两个应用中的app_name/src/main/文件夹下都创建一个aidl文件夹,然后将三个aidl文件整体拷贝进来,当然要保证两个应用的package名字com.android.aidl一样;第二个还有Weather.java文件也必须在两个应用中的com.android.aidl(就是要和Weather.aidl的package名字一致)包下面,做到这两点就可以了。一个工程和两个工程的多进程本质是一样的,有兴趣的可以自己试试。
我们来看看demo中Weather.java类的代码:
public class Weather implements Parcelable{
public String cityName;
public double temperature;
public double humidity;
public AllWeather weather;
protected Weather(Parcel in) {
temperature = in.readDouble();
humidity = in.readDouble();
//使用该方式来写入枚举
weather = AllWeather.values()[in.readInt()];
cityName = in.readString();
}
public Weather() {
}
public static final Creator<Weather> CREATOR = new Creator<Weather>() {
@Override
public Weather createFromParcel(Parcel in) {
return new Weather(in);
}
@Override
public Weather[] newArray(int size) {
return new Weather[size];
}
};
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeDouble(temperature);
dest.writeDouble(humidity);
dest.writeInt(weather.ordinal());
dest.writeString(cityName);
}
public enum AllWeather{
sunny,cloudy,rain,snowy
}
}
代码很简单,就是实现Parcelable接口即可,唯一的难点就是enum枚举在多客户端之间的处理了,处理方法就是使用ordinal()函数将枚举转换成int类型,通着这个int值,就可以找到枚举变量在枚举类中的位置,也就可以知道原始值了。
接着就是服务端的代码实现了:
public class WeatherManagerService extends Service{
//支持并发读写的list
public CopyOnWriteArrayList<Weather> weathers = new CopyOnWriteArrayList<>();
public RemoteCallbackList<IWeatherChangeListener> listeners = new RemoteCallbackList<>();
@Override
public void onCreate() {
super.onCreate();
Weather nanshan = new Weather();
nanshan.cityName = "南山";
nanshan.temperature = 20.5;
nanshan.humidity = 45;
nanshan.weather = Weather.AllWeather.cloudy;
Weather futian = new Weather();
futian.cityName = "福田";
futian.temperature = 21.5;
futian.humidity = 48;
futian.weather = Weather.AllWeather.rain;
weathers.add(nanshan);
weathers.add(futian);
}
private Binder mBinder = new IWeatherManager.Stub() {
@Override
public List<Weather> getWeather() throws RemoteException {
L.i("server returns all of the weathers");
return weathers;
}
@Override
public void addWeather(Weather weather) throws RemoteException {
weathers.add(weather);
L.i("server add new Weather:" + weather.cityName);
int N = listeners.beginBroadcast();
for (int i=0; i<N; i++){
IWeatherChangeListener listener = listeners.getBroadcastItem(i);
listener.onWeatherChange(weather);
}
L.i("server notify the listener that weathers have been changed");
listeners.finishBroadcast();
}
@Override
public void addListener(IWeatherChangeListener listener) throws RemoteException {
L.i("server adding listener");
listeners.register(listener);
}
@Override
public void removeListener(IWeatherChangeListener listener) throws RemoteException {
L.i("server removing listener");
listeners.unregister(listener);
}
@Override
public boolean onTransact(int code, Parcel data, Parcel reply, int flags) throws RemoteException {
int permission = checkCallingPermission("com.android.permission.WRITEWEATHERPERMISSION");
//检测客户端是否声明权限
if (permission == PackageManager.PERMISSION_DENIED){
L.e("permission denied");
return false;
}
L.i("permission granted");
String[] packages = getPackageManager().getPackagesForUid(getCallingUid());
if (packages != null && packages.length > 0){
String packageName = packages[0];
if (!packageName.startsWith("com.android")){
L.e("package name not accept");
return false;
}
L.i("package name accept");
}
return super.onTransact(code, data, reply, flags);
}
};
@Override
public IBinder onBind(Intent intent) {
// int permission = checkCallingPermission("com.android.permission.WRITEWEATHERPERMISSION");
// //检测客户端是否声明权限
// if (permission == PackageManager.PERMISSION_DENIED){
// L.e("permission denied");
// return null;
// }
return mBinder;
}
}
服务端的实现比较复杂,我们一步步来分析:
ArrayMap<IBinder, Callback> mCallbacks = new ArrayMap<IBinder, Callback>()
IBinder binder = callback.asBinder();
Callback cb = new Callback(callback, cookie);
看看客户端的代码:
public class ClientActivity extends BaseActivity implements View.OnClickListener{
private ServiceConnection serviceConnection = null;
private IBinder.DeathRecipient deathRecipient = null;
private IWeatherChangeListener listener = null;
private IWeatherManager weatherManager;
private TextView tv_content;
private TextView tv_add;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_client);
findViewById(R.id.btn_add).setOnClickListener(this);
findViewById(R.id.btn_query).setOnClickListener(this);
findViewById(R.id.btn_remove_listener).setOnClickListener(this);
tv_content = (TextView) findViewById(R.id.tv_content);
tv_add = (TextView) findViewById(R.id.tv_add);
listener = new IWeatherChangeListener.Stub(){
@Override
public void onWeatherChange(Weather newWeather) throws RemoteException {
L.i("client has been notified that "+newWeather.cityName+" has been added");
tv_add.setText(newWeather.cityName + "has been added");
}
};
serviceConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
weatherManager = IWeatherManager.Stub.asInterface(service);
try {
weatherManager.asBinder().linkToDeath(deathRecipient, 0);
weatherManager.addListener(listener);
} catch (RemoteException e) {
e.printStackTrace();
}
}
@Override
public void onServiceDisconnected(ComponentName name) {
weatherManager = null;
}
};
deathRecipient = new IBinder.DeathRecipient() {
@Override
public void binderDied() {
//移出之前的死亡容器
weatherManager.asBinder().unlinkToDeath(deathRecipient, 0);
weatherManager = null;
//重新连接
bindServer();
}
};
bindServer();
}
private void bindServer(){
Intent intent = new Intent(this, WeatherManagerService.class);
bindService(intent, serviceConnection, Context.BIND_AUTO_CREATE);
}
@Override
public void onClick(View v) {
if (v.getId() == R.id.btn_query){
try {
//调用远程服务端接口时,客户端进程会挂起,勿在主线程中调用耗时远程操作
L.i("client is getting weather");
List<Weather> weathers = weatherManager.getWeather();
L.i("client has gotten weather");
StringBuilder sb = new StringBuilder();
for (Weather weather : weathers){
sb.append(weather.cityName).append("\n");
sb.append("humidity:").append(weather.humidity)
.append("temperature").append(weather.temperature)
.append("weather").append(weather.weather).append("\n");
}
tv_content.setText(sb);
} catch (RemoteException e) {
e.printStackTrace();
}
}else if (v.getId() == R.id.btn_add){
Weather weather = new Weather();
weather.weather = Weather.AllWeather.cloudy;
weather.humidity = 25.5;
weather.temperature = 19.5;
weather.cityName = "罗湖";
try {
//调用远程服务端接口时,客户端进程会挂起,勿在主线程中调用耗时远程操作
L.i("client is adding weather " + weather.cityName);
weatherManager.addWeather(weather);
L.i("client has added weather " + weather.cityName);
} catch (RemoteException e) {
e.printStackTrace();
}
}else if (v.getId() == R.id.btn_remove_listener){
try {
weatherManager.removeListener(listener);
} catch (RemoteException e) {
e.printStackTrace();
}
}
}
@Override
protected void onDestroy() {
super.onDestroy();
unbindService(serviceConnection);
try {
weatherManager.asBinder().linkToDeath(deathRecipient, 0);
} catch (RemoteException e) {
e.printStackTrace();
}
}
客户端逻辑很简单,bindService绑定服务端之后,将服务端传过来的IBinder对象通过asInterface方法转换成AIDL接口,然后就能通过这个接口去调用服务端的远程方法了。而且在客户端还能够注册死亡代理,新建一个DeathRecipient对象,并且使用Binder的linkToDeath注册该对象,当Binder死亡时,我们就会收到通知,unlinkToDeath函数可以解注册该死亡代理。
还有非常重要的几点需要说明:客户端调用服务端方法,被调用的方法运行在服务端的Binder线程池中,同时客户端线程会被挂起,这个时候如果服务端方法执行比较耗时,就会导致客户端线程长时间阻塞,而如果这个客户端线程是UI线程的话,就会导致客户端ANR,所以如果知道服务端的一个方法是耗时的,就要避免在客户端的UI线程中去调用该远程方法。由于onServiceConnected和onServiceDisconnected方法都运行在UI线程中,所以也不可以在这两个函数中调用耗时方法。另外,由于服务端的方法本身就运行在服务端的Binder线程池中,所以服务端方法可以执行大量的耗时操作,这个时候切记不要在服务端方法中开线程去进行异步任务,除非你明确知道自己在干什么,否则不建议这么做。
关于服务端和客户端的方法分别执行在那个进程和线程中以及它们执行的先后顺序,我们先来看看log日志的输出:
I/[PID:28533](28533): [TID:7035] 1.onTransact(line:90): permission granted
I/[PID:28533](28533): [TID:7035] 1.onTransact(line:99): package name accept
I/[PID:28533](28533): [TID:7035] 1.addListener(line:72): server adding listener
//***client add click
I/[PID:28502](28502): [TID:1] ClientActivity.onClick(line:115): client is adding weather 罗湖
I/[PID:28533](28533): [TID:7036] 1.onTransact(line:90): permission granted
I/[PID:28533](28533): [TID:7036] 1.onTransact(line:99): package name accept
I/[PID:28533](28533): [TID:7036] 1.addWeather(line:59): server add new Weather:罗湖
I/[PID:28502](28502): [TID:1] 1.onWeatherChange(line:47): client has been notified that 罗湖 has been added
I/[PID:28533](28533): [TID:7036] 1.addWeather(line:66): server has notified the listener that weathers have been changed
I/[PID:28502](28502): [TID:1] ClientActivity.onClick(line:117): client has added weather 罗湖
//***client remove listener click
I/[PID:28533](28533): [TID:7035] 1.onTransact(line:90): permission granted
I/[PID:28533](28533): [TID:7035] 1.onTransact(line:99): package name accept
I/[PID:28533](28533): [TID:7035] 1.removeListener(line:78): server removing listener
//***client get click
I/[PID:28502](28502): [TID:1] ClientActivity.onClick(line:93): client is getting weather
I/[PID:28533](28533): [TID:7036] 1.onTransact(line:90): permission granted
I/[PID:28533](28533): [TID:7036] 1.onTransact(line:99): package name accept
I/[PID:28533](28533): [TID:7036] 1.getWeather(line:52): server returns all of the weathers
I/[PID:28502](28502): [TID:1] ClientActivity.onClick(line:95): client has gotten weather
PID:28502为客户端进程,PID:28533为服务端进程,TID:1为UI主线程,TID:7036为Binder线程。看看log打印的顺序基本就能够明白方法的执行进程,线程和客户端的阻塞情况了。
源码下载:https://github.com/zhaozepeng/IPC-demo/tree/master/AIDL
上面差不多就把AIDL的用法详细介绍完了,但是有的时候我们可能需要不止一个业务模块,也就是不单单需要一个天气模块,我们还需要一个计算温度平均值的模块(虽然可以写在一个模块中,但是我们还是假设要用两个模块吧~),是不是需要为每个模块都单独建立一个Service呢?当然不是,会很耗资源的好吗,解决方法就是先为每一个模块建立一个单独的aidl文件,最后再建立一个整体的aidl文件用来管理这些单独的aidl。
看看这三个文件,IWeatherManager.aidl,IComputerManager.aidl和IBinderPoolManager.aidl:
// IWeatherManager.aidl
package com.android.binderpool;
import com.android.binderpool.Weather;
interface IWeatherManager {
List<Weather> getWeather();
void addWeather(in Weather weather);
}
// IComputerManager.aidl
package com.android.binderpool;
import com.android.binderpool.Weather;
interface IComputerManager {
double computeAverageTemperature(in List<Weather> weathers);
}
// IBinderPoolManager.aidl
package com.android.binderpool;
interface IBinderPoolManager {
IBinder queryCode(int code);
}
IBinderPoolManager.aidl文件用来统一管理所有的AIDL接口,queryCode函数通过code值来确定需要返回给客户端的IBinder对象。
来看看服务端的代码的变动:
public class BinderPoolService extends Service{
public static final int CODE_WEATHER = 1;
public static final int CODE_COMPUTER = 2;
private IBinderPoolManager iBinderPoolManager;
//支持并发读写的list
public CopyOnWriteArrayList<Weather> weathers = new CopyOnWriteArrayList<>();
@Override
public void onCreate() {
super.onCreate();
Weather nanshan = new Weather();
nanshan.cityName = "南山";
nanshan.temperature = 20.5;
nanshan.humidity = 45;
nanshan.weather = Weather.AllWeather.cloudy;
Weather futian = new Weather();
futian.cityName = "福田";
futian.temperature = 21.5;
futian.humidity = 48;
futian.weather = Weather.AllWeather.rain;
weathers.add(nanshan);
weathers.add(futian);
iBinderPoolManager = new IBinderPoolManager.Stub(){
@Override
public IBinder queryCode(int code) throws RemoteException {
switch (code){
case CODE_WEATHER:
return new IWeatherManager.Stub(){
@Override
public List<Weather> getWeather() throws RemoteException {
return weathers;
}
@Override
public void addWeather(Weather weather) throws RemoteException {
weathers.add(weather);
}
};
case CODE_COMPUTER:
return new IComputerManager.Stub() {
@Override
public double computeAverageTemperature(List<Weather> weathers) throws RemoteException {
double sum = 0;
for (int i=0; i<weathers.size(); i++){
sum += weathers.get(i).temperature;
}
return sum/weathers.size();
}
};
default:
return null;
}
}
};
}
@Override
public IBinder onBind(Intent intent) {
return iBinderPoolManager.asBinder();
}
}
根据code的不同返回不同的IBinder对象,这样在客户端中就能够获取对应AIDL接口的IBinder对象,最终就能在客户端调用不同AIDL模块中的方法。客户端代码很简单,在这里就不介绍了,感兴趣的可以去看看源码:
https://github.com/zhaozepeng/IPC-demo/tree/master/BinderPool
关于IPC相关知识的介绍就到这了,如果有什么疑问,大家可以多多交流啊,谢谢~
标签:
原文地址:http://blog.csdn.net/self_study/article/details/50311611