最近一个项目涉及到跨进程的操作,我们的应用ClientApp需要用到底层的数据,但是这个数据data是在一个不断监听接收底层的服务ServiceA里面,痛苦的是这个服务是在另外一个进程里面。虽然我们可以用万能的广播机制,但这会造成系统非常大的负担。另外一种方法可以用ContentProvider,很多时候还是挺好用的,但是实时性不够,两个进程之间也缺乏交互性,所以我决定用AIDL来实现这一功能。
1、AIDL基本介绍
AIDl是Android中专门针对跨进程而设计的一种机制。AIDL全称是Android Interface Definition Language(安卓接口定义语言),使用它的语法和Java的风格很相似。这是我写一个aidl文件:
package com.zhonghong.airconditione.aidl;
import com.zhonghong.airconditione.aidl.NotifyCallBack;
interface HealthyServer{
int[] getHealthyInfo();
int[] getWheelPressure();
void registerCallBack(NotifyCallBack callback);
void unregisterCallBack(NotifyCallBack callback);
}
项目结构如下:
大家可以看到我在这个定义了一系列的接口,就像我们平时用到的回调接口一样,只是和java不同的是返回值前面不能有其他的修饰字符。同时注意以下几点:
a、AIDL支持的java中8种基本数据类型:byte、short、int、float、double、char、boolean;
String类型;
CharSequence类型;
List类型,当然list里面的元素必须是AIDL所支持的;
Map类型,同理;
b、如果想要自定义类型,那么就需要实现Parcelable接口让对象序列化;
c、如图所示,即使两个aidl文件是在同一个包下面,引用其他的非默认支持的类型时也要导包。
d、参数其实是分三种的,一种是只能输入参数 in,例如:void setSomething(in int data),默认是in参数;一种是输出参数 out,当客户端用这种参数时,服务端不会获取数据,但是当这个对象在服务端改变的时候,客户端的数据也会跟着改变。另外一种就是输入输出参数 inout。
2、使用AIDL
定义完AIDL文件后,系统会在gen文件里自动生成相关的java接口,如下:
在我的服务端里面,我就会根据这个接口生成一个binder返回给客户端:
public class MyIBinder extends HealthyServer.Stub{
@Override
public int[] getHealthyInfo() throws RemoteException {
return carHealthyData;
}
@Override
public int[] getWheelPressure() throws RemoteException {
return wheelPressureData;
}
public void setActivity(MainActivity activity){
mainActivity = activity;
}
public void setHandler(Handler handler){
mHandler = handler;
}
public AcStutas getAcStatues(){
return mAcStutas;
}
public void setNull(){
mainActivity=null;
mHandler = null;
}
@Override
public void registerCallBack(NotifyCallBack callback) throws RemoteException {
/**此处是服务端获得回调对象 */
mCallBacks.register(callback);
}
@Override
public void unregisterCallBack(NotifyCallBack callback) throws RemoteException {
mCallBacks.unregister(callback);
}
}
在客户端这边,我们想要使用其他进程提供的AIDL服务,那么也需要有和服务端相同的.adil文件,同样也在gen文件里生成相应的接口,然后去绑定服务端获取binder。客户端代码如下:
private ServiceConnection conn = new ServiceConnection() {
@Override
public void onServiceDisconnected(ComponentName name) {
try {
mServer.unregisterCallBack(callback);
} catch (RemoteException e) {
e.printStackTrace();
}
}
@SuppressLint("NewApi")
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
/** 这句最重要,就是获取到binder*/
mServer = HealthyServer.Stub.asInterface(service);
try {
/** 这里注册回调*/
mServer.registerCallBack(callback);
} catch (RemoteException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
refresh();
if(healthyData==null){
shareHealthy();
}
if(wheelPressure==null){
shareWheel();
}
}
};
这里的绑定动作和一般的绑定服务没什么区别,在这里我只说明一个小问题,我们的服务Service可以被多个客户端绑定,当第一个客户端绑定成功并且返回binder之后,其他客户端绑定该服务时同样也只会返回这个binder。下面我们说说这里的跨进程回调吧。
3、跨进程的回调机制:
a、首先,我们再aidl文件中定义这个回调接口:
package com.zhonghong.airconditione.aidl;
interface NotifyCallBack{
void notifychanged(int event);
}
b、在服务端我们需要用到一个系统提供的泛型容器
private RemoteCallbackList<NotifyCallBack> mCallBacks = new RemoteCallbackList<NotifyCallBack>();
然后在服务端接收回调对象的地方,把这个对象放入容器:
@Override
public void registerCallBack(NotifyCallBack callback) throws RemoteException {
mCallBacks.register(callback);
}
@Override
public void unregisterCallBack(NotifyCallBack callback) throws RemoteException {
mCallBacks.unregister(callback);
}
c、在客户端我们去实现这个回调接口,并在绑定服务成功后去设置这个回调:
NotifyCallBack callback = new NotifyCallBack.Stub() {
@Override
public void notifychanged(int event) throws RemoteException {
handler.sendEmptyMessage(0x01);
}
};
/** 这个是在ServiceConnection里的绑定成功的回调方法*/
public void onServiceConnected(ComponentName name, IBinder service) {
mServer = HealthyServer.Stub.asInterface(service);
try {
mServer.registerCallBack(callback);
} catch (RemoteException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
refresh();
if(healthyData==null){
shareHealthy();
}
if(wheelPressure==null){
shareWheel();
}
}
d、当服务端有底层的数据发送上来的时候,它就会调用回调函数通知有新的数据更新,但是这个和一般的调用不同,需要有特定的方式才能都跨进程通讯:
try {
int len = mCallBacks.beginBroadcast();
Log.i(TAG, "len:"+len);
for(int i =0;i<len;i++){
mCallBacks.getBroadcastItem(i).notifychanged(COM_MSG_5E0H);
};
} catch (RemoteException e) {
e.printStackTrace();
}
Log.i(TAG, "finish braodcast!");
mCallBacks.finishBroadcast();
记住发送完信息后,需要关闭信息通道。