Android中AIDL的基本用法
Android 中AIDL的使用与理解
Android AIDL使用详解
彻底明白Android中AIDL及其使用
AIDL使用解析
AIDL 简介
AIDL 的全称为Android Interface Definition Language,是一种android内部进程通信接口的描述语言,通过它我们可以定义进程间的通信接口。
- 当作为客户的一方和要和作为服务器的一方进行通信时,需要指定一些双方都认可的接口, 这样才能顺利地进行通信。而AIDL就是定义这些接口的一种工具。为什么要借助AIDL来定义,而不直接编写接口呢(比如直接通过Java定义一个Interface)? 这里涉及到进程间通信(IPC)的问题。和大多数系统一样,在Android平台下,各个进程都占有一块自己独有的内存空间,各个进程在通常情况下只能访问自己的独有的内存空间,而不能对别的进程的内存空间进行访问。 进程之间如果要进行通信,就必须先把需要传递的对象分解成操作系统能够理解的基本类型,并根据你的需要封装跨边界的对象。而要完成这些封装工作,需要写的代码量十分地冗长而枯燥。因此Android提供了AIDL来帮助你完成这些工作。
IPC:inter-process communication :进程间通信
什么时候使用 AIDL?
- 如果不需要IPC,那就直接实现通过继承Binder类来实现客户端和服务端之间的通信。
- 如果确实需要IPC,但是无需处理多线程,那么就应该通过Messenger来实现。Messenger保证了消息是串行处理的,其内部其实也是通过AIDL来实现。
- 在有IPC需求,同时服务端需要并发处理多个请求的时候,使用AIDL才是必要的。
一句话总结:
只有当你允许来自不同的客户端访问你的服务并且需要处理多线程问题时你才必须使用AIDL。
使用 AIDL
一、服务端
1. 编写 .aidl 文件,定义需要的接口
在Android Studio下,右键src文件夹,选择新建AIDL文件,并填写名字,点击Finish后,会发现main下多了一个名字为AIDL的目录,目录下的包名和Java的包名保持一致,包下即是新建的.aidl文件:
内容我们编写如下:
package com.rickqiu.aidltest;
interface IMyAidlInterface {
String addString(String string);
}
接口里创建了一个很简单的抽象方法 addString(),用来给客户端调用,作用很简单,接收客户端发来的 String 并在后面加上一段 String 后返回给客户端。
最后make project或者make对应的module,Android SDK就会根据我这里编写的.AIDL文件生成对应的Java文件。
在Android Studio下,可以在build/generated/aidl目录下找到这些Java文件。
2. 实现定义的接口,并将接口暴露给客户端调用
创建一个服务,利用 new IMyAidlInterface.Stub() 实现 AIDL 接口里的方法,并得到一个 Binder,并在 Service 的 onBind() 方法里返回这个 Binder。
public class MyService extends Service {
private String s="service return";
private IMyAidlInterface.Stub mBinder = new IMyAidlInterface.Stub(){
@Override
public String addString(String string) throws RemoteException {
Log.d("ppqq","收到客户端信息:"+string);
return string+"+"+s;
}
};
@Override
public IBinder onBind(Intent intent) {
return mBinder.asBinder();
}
}
Stub 是 IMyAidlInterface.java 里的一个静态抽象类,它继承了 Binder 类,并抽象实现了 IMyAidlInterface 接口。
记得注册 Service,并加上一个 action:
<service
android:name=".MyService"
android:enabled="true"
android:exported="true">
<intent-filter>
<action android:name="com.rickqiu.aidltest.MyService"/>
</intent-filter>
</service>
到此服务端的准备完成了。
二、客户端:
1. 创建和服务端一模一样的 .aidl 文件
服务端和客户端的 AIDL 接口要完全相同,包括包名,也就是说要在不同工程下建立相同的包名,不然会出现 Java.lang.SecurityException: Binder invocation to an incorrect interface 这个错误。
服务端:
客户端:
一样 make project 出 .java。
2. 创建 ServiceConnection,在 ServiceConnection 的 onServiceConnected() 方法里通过 IMyAidlInterface.Stub.asInterface(IBinder service) 获得 IMyAidlInterface 接口的对象,通过这个对象就能调用服务端的方法了
public class MainActivity extends AppCompatActivity {
private IMyAidlInterface myAidlInterface;
private ServiceConnection connection;
private EditText editText;
private TextView textView;
private Button button;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
editText = (EditText) findViewById(R.id.edit);
textView = (TextView) findViewById(R.id.text);
button = (Button) findViewById(R.id.button);
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent intent = new Intent();
intent.setPackage("com.rickqiu.aidltest");
intent.setAction("com.rickqiu.aidltest.MyService");
bindService(intent, connection, BIND_AUTO_CREATE);
}
});
connection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
Log.d("ppqq", "Service Connected");
// 这里的service就是服务端onBinde()方法里返回的Binder
myAidlInterface = IMyAidlInterface.Stub.asInterface(service);
String result="";
try {
result=myAidlInterface.addString(editText.getText().toString());
} catch (RemoteException e) {
e.printStackTrace();
}
textView.setText(result);
}
@Override
public void onServiceDisconnected(ComponentName name) {
Log.d("ppqq", "Service Disconnected");
}
};
}
}
3. 绑定服务端的 Service,绑定成功时会调用 onServiceConnected() 方法并实现和服务端的通信
Intent intent = new Intent();
intent.setPackage("com.rickqiu.aidltest");
intent.setAction("com.rickqiu.aidltest.MyService");
bindService(intent, connection, BIND_AUTO_CREATE);
注意,Android5.0 后不能直接隐式启动服务,不过可以多设置一下包名来实现隐式启动。intent.setPackage("com.rickqiu.aidltest")
注意这是 Service 所在的 Project 的包名。
三、传递自定义类型
默认情况下,AIDL支持下列所述的数据类型:
- 所有的基本类型(int、float等)
- String
- CharSequence
- List
- Map
其中,List和Map中的元素类型必须是上述类型之一或者由其他AIDL生成的接口类型,或者是已经声明的Pacelable类型。
List类型可以指定泛型类,比如写成List<String>, 并且对方接收到的具体实例都是ArrayList。
Map类型不支持指定泛型类,比如Map<String,String>。只能Map表示类型,并且对方接收到的具体实例都是HashMap。
如果想在进程间传递其他类型,必须实现Parcelable接口实现序列化。
比如想在进程间传递一个Book对象:
服务端;
IMyAidlInterface.aidl:
package com.rickqiu.aidltest;
import com.rickqiu.aidltest.Book;
interface IMyAidlInterface {
Book getBook();
void setBook(in Book book);
int getProcessId();
}
先要把 Book 序列化:
Book.java:
package com.rickqiu.aidltest;
import android.os.Parcel;
import android.os.Parcelable;
public class Book implements Parcelable{
private int id;
private String name;
private float price;
public Book() {
}
public Book(int id, String name, float price) {
this.id = id;
this.name = name;
this.price = price;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public float getPrice() {
return price;
}
public void setPrice(float price) {
this.price = price;
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeInt(id);
dest.writeString(name);
dest.writeFloat(price);
}
public final static Creator<Book> CREATOR = new Creator<Book>(){
@Override
public Book createFromParcel(Parcel source) {
Book book=new Book();
book.id=source.readInt();
book.name=source.readString();
book.price=source.readFloat();
return book;
}
@Override
public Book[] newArray(int size) {
return new Book[size];
}
};
}
定义好Book.java之后,还需要新增一个与其同名的AIDL文件。那么同样按照刚才的步骤右键src文件夹,添加一个名为Book的AIDL文件。
这个AIDL的编写十分简单,只需要简单的声明一下要用到的Pacelable类即可,有点类似C语言的头文件,这个AIDL文件是不参与编译的。
注意到parcelable的首字母是小写的,这算是AIDL一个特殊的地方。
Book.aidl:
package com.rickqiu.aidltest;
parcelable Book;
接下来还需要再IMyAidlInterface.aidl文件中使用import关键字导入这个Book类型。详细的写法参考上面的IMyAidlInterface.aidl代码。
即便IMyAidlInterface.aidl和Book.aidl位于同一个包下,这里的import是必须要有的。这也是AIDL一个特殊的地方。
文件结构;
客户端:
客户端也需要一个 Parcelable 过的 Book.java。
也同样需要 IMyAidlInterface.aidl 和 Book.aidl。
注意,Book.aidl 的包名要与客户端这里的 Book.java 相同,IMyAidlInterface.aidl 里 import 的是客户端这边 Book 的包名。
IMyAidlInterface.aidl:
package com.rickqiu.aidltest;
import com.rickqiu.aidltestclient.Book;
interface IMyAidlInterface {
String addString(String string);
Book getBook();
void setBook(in Book book);
int getProcessId();
}
文件结构;
方向指示符
注意到 IMyAidlInterface.aidl 里 Book 参数前面有个 in。
- 非原始类型中,除了String和CharSequence以外,其余均需要一个方向指示符。
- in表示由客户端设置。
- out表示由服务端设置。
- inout表示客户端和服务端都设置了该值。
四、效果
不打开AIDLTest应用,打开AIDLTestClient应用,输入字符串:
按下按键绑定服务,给服务端提供数据,并接收服务端返回的数据:
五、回调
上面的简单例子,是客户端主动传输数据给服务器,服务器只能被动通过 return 返回数据给客户端。如果想服务器主动传输数据给客户端,可以注册一个回调。
- IRemoteService.aidl:
interface IRemoteService
{
void registerCallback(in IRemoteCallback cb);
void unregisterCallback(in IRemoteCallback cb);
......
}
- IRemoteCallback.aidl:
interface IRemoteCallback
{
void onConnection(in int status);
void onConfig(in Config config);
void onCommand(in Command cmd);
void onResponse(in Response rsp);
void onPackage(in Package pkg);
}
-
客户端:
在客户端里设置好回调 IRemoteCallback 的各个方法,然后通过 registerCallback(in IRemoteCallback cb) 传入 IRemoteService,之后在服务器就能调用 IRemoteCallback 的各个方法了:
private IRemoteCallback mCallback = new IRemoteCallback.Stub()
{
@Override
public void onConfig(Config config) throws RemoteException {
......
}
@Override
public void onCommand(Command cmd) throws RemoteException {
......
}
@Override
public void onResponse(Response rsp) throws RemoteException {
......
}
@Override
public void onPackage(Package pkg) throws RemoteException {
......
}
@Override
public void onConnection(int status) throws RemoteException {
......
}
};
......
mRemoteService.registerCallback(mCallback);
-
服务器端:
因为用到 AIDL 时一般都是多线程并发的情况,也就是可能有好几个客户端通过 AIDL 连接服务器,并都注册了一个回调,这就要通过 RemoteCallbackList 来管理多个不同客户端的回调:
private RemoteCallbackList<IRemoteCallback> mRemoteCallbacks = new RemoteCallbackList<IRemoteCallback>();
......
private IRemoteService.Stub mBinder = new IRemoteService.Stub() {
@Override
public void registerCallback(IRemoteCallback cb) throws RemoteException {
if (cb != null) {
mRemoteCallbacks.register(cb);
}
}
@Override
public void unregisterCallback(IRemoteCallback cb) throws RemoteException {
if (cb != null) {
mRemoteCallbacks.unregister(cb);
}
}
调用回调的方法:
int cbCount = mRemoteCallbacks.beginBroadcast();
for (int i = 0; i < cbCount; i++) {
final IRemoteCallback cb = mRemoteCallbacks.getBroadcastItem(i);
try {
cb.onConnection(connectionStatus);
} catch (RemoteException e) {
e.printStackTrace();
}
}
mRemoteCallbacks.finishBroadcast();
AIDL 高级示例
见 AIDL使用解析