前言
IPC 系列文章:
建议按顺序阅读。
Android IPC 之Service 还可以这么理解
Android IPC 之Binder基础
Android IPC 之Binder应用
Android IPC 之AIDL应用(上)
Android IPC 之AIDL应用(下)
Android IPC 之Messenger 原理及应用
Android IPC 之服务端回调
Android IPC 之获取服务(IBinder)
Android Binder 原理换个姿势就顿悟了(图文版)
上一篇文章分析了如何使用Binder进行进程间通信以及提出了直接使用Binder编码的缺点,本篇阐述如何使用AIDL解决以上缺点。
通过本篇文章,你将了解到:
1、如何编写AIDL 文件
2、如何使用AIDL
1、如何编写AIDL 文件
什么是AIDL
AIDL 是Android Interface Definition Language (Android 接口定义语言)的缩写。
创建AIDL 文件
Android Studio本身支持创建AIDL文件,先创建名为IMyServer 的AIDL文件。
在Module上右键单击:
输入名字:
确定后生成 IMyServer.aidl文件:
可以看出,由于是第一次创建AIDL文件,因此还创建了aidl文件夹并添加了包名作为目录结构,其总体结构如下:
src/main/aidl/com/fish/myapplication/IMyServer.aidl
其中/aidl目录与/java、/res目录平级,都在main目录下:
app/src/main/
app/src/aidl/
app/src/res
AIDL 文件内容
生成IMyServer.aidl内容如下:
//包名
package com.fish.myapplication;
interface IMyServer {
//aidl 支持的基本数据类型
//默认生成的方法,可以去掉
void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat,
double aDouble, String aString);
}
其中basicTypes(xx)方法是自动生成的,用来指导我们如何编写方法,可以去掉。
IMyServer 接口里声明的方法为Server端暴露给外部调用的方法,先为Server添加方法:
//包名
package com.fish.myapplication;
interface IMyServer {
//只有一个参数,并且没有返回值
void say(String word);
//有两个参数,并且返回int
int tell(String word, int age);
}
然后编译工程。
AIDL 编译产物
编译成功后,切换到Project模式,搜索IMyServer.java:
可以看出,编写的AIDL文件,最终根据一定的规则映射生成Java文件,接着来看看IMyServer.java内容。
/*
* This file is auto-generated. DO NOT MODIFY.
*/
package com.fish.myapplication;
public interface IMyServer extends android.os.IInterface
{
//默认类实现接口,可以不用关注
public static class Default implements com.fish.myapplication.IMyServer
{
@Override public void say(java.lang.String word) throws android.os.RemoteException
{
}
@Override public int tell(java.lang.String word, int age) throws android.os.RemoteException
{
return 0;
}
@Override
public android.os.IBinder asBinder() {
return null;
}
}
public static abstract class Stub extends android.os.Binder implements com.fish.myapplication.IMyServer
{
//描述符
private static final java.lang.String DESCRIPTOR = "com.fish.myapplication.IMyServer";
public Stub()
{
//调用Binder方法,将Binder与IInterface 关联起来
//也就是说Binder持有IInterface引用
this.attachInterface(this, DESCRIPTOR);
}
//通过Binder找到关联的IInterface
public static com.fish.myapplication.IMyServer asInterface(android.os.IBinder obj)
{
if ((obj==null)) {
return null;
}
android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
if (((iin!=null)&&(iin instanceof com.fish.myapplication.IMyServer))) {
//IBinder引用与调用者同一进程,直接返回IInterface
return ((com.fish.myapplication.IMyServer)iin);
}
//不同进程则返回Proxy,并传入Binder
return new com.fish.myapplication.IMyServer.Stub.Proxy(obj);
}
//返回自身
@Override public android.os.IBinder asBinder()
{
return this;
}
//重写onTransact(xx)
@Override public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException
{
java.lang.String descriptor = DESCRIPTOR;
switch (code)
{
//根据code,调用不同的方法
case INTERFACE_TRANSACTION:
{
reply.writeString(descriptor);
return true;
}
case TRANSACTION_say:
{
data.enforceInterface(descriptor);
java.lang.String _arg0;
//反序列化,读取数据
_arg0 = data.readString();
this.say(_arg0);
reply.writeNoException();
return true;
}
case TRANSACTION_tell:
{
data.enforceInterface(descriptor);
java.lang.String _arg0;
_arg0 = data.readString();
int _arg1;
_arg1 = data.readInt();
int _result = this.tell(_arg0, _arg1);
reply.writeNoException();
//写入回复
reply.writeInt(_result);
return true;
}
default:
{
return super.onTransact(code, data, reply, flags);
}
}
}
private static class Proxy implements com.fish.myapplication.IMyServer
{
private android.os.IBinder mRemote;
Proxy(android.os.IBinder remote)
{
mRemote = remote;
}
@Override public android.os.IBinder asBinder()
{
return mRemote;
}
public java.lang.String getInterfaceDescriptor()
{
return DESCRIPTOR;
}
@Override public void say(java.lang.String word) throws android.os.RemoteException
{
//构造序列化数据
android.os.Parcel _data = android.os.Parcel.obtain();
android.os.Parcel _reply = android.os.Parcel.obtain();
try {
_data.writeInterfaceToken(DESCRIPTOR);
//写入序列化
_data.writeString(word);
//mRemote为远程的IBinder
boolean _status = mRemote.transact(Stub.TRANSACTION_say, _data, _reply, 0);
//阻塞等待transact(xx)调用结果
if (!_status && getDefaultImpl() != null) {
getDefaultImpl().say(word);
return;
}
_reply.readException();
}
finally {
_reply.recycle();
_data.recycle();
}
}
@Override public int tell(java.lang.String word, int age) throws android.os.RemoteException
{
android.os.Parcel _data = android.os.Parcel.obtain();
android.os.Parcel _reply = android.os.Parcel.obtain();
int _result;
try {
_data.writeInterfaceToken(DESCRIPTOR);
_data.writeString(word);
_data.writeInt(age);
boolean _status = mRemote.transact(Stub.TRANSACTION_tell, _data, _reply, 0);
if (!_status && getDefaultImpl() != null) {
return getDefaultImpl().tell(word, age);
}
_reply.readException();
_result = _reply.readInt();
}
finally {
_reply.recycle();
_data.recycle();
}
return _result;
}
public static com.fish.myapplication.IMyServer sDefaultImpl;
}
static final int TRANSACTION_say = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
static final int TRANSACTION_tell = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1);
public static boolean setDefaultImpl(com.fish.myapplication.IMyServer impl) {
if (Stub.Proxy.sDefaultImpl == null && impl != null) {
Stub.Proxy.sDefaultImpl = impl;
return true;
}
return false;
}
public static com.fish.myapplication.IMyServer getDefaultImpl() {
return Stub.Proxy.sDefaultImpl;
}
}
//声明的公共方法
public void say(java.lang.String word) throws android.os.RemoteException;
public int tell(java.lang.String word, int age) throws android.os.RemoteException;
}
代码不长,但是初看起来云里雾里的,如果不怎么熟悉以上内容,强烈建议先阅读上篇文章:Android IPC 之Binder应用,再看这篇就很容易了。
提取重点分析上面的代码。
定义接口
定义了IMyServer 接口,该接口里的方法就是根据IMyServer.aidl里声明的方法生成的。
两个静态类
Stub是抽象类。
继承了Binder,重写了onTransact(xx)方法。
实现了IMyServer 接口,并没有实现里面的方法,这些方法待服务端实现。
当onTransact(xx)被调用的时候,根据不同的code调用相应的方法。
在上篇文章里分析的时候,onTransact(xx)与IMyServer 接口是分离的,我们需要手动在onTransact(xx)里调用IMyServer 方法。而此时Stub将两者结合起来了,完成了服务端与Binder驱动的联动。
Proxy虽然没有继承自Binder,但是持有IBinder引用:mRemote。
实现了IMyServer,并且实现了其所有方法,每个方法里最终都通过mRemote调用transact(xx)完成了客户端与Binder驱动联动。
至此,通过这两个类,分别完成了服务端、客户端与Binder的联动。
继续引用上篇的图:
可以看出,有了AIDL自动生成的类后:
1、繁杂的switch case 不用自己编写了
2、序列化反序列化也不用编写了
3、不再需要编写transact(xx)与onTransact(xx)了
极大解放了生产力。
2、如何使用AIDL
既然IMyServer.java 已经生成了,继续来看看如何使用它。
编写Server端业务
public class MyService extends Service {
private final String TAG = "IPC";
//构造内部类
private IMyServer.Stub stub = new IMyServer.Stub() {
@Override
public void say(String word) throws RemoteException {
Log.d(TAG, "receive say content:" + word + " in server");
}
@Override
public int tell(String word, int age) throws RemoteException {
Log.d(TAG, "receive tell content:" + word + " age:" + age + " in server");
return age + 1;
}
};
@Nullable
@Override
public IBinder onBind(Intent intent) {
//Stub 继承自Binder,因此是IBinder类型
return stub;
}
}
首先实现业务接口。
其次在onBind(xx)里将Binder返回给客户端。
业务逻辑实现了,等待客户端调用。
编写客户端业务
先定义ServiceConnection:
ServiceConnection serviceConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
IMyServer iMyServer = IMyServer.Stub.asInterface(service);
try {
iMyServer.say("how are you?");
int result = iMyServer.tell("how are you?", 18);
Log.d("IPC", "receive return content:" + result + " in client");
} catch (Exception e) {
}
}
@Override
public void onServiceDisconnected(ComponentName name) {
}
};
service 为IBinder引用,该引用从服务端经过Binder驱动传递而来(不一定是同一个引用)。
IMyServer.Stub.asInterface(service) 用来寻找该IBinder对应的服务端提供的接口。
1、当IBinder与调用者同一进程,则IBinder为Binder类型,即为自身定义的Stub。
2、当IBinder与调用者不是同一进程,则IBinder为BinderProxy类型(为什么是这个类型,后续文章会分析)。
此处测试的是两个不同的进程,因此IBinder service指向BinderProxy。
在ServiceConnection里,当绑定成功后调用Proxy里的方法,其内部通过BinderProxy调用transact(xx)。
上面的逻辑都写了,最后当然需要绑定Service:
private void bindService() {
Intent intent = new Intent(this, MyService.class);
bindService(intent, serviceConnection, BIND_AUTO_CREATE);
}
来看看打印结果:
可以看出通信成功了。
需要注意的是:
以上Demo都是同一个工程里编写的,因此客户端、服务端都能访问IMyServer.Stub,若是在不同的进程,需要写同样的IMyServer.aidl文件。
让Service在不同的进程运行只需要在AndroidManifest.xml添加如下字段:
绑定后,运行的两个进程如下:
总结AIDL用法
通过以上Demo可以看出,通过编写AIDL实现IPC。
服务端仅需要两步:
第一步
实现接口对应方法的业务逻辑
第二步
在onBind(xx)里将接口关联的Binder返回
同样的客户端调用服务端仅仅只需要两步:
第一步
通过Stub拿到服务端的接口
第二步
拿到接口后调用对应的方法
明显的,IMyServer.java 已经为我们实现了连接Binder的功能,屏蔽了对接的Binder细节。客户端调用服务端的方法(Proxy)与服务端进行通信,就像是"直接"调用一般,符合Java一贯的面向对象的思维。
结合上一篇文章对Binder应用的分析,以及本篇AIDL的分析,我们知道:
- AIDL 并不是我们熟知的Java、C++语言,而是一种规范。按此规范编写的AIDL文件最终生成对应的.java文件,该文件里实现了客户端调用transact(xx)以及调用服务端的onTransact(xx),通过.java文件就能实现进程间通信。
- .java文件里将工作分为了两部分:一是客户端的逻辑封装在Proxy里,而服务端的逻辑封装在Stub里,典型的Proxy-Stub(代理-桩)模式。
- 进程间通信的核心是Binder,AIDL本身并不能实现进程间通信,仅仅是简化了编码的流程。
接下来将重点分析AIDL 传递自定义数据类型以及定向Tag相关问题。
本文基于Android 10.0。
您若喜欢,请点赞、关注,您的鼓励是我前进的动力
持续更新中,和我一起步步为营学习Android
1、Android各种Context的前世今生
2、Android DecorView 必知必会
3、Window/WindowManager 不可不知之事
4、View Measure/Layout/Draw 真明白了
5、Android事件分发全套服务
6、Android invalidate/postInvalidate/requestLayout 彻底厘清
7、Android Window 如何确定大小/onMeasure()多次执行原因
8、Android事件驱动Handler-Message-Looper解析
9、Android 键盘一招搞定
10、Android 各种坐标彻底明了
11、Android Activity/Window/View 的background
12、Android Activity创建到View的显示过
13、Android IPC 系列
14、Android 存储系列
15、Java 并发系列不再疑惑
16、Java 线程池系列