android IPC通讯(下)-AIDL

  android IPC通讯(上)-sharedUserId&&Messenger
  android IPC通讯(中)-ContentProvider&&Socket
  这篇咱们将会着重介绍AIDL的使用方式和原理,要介绍AIDL先要简单介绍一下Binder,并且Messenger,ContentProvider和AIDL的最底层都是使用的Binder。javascript

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 的调用中有四个角色:Client, Proxy, Binder Driver 和 Server ,他们的关系以下图所示:
  这里写图片描述

这四者的关系相似于网络访问,html

  • Binder Client 只须要知道本身要使用的 Binder 的名字及其在 ServerManager 中的引用便可获取该 Binder 的引用,获得引用后就能够像普通方法调用同样调用 Binder 实体的方法;
  • Binder Server 在生成一个 Binder 实体时会为其绑定一个名称并传递给 Binder Driver,Binder Driver 会在内核空间中建立相应的 Binder 实体节点和节点引用,并将引用传递给 ServerManager。ServerManager 会将该 Binder 的名字和引用插入一张数据表中,这样 Binder Client 就可以获取该 Binder实体 的引用,并调用上面的方法;
  • ServerManager 至关于 DNS服务器,负责映射 Binder 名称及其引用,其本质一样是一个标准的 Binder Server;
  • Binder Driver 则至关于一个路由器。
其中 Binder Driver 实如今内核空间中,而其他的 3 者 Binder Client, Binder Server, Server Manager 实如今用户空间中。Binder Driver 在内核空间中,其以字符设备中的 misc 类型注册,用户能够从 /dev/binder 设备文件节点上,经过 open 和 ioctl 文件操做函数与 Binder Driver 进行通讯,其主要负责 Binder 通讯的创建,以及其在进程间的传递和 Binder 引用计数管理/数据包的传输等。而 Binder Client 与 Binder Server 之间的跨进程通讯则统一经过 Binder Driver 处理转发,对于 Binder Client 来讲,其只须要知道本身要使用的 Binder 的名字以及该 Binder 实体在 ServerManager 中的 0 号引用便可,访问的原理也很简单,Binder Client 先是经过 0 号引用去访问 ServerManager 获取该 Binder Server 的引用,获得引用后就能够像普通方法调用那样调用 Binder 实体的方法。最后咱们的 ServerManager 则用来管理 Binder Server,Binder Client 能够经过它来查询 Binder Server 接口,刚才咱们说到 Binder Client 能够经过 ServerManager 来获取 Binder Server 的引用,这个 Binder Server 的引用就是由 ServerManager 来转换的,其实不如说映射更直接,Binder Server 在生成一个 Binder 实体的同时会为其绑定一个名字并将这个名字封装成一个数据包传递给 Binder Driver,Binder Driver 接收到这个数据包后,若是发现这个 Binder 是新传递来的,那么就会为其在内核空间中建立对应的 Binder 实体节点和一个对该实体节点的引用,这个实体节点在相应的源码中叫作 Binder_node 而其引用则叫作 Binder_ref,建立完毕后,Binder Driver 就会将该引用传递给 ServerManager ,ServerManager 收到后就会从中取出该 Binder 的名字和引用插入一张数据表中,这跟 DNS 中存储的域名到 IP 地址的映射原理相似,而对于网络访问来讲,DNS 服务器也并不必定对每个 IP 地址都有域名映射的记录,咱们经常也会碰到直接经过 IP 地址访问服务器的状况,而 Binder 也同样并不是必定要在 ServerManager 中有记录,不少时候 Binder Server 会将一个 Binder 实体封装进数据包传递给 Binder Client,而此时 Binder Server 会在该数据包中标注 Binder 实体的位置,Binder Driver 则会为该匿名的 Binder 生成实体节点和实体引用,并将该引用传递给 Binder Client。从大的角度来讲, ServerManager 其实也是一个标准的 Binder Server,而且在 Android 中约定其在 Binder 通讯的过程当中惟一标识永远是 0 ,这个标识就是进程号,只不过它在 Binder Driver 中是最早被注册的。
  ServerManager 既然是一个标准的 Binder Server,那么它应该对外公布其可用的接口方法,这里你能够将它看做一个联想服务器,既然能让客户端访问,总得给客户端可访问的接口和数据吧,以博客 java/android 设计模式学习笔记(9)—代理模式 中的 ActivityManagerNative 为例来分析,在 ActivityManagerNative 的 gDefault 对象中,有这样一行代码:
IBinder b = ServiceManager.getService("activity");复制代码
经过 ServiceManager 去获取 AMS 的 Binder 对象,ServiceManager 类的代码很简单:
public final class ServiceManager {
private static final String TAG = "ServiceManager";

private static IServiceManager sServiceManager;
private static HashMap<String, IBinder> sCache = new HashMap<String, IBinder>();

private static IServiceManager getIServiceManager() {
if (sServiceManager != null) {
return sServiceManager;
}

// Find the service manager
sServiceManager = ServiceManagerNative.asInterface(BinderInternal.getContextObject());
return sServiceManager;
}

/
Returns a reference to a service with the given name.
@param name the name of the service to get @return a reference to the service, or <code>null</code> if the service doesn't exist
*/

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 getService", e);
}
return null;
}

public static void addService(String name, IBinder service) {
...
}
....
}复制代码
  • 每次经过 ServiceManager 的 getService() 方法取得一个 SystemService 的引用,实际上只是经过 getIServiceManager() 取回一个 Proxy 对象,而后再调用这个 Proxy对象的 getService() 方法;
  • getIServiceManager(),实际上则是经过IServiceManager接口,访问到一个ServiceManagerNative对象。
IServiceManager 为一个接口,它所承担的就是 “DNS 服务器” 的角色,即 ServerManager 角色:


public interface IServiceManager extends IInterface
{

/
Retrieve an existing service called @a name from the service manager. Blocks for a few seconds waiting for it to be
published if it does not already exist. /

public IBinder getService(String name) throws RemoteException;

/
Retrieve an existing service called @a name from the service manager. Non-blocking.
*/

public IBinder checkService(String name) throws RemoteException;

/
Place a new @a service called @a name into the service manager.
/

public void addService(String name, IBinder service, boolean allowIsolated)
throws RemoteException;

/** Return a list of all currently running services.
/

public String[] listServices() throws RemoteException;

/** Assign a permission controller to the service manager. After set, this
interface is checked before any services are added. /

public void setPermissionController(IPermissionController controller)
throws RemoteException;

static final String descriptor = "android.os.IServiceManager";

int GET_SERVICE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION;
int CHECK_SERVICE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+1;
int ADD_SERVICE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+2;
int LIST_SERVICES_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+3;
int CHECK_SERVICES_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+4;
int SET_PERMISSION_CONTROLLER_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+5;
}复制代码


它的具体实现类是 ServiceManagerNative 和 ServiceManagerProxy 类(和 ActivityManagerNative, ActivityManagerProxy与 IActivityManager 的关系同样: java/android 设计模式学习笔记(9)—代理模式),ServiceManagerNative 和 ServiceManagerProxy 类都属于 Binder Client 端, ServiceManager 和 Binder Driver 在 Android 平台中已经实现。这里要提到的一点是 ServiceManager 在 Java 和 Native 环境里各有其实现,但在 Java 端实际上只有 Proxy端,而 Native 环境里实现的 Servicemanager 才具备完整的 Proxy 与 Stub 实现,这也就是为何 ServiceManagerNative 和 ServiceManagerProxy 相对于 ServiceManager 这个 BinderServer 角色来讲都是属于 BinderClient 了。Binder的介绍就到此为止了,毕竟太复杂,须要详细了解 Binder 的能够看看老罗的文章: blog.csdn.net/luoshengyan…
或者这篇博客也讲的很清楚: blog.csdn.net/21cnbao/art…

AIDL


 AIDL的全称是Android Interface definition language,一看就明白,它是一种android内部进程通讯接口的描述语言,经过它咱们能够定义进程间的通讯接口,用处固然就是用来进程间的通讯和方法调用了(我在 IPC通讯上篇中介绍过也可使用Messenger加上反射机制来进行跨应用的方法调用,可是前提是让两个应用在一个进程中,局限性比AIDL大)。先介绍一下AIDL进程间通讯的流程:
  1. AIDL接口的建立
  2. AIDL文件中,并非全部的数据类型都是可使用的,它支持的数据类型有:
    • 基本数据类型(int,long,char,boolean,double等)
    • String和CharSequence
    • List:只支持ArrayList,并且list中的元素也必须是AIDL支持的类型
    • Map:只支持HashMap,里面的key和value也必须是AIDL支持的类型
    • Parceable:全部实现了Parceable接口的对象
    • AIDL:全部的AIDL接口自己也能够在AIDL文件中使用,因此IBinder类型也是支持的。
  3. 服务端
  4. 服务端首先要建立一个Service用来监听客户端的请求,而后将在对应AIDL文件中声明的接口实现,而且经过onbind函数返回相应IBinder对象便可。
  5. 客户端
  6. 客户端所要作的事情就稍微简单一些,首先须要绑定服务端的Service,绑定成功后,将服务端返回的Binder对象转成AIDL接口所属的类型,接着就能够调用AIDL中的方法了。
  更多内容也能够去看 google文档,介绍完流程以后,紧接着来介绍一下demo中的相关代码。

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名字一致)包下面,作到这两点就能够了。一个工程和两个工程的多进程本质是同样的,有兴趣的能够本身试试。

java文件


Parcelable实体类


  咱们来看看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;
}
}复制代码

  服务端的实现比较复杂,咱们一步步来分析:
  1. 服务端固然是个Service,在该Service中咱们须要新建一个binder对象,这个binder对象是一个由IWeatherManager.aidl生成的IWeatherManager接口中的内部Stub类的对象,该对象须要实现4个接口中的方法。而后在onBind函数返回这个binder对象便可。
  2. 为了支持多进程的并发读写,咱们须要使用CopyOnWriteArrayList而不是普通list,相似的还有ConcurrentHashMap。
  3. 若是须要为这个Service增长访问的权限,有三个方法来实现:
    • 先使用permission标签订义一个permission(详情看博客 android permission权限与安全机制解析(上)),而后在manifest文件中的服务端service标签中添加android:permission=”yourPermissionName”便可。
    • 一样的先声明一个permission,接着在Service的onBind函数中,经过checkCallingPermission函数检测调用者是否使用了该声明的权限,若是没有就直接返回null。
    • 在onTransact函数中进行检测,和onBind中的检测同样,不经过返回false,并且在该函数中还能够检测调用者的package name,在demo中若是调用者应用的包名不是以com.android开头,就会拒绝访问。简单介绍一下onTransact函数,该函数运行在服务端中的Binder线程池中,当客户端发起跨进程请求时,远程请求会经过系统底层封装后交由此方法来处理,若是此方法返回false,那么客户端的请求会失败,因此在这个方法中检测权限也是能够的。
  4. 如何为服务端添加监听器?咱们知道客户端的listener对象通过parcelable以后到服务端的对象并非同一个对象,因此若是客户端想要解注册一个listener,调用服务端removeListener函数并传入一个listener参数,可是这个listener对象通过parcelable以后并非原来的那个对象,服务端没法处理,因此为了应对这种状况,系统专门提供了用于跨进程删除listener的接口RemoteCallbackList。RemoteCallbackList是一个泛型,由于继承自IInterface接口,因此支持管理任意的AIDL接口。为何RemoteCallbackList类就能够识别parcelable以后的对象呢?先来看看RemoteCallbackList的实现,在它的内部有一个Map结构专门用来保存全部的AIDL回调,这个Map的key是IBinder类型,value是Callback类型:
    ArrayMap<IBinder, Callback> mCallbacks = new ArrayMap<IBinder, Callback>()
    其中Callback中封装了真正的远程listener。当客户端注册listener的时候,他会把这个listener的信息存入mCallbacks中,其中的key和value分别经过下面的方式得到:
    IBinder binder = callback.asBinder();
    Callback cb = new Callback(callback, cookie);
    虽说屡次跨进程传输客户端的同一个对象在服务端生成不一样的对象,可是这些新生成的对象有一个共同点,那就是它们底层的Binder对象是同一个,因此使用RemoteCallbackList就可以成功的删除指定listener。

客户端


  看看客户端的代码:
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线程池中,因此服务端方法能够执行大量的耗时操做,这个时候切记不要在服务端方法中开线程去进行异步任务,除非你明确知道本身在干什么,不然不建议这么作。 可是有一种方法能够发起非阻塞式的远程调用,Messager 就是采用的非阻塞式的方式通信,其关键就在于 IMessager.aidl 的实现:
这里写图片描述
相比日常自定义的 aidl,多了 oneway 的关键字,声明和不声明 oneway 关键字的在于生成 Java 类中一个参数:
这里写图片描述
这里写图片描述
不声明 oneway 时,mRemote.transact 传入的最后一个参数是 0;声明 oneway 时,mRemote.transact 传入的最后一个参数是 android.os.IBinder.FLAG_ONEWAY 。
这里写图片描述
这里写图片描述
查看 API 文档便可以看到 FLAG_ONEWAY 的做用就是让客户端可以非阻塞的调用远程方法,至此真相大白,若是咱们自定义的 aidl 也想实现非阻塞的调用,只需声明 oneway 关键字便可。
  关于服务端和客户端的方法分别执行在那个进程和线程中以及它们执行的前后顺序,咱们先来看看log日志的输出:
I/PID:28533: [TID:7035] 1.onTransact(line:90): permission granted
I/PID:28533: [TID:7035] 1.onTransact(line:99): package name accept
I/PID:28533: [TID:7035] 1.addListener(line:72): server adding listener

//client add click
I/PID:28502: [TID:1] ClientActivity.onClick(line:115): client is adding weather 罗湖
I/PID:28533: [TID:7036] 1.onTransact(line:90): permission granted
I/PID:28533: [TID:7036] 1.onTransact(line:99): package name accept
I/PID:28533: [TID:7036] 1.addWeather(line:59): server add new Weather:罗湖
I/PID:28502: [TID:1] 1.onWeatherChange(line:47): client has been notified that 罗湖 has been added
I/PID:28533: [TID:7036] 1.addWeather(line:66): server has notified the listener that weathers have been changed
I/PID:28502: [TID:1] ClientActivity.onClick(line:117): client has added weather 罗湖

//client remove listener click
I/PID:28533: [TID:7035] 1.onTransact(line:90): permission granted
I/PID:28533: [TID:7035] 1.onTransact(line:99): package name accept
I/PID:28533: [TID:7035] 1.removeListener(line:78): server removing listener

//*client get click
I/PID:28502: [TID:1] ClientActivity.onClick(line:93): client is getting weather
I/PID:28533: [TID:7036] 1.onTransact(line:90): permission granted
I/PID:28533: [TID:7036] 1.onTransact(line:99): package name accept
I/PID:28533: [TID:7036] 1.getWeather(line:52): server returns all of the weathers
I/PID:28502: [TID:1] ClientActivity.onClick(line:95): client has gotten weather复制代码

  PID:28502为客户端进程,PID:28533为服务端进程,TID:1为UI主线程,TID:7036为Binder线程。看看log打印的顺序基本就可以明白方法的执行进程,线程和客户端的阻塞状况了。
  源码下载: github.com/zhaozepeng/…

BinderPool

  上面差很少就把AIDL的用法详细介绍完了,可是有的时候咱们可能须要不止一个业务模块,也就是不仅仅须要一个天气模块,咱们还须要一个计算温度平均值的模块(虽然能够写在一个模块中,可是咱们仍是假设要用两个模块吧~),是否是须要为每一个模块都单独创建一个Service呢?固然不是,会很耗资源的好吗,解决方法就是先为每个模块创建一个单独的aidl文件,最后再创建一个总体的aidl文件用来管理这些单独的aidl。
  看看这三个文件,IWeatherManager.aidl,IComputerManager.aidl和IBinderPoolManager.aidl:java

// 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对象。
  来看看服务端的代码的变更:node

public class BinderPoolService extends Service{
public static final int CODE_WEATHER = 1;
public static final int CODE_COMPUTER = 2;复制代码

<span class="hljs-keyword">private</span> IBinderPoolManager iBinderPoolManager;

<span class="hljs-comment">//支持并发读写的list</span>
<span class="hljs-keyword">public</span> CopyOnWriteArrayList&lt;Weather&gt; weathers = <span class="hljs-keyword">new</span> CopyOnWriteArrayList&lt;&gt;();

<span class="hljs-annotation">@Override</span>
<span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">onCreate</span>() {
    <span class="hljs-keyword">super</span>.onCreate();
    Weather nanshan = <span class="hljs-keyword">new</span> Weather();
    nanshan.cityName = <span class="hljs-string">"南山"</span>;
    nanshan.temperature = <span class="hljs-number">20.5</span>;
    nanshan.humidity = <span class="hljs-number">45</span>;
    nanshan.weather = Weather.AllWeather.cloudy;

    Weather futian = <span class="hljs-keyword">new</span> Weather();
    futian.cityName = <span class="hljs-string">"福田"</span>;
    futian.temperature = <span class="hljs-number">21.5</span>;
    futian.humidity = <span class="hljs-number">48</span>;
    futian.weather = Weather.AllWeather.rain;

    weathers.add(nanshan);
    weathers.add(futian);
    iBinderPoolManager = <span class="hljs-keyword">new</span> IBinderPoolManager.Stub(){
        <span class="hljs-annotation">@Override</span>
        <span class="hljs-keyword">public</span> IBinder <span class="hljs-title">queryCode</span>(<span class="hljs-keyword">int</span> code) <span class="hljs-keyword">throws</span> RemoteException {
            <span class="hljs-keyword">switch</span> (code){
                <span class="hljs-keyword">case</span> CODE_WEATHER:
                    <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> IWeatherManager.Stub(){

                        <span class="hljs-annotation">@Override</span>
                        <span class="hljs-keyword">public</span> List&lt;Weather&gt; <span class="hljs-title">getWeather</span>() <span class="hljs-keyword">throws</span> RemoteException {
                            <span class="hljs-keyword">return</span> weathers;
                        }

                        <span class="hljs-annotation">@Override</span>
                        <span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">addWeather</span>(Weather weather) <span class="hljs-keyword">throws</span> RemoteException {
                            weathers.add(weather);
                        }
                    };
                <span class="hljs-keyword">case</span> CODE_COMPUTER:
                    <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> IComputerManager.Stub() {
                        <span class="hljs-annotation">@Override</span>
                        <span class="hljs-keyword">public</span> <span class="hljs-keyword">double</span> <span class="hljs-title">computeAverageTemperature</span>(List&lt;Weather&gt; weathers) <span class="hljs-keyword">throws</span> RemoteException {
                            <span class="hljs-keyword">double</span> sum = <span class="hljs-number">0</span>;
                            <span class="hljs-keyword">for</span> (<span class="hljs-keyword">int</span> i=<span class="hljs-number">0</span>; i&lt;weathers.size(); i++){
                                sum += weathers.get(i).temperature;
                            }
                            <span class="hljs-keyword">return</span> sum/weathers.size();
                        }
                    };
                <span class="hljs-keyword">default</span>:
                    <span class="hljs-keyword">return</span> <span class="hljs-keyword">null</span>;
            }
        }
    };
}

<span class="hljs-annotation">@Override</span>
<span class="hljs-keyword">public</span> IBinder <span class="hljs-title">onBind</span>(Intent intent) {
    <span class="hljs-keyword">return</span> iBinderPoolManager.asBinder();
}复制代码

}  根据code的不一样返回不一样的IBinder对象,这样在客户端中就可以获取对应AIDL接口的IBinder对象,最终就能在客户端调用不一样AIDL模块中的方法。客户端代码很简单,在这里就不介绍了,感兴趣的能够去看看源码:
  github.com/zhaozepeng/…
  关于IPC相关知识的介绍就到这了,若是有什么疑问,你们能够多多交流啊,谢谢~android

相关文章
相关标签/搜索