前言
- 此教程的目的是教会大家如何使用AIDL,包括定义AIDL服务、调用AIDL服务、传递复杂对象、AIDL回调客户端等。
概述
-
全称Android Interface Definition Language。
-
像其他IDLs一样,允许你定义编程接口,以便客户端和服务能通过内部进程通信(interprocess communication,IPC)。
定义AIDL服务
- 创建.aidl文件
- SDK生成对应.java文件和Stub内部类
- 通过Service子类将接口暴露给外界
1. 创建.aidl文件
-
用Java编程语言来构造.aidl文件。每个.aidl文件必须定义一个带方法声明的接口。
-
AIDL支持以下数据类型:
- Java基本类型,即int、long、char等;
- String;
- CharSequence;
- List
- List中的所有元素都必须是AIDL支持的数据类型、其他AIDL接口或你之前声明的Parcelable实现类。
- Map
- Map中的所有元素都必须是AIDL支持的数据类型、其他AIDL接口或你之前声明的Parcelable实现类。
- 其他类型,必须要有import语句,即使它跟.aidl是同一个包下。
-
AIDL中的方法和变量
- 方法可有零、一或多个参数,可有返回值或void。
- 所有非基本类型的参数都需要标签来表明这个数据的去向:
- in,表示此变量由客户端设置;
- out,表示此变量由服务端设置;
- inout,表示此变量可由客户端和服务端设置;
- 基本类型只能是in。
- 只expose方法,不会expose静态变量。
-
.aidl文件保存在项目
/src/<SourceSet>/aidl
目录下。// IRemoteService.aidl package com.daking.aidl; // Declare any non-default types here with import statements interface IRemoteService { /** * 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); }
2. SDK生成对应.java文件和Stub内部类
-
当编译APP时,SDK工具会将项目
/src/<SourceSet>/aidl
目录下的.aidl文件一个个在项目/build/generated/source/aidl
目录下生成IBinder接口.java文件。两个文件名一样,只是后缀不同。如IRemoteService.aidl生成IRemoteService.java。 -
Stub内部类
- .aidl文件编译后生成的.java文件中自动生成的内部类。
- public static abstract声明。
- extends android.os.Binder。
- 实现.aidl文件中定义的接口,且声明其所有方法。
-
实现Stub内部类要注意
- 对于传过来的调用,无法保证是在主线程中执行的。Service必须要考虑多线程和线程安全。
- 默认情况下,RPC都是异步的。避免在主线程中调用AIDL,不然可能会导致ANR。
- 不能给调用方回抛异常。
3. 通过Service子类将接口暴露给外界
-
需要创建Service子类,并在
onBind()
中返回Stub内部类。package com.daking.aidl; import android.app.Service; import android.content.Intent; import android.os.IBinder; import android.os.RemoteException; public class RemoteService extends Service { public RemoteService() {} @Override public IBinder onBind(Intent intent) { return mBinder; } private final IRemoteService.Stub mBinder = new IRemoteService.Stub() { @Override public void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat, double aDouble, String aString) throws RemoteException { } }; }
-
<service>
配置,设置exported为true、自定义action名等。<service android:name=".RemoteService" android:enabled="true" android:exported="true"> <intent-filter> <action android:name="com.daking.aidl.RemoteService" /> <category android:name="android.intent.category.DEFAULT" /> </intent-filter> </service>
调用AIDL服务
-
若客户端组件和服务分开在不同APP,那么客户端所在APP的
/src/<SourceSet>/aidl
目录下必须要有一份.aidl副本。 -
绑定AIDL服务
Intent intent = new Intent(); intent.setPackage("com.daking.aidl"); intent.setAction("com.daking.aidl.RemoteService"); bindService(intent, serviceConnection, Context.BIND_AUTO_CREATE);
-
当客户端
onServiceConnected()
接收到这个AIDL服务返回的IBinder时,必须要将其强制类型转换为YourServiceInterface类型。如public void onServiceConnected(ComponentName className, IBinder service) { mIRemoteService = IRemoteService.Stub.asInterface(service); }
-
可像普通对象一样调用
mIRemoteService
。注意,AIDL服务默认是运行在主线程中,若里面有耗时操作,应该在子线程中调用AIDL。
传递复杂对象
-
在AIDL中传递的复杂对象必须要实现Parcelable接口,这是因为Parcelable允许Android系统将复杂对象分解成基本类型以便在进程间传输。
-
Parcelable实现类
- implements Parcelable;
- 实现writeToParcel(),它会读取这个对象的当前状态并写入一个包中;
- 实现describeContents();
- 添加一个实现Parcelable.Creator接口的静态变量CREATOR。
package com.daking.aidl; import android.os.Parcel; import android.os.Parcelable; public class RequestVO implements Parcelable { private String name; private String type; public RequestVO() {} public RequestVO(Parcel source) { super(); this.setName(source.readString()); this.setType(source.readString()); } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getType() { return type; } public void setType(String type) { this.type = type; } public int describeContents() { return 0; } public void writeToParcel(Parcel dest, int flags) { dest.writeString(name); dest.writeString(type); } public static final Parcelable.Creator<RequestVO> CREATOR = new Parcelable.Creator<RequestVO>() { public RequestVO createFromParcel(Parcel source) { return new RequestVO(source); } public RequestVO[] newArray(int size) { return new RequestVO[size]; } }; }
-
若客户端组件和服务分开在不同APP,必须要把该Parcelable实现类.java文件拷贝到客户端所在的APP,包路径要一致。
-
另外,需要为这个Parcelable实现类定义一个相应的.aidl文件。与AIDL服务接口.aidl同理,客户端所在APP的
/src/<SourceSet>/aidl
目录下也要有这份副本。package com.daking.aidl; parcelable RequestVO;
-
将复杂对象作为AIDL接口的形参时,记得加上
in
。import com.daking.aidl.RequestVO; interface IRemoteService { void request(in RequestVO vo); }
AIDL服务回调客户端
-
自定义回调接口.aidl。
package com.daking.aidl; import com.daking.aidl.ResponseVO; // 自定义结构类,具体实现可参考上一节。 interface ICallback { void onResult(in ResponseVO vo); }
-
AIDL服务.aidl提供接口给客户端注册和注销此回调。
package com.daking.aidl; import com.daking.aidl.RequestVO; import com.daking.aidl.ICallback; interface IRemoteService { void request(in RequestVO vo); void registerCallback(in ICallback cb); void unregisterCallback(in ICallback cb); }
-
AIDL服务.java的具体实现。
public class RemoteService extends Service { // ICallback列表 private RemoteCallbackList<ICallback> icallbacks; @Override public IBinder onBind(Intent intent) { icallbacks = new RemoteCallback<ICallback>(); return mBinder; } private final IRemoteService.Stub mBinder = new IRemoteService.Stub() { @Override public void request(in RequestVO vo) { sendResponse(); } @Override public void registerCallback(in ICallback cb) { if(cb != null) { icallbacks.register(cb); } } @Override public void unregisterCallback(in ICallback cb) { if(cb != null) { icallbacks.unregister(cb); } } }; private void sendResponse() { ResponseVO vo = new ResponseVO(); // 以广播的方式进行客户端回调 int len = icallbacks.beginBroadcast(); for (int i = 0; i < len; i++) { try { icallbacks.getBroadcastItem(i).onResult(vo); } catch (RemoteException e) { e.printStackTrace(); } } // 记得要关闭广播 icallbacks.finishBroadcast(); } }
-
客户端创建回调接口的实现对象,并注册到AIDL。
protected ICallback callback = new ICallback.Stub() { @Override public void onResult(ResponseVO vo) { // AIDL回调客户端后的业务处理 } }; // mService为AIDL服务 mService.registerCallback(callback);
-
Gradle跨module引用aidl
工程中有两个module,module A和module B,module A在build.gradle中通过compile project(:B)引用了module B。module B定义了com.luo.TestB.aidl,同时module A定义的TestA.aidl并且Test B.aidl,但是as编译失败,提示无法找到TestB.aidl。原因是gradle编译module B时默认不会将aidl文件打进aar中。
解决方法
在module B的build.gradle中添加aidlPackageWhiteList "com/luo/TestB.aidl",即如下所示
apply plugin: 'com.android.library'
dependencies {
...
}
android {
...
aidlPackageWhiteList "com/luo/TestB.aidl"
...
}
网友评论