Android Framework 之AIDL应用

2022-12-27  本文已影响0人  安安_660c

前言

我们可能都知道直接使用Binder编码是有一定缺点的,那么是否有一种方法能解决上面的问题呢?

没错就是AIDL。接下来的文章将着重分析AIDL原理及其使用

什么是AIDL

AIDL 是Android Interface Definition Language (Android 接口定义语言)的缩写。

创建AIDL 文件

Android Studio本身支持创建AIDL文件,先创建名为IMyServer 的AIDL文件。 在Module上右键单击:

image.png

再输入名字:

image.png

确定后生成 IMyServer.aidl文件


image.png

可以看出,由于是第一次创建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:

image.png

可以看出,编写的AIDL文件,最终根据一定的规则映射生成Java文件,接着来看看 IMyServer.java内容。


<pre spellcheck="false" class="md-fences md-end-block ty-contain-cm modeLoaded" lang="java" cid="n26" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: var(--monospace); font-size: 0.9em; display: block; break-inside: avoid; text-align: left; white-space: normal; background-image: inherit; background-position: inherit; background-size: inherit; background-repeat: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248); position: relative !important; border: 1px solid rgb(231, 234, 237); border-radius: 3px; padding: 8px 4px 6px; margin-bottom: 15px; margin-top: 15px; width: inherit; color: rgb(51, 51, 51); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">/*
* 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;
}</pre>

提取重点分析上面的代码。 定义接口 定义了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的联动。 继续引用上篇的图:


image.png

可以看出,有了AIDL自动生成的类后:

1、繁杂的switch case 不用自己编写了 2、序列化反序列化也不用编写了 3、不 再需要编写transact(xx)与onTransact(xx)了,极大解放了生产力。

如何使用AIDL

既然IMyServer.java 已经生成了,继续来看看如何使用它。

编写Server端业务

<pre spellcheck="false" class="md-fences md-end-block ty-contain-cm modeLoaded" lang="java" cid="n38" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: var(--monospace); font-size: 0.9em; display: block; break-inside: avoid; text-align: left; white-space: normal; background-image: inherit; background-position: inherit; background-size: inherit; background-repeat: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248); position: relative !important; border: 1px solid rgb(231, 234, 237); border-radius: 3px; padding: 8px 4px 6px; margin-bottom: 15px; margin-top: 15px; width: inherit; color: rgb(51, 51, 51); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">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;
}
}</pre>

首先实现业务接口。 其次在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:


<pre spellcheck="false" class="md-fences md-end-block ty-contain-cm modeLoaded" lang="java" cid="n48" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: var(--monospace); font-size: 0.9em; display: block; break-inside: avoid; text-align: left; white-space: normal; background-image: inherit; background-position: inherit; background-size: inherit; background-repeat: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248); position: relative !important; border: 1px solid rgb(231, 234, 237); border-radius: 3px; padding: 8px 4px 6px; margin-bottom: 15px; margin-top: 15px; width: inherit; color: rgb(51, 51, 51); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">private void bindService() {
Intent intent = new Intent(this, MyService.class);
bindService(intent, serviceConnection, BIND_AUTO_CREATE);
}</pre>

来看看打印结果:

image.png

可以看出通信成功了。

需要注意的是:

以上Demo都是同一个工程里编写的,因此客户端、服务端都能访问IMyServer.Stub ,若是在不同的进程,需要写同样的IMyServer.aidl文件。

让Service在不同的进程运行只需要在AndroidManifest.xml添加如下字段:


<service android:name=".MyService" android:process=":aidl">
</service>

绑定后,运行的两个进程如下:

image.png
上一篇 下一篇

猜你喜欢

热点阅读