AIDL

AIDL

全称Android Interface Definition Language。

像其他IDLs一样,允许你定义编程接口,实现客户端和服务端之间的内部进程通信(Inter Process Communication, IPC)。

AIDL通信分为两部分:

  • 服务端,即提供AIDL服务的进程。

  • 客户端,即调用AIDL服务的进程。

定义AIDL服务

  1. 创建AIDL接口:创建.aidl文件,定义对外提供服务的方法。

  2. AIDL接口的编码实现:Android SDK工具基于AIDL接口文件,在编译时使用Java语言生成一个与.aidl文件同名的.java文件。

  3. 实现服务端:创建一个Service,重写onBind()返回AIDL接口的实现对象。

  4. 实现客户端:绑定远程服务,将服务端返回的IBinder对象转变成AIDL接口类型,接着调用AIDL接口中公开声明的方法。

AIDL接口的创建

创建一个后缀为aidl的文件,在这个文件中声明将暴露给客户端的接口方法。

// IRemoteService.aidl
package com.daking.aidl;

interface IRemoteService {
    String getName();
}
1
2
3
4
5
6

AIDL支持以下数据类型:

  • Java基本类型,即int、long、char等。

  • String和CharSequence。

  • List:其中的每个元素都必须是AIDL支持的。客户端实际接收的具体类始终是ArrayList,但生成的方法使用的是List接口。

  • Map:其中的每个元素都必须是AIDL支持的。客户端实际接收的具体类始终是HashMap,但生成的方法使用的是Map接口。

  • Parcelable:必须要显式import,即使它跟.aidl是同一个包下

  • AIDL接口:必须要显式import,即使它跟.aidl是同一个包下

AIDL中的方法可有零、一或多个参数,可有返回值或void。

AIDL中除了基本数据类型(默认为in,不能是其他方向),其他类型的参数必须标上方向:

  • in,表示输入型参数,由客户端赋值;

  • out,表示输出型参数,由服务端赋值;

  • inout,表示输入输出型参数,可由客户端或服务端赋值;

AIDL只expose方法,不会expose静态变量

AIDL接口的编码实现

在AIDL的开发中,一般将所有和AIDL相关的类和文件全部放在同一个包中。这是因为若客户端与服务端位于不同的App中,客户端需要拷贝这些文件,且路径也要一致。AIDL文件一般保存在项目**/src/<SourceSet>/aidl**目录下。

当编译APP时,SDK工具会将项目**/src/<SourceSet>/aidl目录下的每个AIDL文件,在项目/build/generated/source/aidl目录下生成对应的Java文件。两个文件的名称一样,只是后缀不同。如IRemoteService.aidl生成IRemoteService.java**。

服务端的实现

创建一个Service,并在onBind()中返回<AIDL接口>.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 String getName() throws RemoteException {
            return "I am com.daking.aidl.IRemoteService";
        }
    };
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

在AndroidManifest中配置<service>android: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>
1
2
3
4
5
6
7
8
9

客户端的实现

客户端的实现步骤为:

  1. 绑定服务端Service。

  2. 在绑定成功的回调中,将服务端返回的IBinder对象转换成AIDL接口类型。

  3. 调用这个AIDL接口的公开方法。

绑定AIDL服务:

Intent intent = new Intent();
// 提供AIDL服务的App包名
intent.setPackage("com.daking.aidl");
// AIDL服务的Action名
intent.setAction("com.daking.aidl.RemoteService");
bindService(intent, serviceConnection, Context.BIND_AUTO_CREATE);
1
2
3
4
5
6

在绑定成功的回调中,将服务端返回的IBinder对象转换成AIDL接口:

private IRemoteService mIRemoteService;

public void onServiceConnected(ComponentName className, IBinder service) {
    mIRemoteService = IRemoteService.Stub.asInterface(service);
}
1
2
3
4
5

通过这个AIDL接口去调用服务端的远程方法。注意,AIDL服务默认是运行在主线程中且是同步操作,若里面有耗时操作,应该在子线程中调用AIDL。

若客户端和服务端位于不同App,那么客户端必须要有涉及到的AIDL相关的类和文件的副本,一般保存目录为/src/<SourceSet>/aidl

传递复杂对象

在AIDL中传递的复杂对象必须要实现Parcelable接口,这是因为Parcelable允许Android系统将复杂对象分解成基本类型以便在进程间传输。

public class RequestVO implements Parcelable {

    private String name;
    private String type;
    
    // 忽略Parcelable实现
}
1
2
3
4
5
6
7

Parcelable的实现可手动编码,也可利用插件来自动编码,如Android Studio的Android Parcelable code generator插件

若客户端和服务端位于不同App,必须要该Parcelable实现类.java文件拷贝到客户端所在的APP,包路径要一致

另外,需要为这个Parcelable实现类定义一个相应的AIDL文件。客户端的对应目录下也要有这份副本。

// RequestVO.aidl
package com.daking.aidl;
	
parcelable RequestVO;
1
2
3
4

将复杂对象作为AIDL接口的形参时,记得加上in

// IRemoteService.aidl
import com.daking.aidl.RequestVO;
	
interface IRemoteService {
    void request(in RequestVO vo);
}
1
2
3
4
5
6

AIDL服务回调客户端

回调原理

服务端回调客户端,实际上是一个逆向的AIDL服务过程。

首先,客户端以AIDL接口方式定义一个监听器接口。接着,客户端调用服务端的注册方法,并传入监听器接口实现对象(AIDL的Stub对象,也是IBinder对象)。随后,服务端收到客户端的调用请求,将客户端传过来的IBinder对象转换成AIDL接口,这个AIDL接口类型变量就是监听器对象。(类似于之前的客户端绑定服务端的AIDL服务)

服务端调用这个AIDL接口类型变量的方法来回调信息给客户端。(类似于客户端调用服务端的AIDL接口提供的公开方法)

回调Listener的定义

自定义回调接口.aidl

// ICallback.aidl
package com.daking.aidl;
	
import com.daking.aidl.ResponseVO;
	
interface ICallback {
    void onResult(in ResponseVO vo);
}
1
2
3
4
5
6
7
8

注册和注销回调Listener

AIDL服务.aidl提供接口给客户端注册和注销回调。

// IRemoteService.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);
}
1
2
3
4
5
6
7
8
9
10
11

服务端的实现

AIDL服务.java的具体实现。

// RemoteService.java
public class RemoteService extends Service {
    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++) {
            ICallback cb = icallbacks.getBroadcastItem(i);
            try {
                cb.onResult(vo);
            } catch (RemoteException e) {
                e.printStackTrace();
            }
        }
        icallbacks.finishBroadcast();
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46

客户端的实现

客户端创建回调接口的实现对象,并注册到AIDL。

protected ICallback callback = new ICallback.Stub() {
    @Override
    public void onResult(ResponseVO vo) {
        // AIDL回调客户端后的业务处理
    }
};

// mService为AIDL接口
mService.registerCallback(callback);
1
2
3
4
5
6
7
8
9

RemoteCallbackList

RemoteCallbackList是系统专门提供的用于注册和注销跨进程Listener的接口。RemoteCallbackList是一个泛型,支持管理任意的AIDL接口(因为AIDL接口都是继承自IInterface)。

public class RemoteCallbackList<E extends IInterface>
1

RemoteCallbackList的内部有一个Map结构专门用来保存所有的AIDL回调。这个Map的键是IBinder类型,value是Callback类型。

ArrayMap<IBinder, Callback> mCallbacks = new ArrayMap<IBinder, Callback>();
1

其中Callback是RemoteCallbackList的内部类,用它来封装真正的远程listener。因为listener实际上是一个Binder对象,Callback实现了IBinder.DeathRecipient接口,能监听到listener的死亡,并自动将其移出列表。

RemoteCallbackList中注册和注销跨进程Listener的方法(这两个方法内部实现了线程同步):

  • boolean register(E callback)

  • boolean unregister(E callback)

RemoteCallbackList的遍历,必须要在beginBroadcast()finishBroadcast()中。

int len = icallbacks.beginBroadcast();
for (int i = 0; i < len; i++) {
    ICallback cb = icallbacks.getBroadcastItem(i);
    if (cb != null) {
        // 回调处理
    }
}
icallbacks.finishBroadcast();
1
2
3
4
5
6
7
8

Binder的死亡

Binder是很可能意外死亡的,这往往是由于服务端进程意外停止了,在实际开发中需要监听Binder死亡然后进行重连。

调用Binder#linkToDeath()设置IBinder.DeathRecipient监听。当Binder死亡时,DeathRecipient#binderDied()会被回调。

binder.linkToDeath(new IBinder.DeathRecipient() {
    public void binderDied() {
        // Binder挂掉了...
    }
});
1
2
3
4
5

另外,Service在crash或killed时,系统会回调onServiceDisconnected(),也意味着Binder已经死亡了。

上面两种方式都可以监听到Binder的死亡,区别在于:binderDied()是在客户端的Binder线程池中被回调,而onServiceDisconnected()是在客户端的UI线程中被回调。

AIDL权限验证

onBind()中进行验证

在服务端的Service#onBind()中进行验证,若验证不通过就直接返回null,这样验证失败的客户端就无法绑定服务,更无法调用AIDL接口的公开方法。

验证方式可以有很多种,比如使用自定义permission验证。

第一步,服务端的AndroidManifest中定义自定义权限。

<permission 
    android:name="自定义权限名"
    android:protectionLevel="normal" />
1
2
3

第二步,客户端的AndroidManifest中添加自定义权限。

<uses-permission
    android:name="自定义权限名" />
1
2

第三步,服务端检测调用方是否具有指定的权限,验证成功方可绑定服务。

public IBinder onBind(Intent intent) {
    int check = checkCallingOrSelfPermission("自定义权限名");
    if (check == PackageManager.PERMISSION_DENIED) {
        return null;
    }
    return mBinder;
}
1
2
3
4
5
6
7

onTransact()中进行验证

在服务端的AIDL.Stub对象的onTransact()中进行验证,如果验证失败就返回false,这样服务端就不会执行AIDL中的方法。

验证方式可以有很多种,比如像上面一样使用自定义permission验证,也可以验证Uid(通过getCallingUid()获取)和Pid(通过getCallingPid()获取)。

public boolean onTransact(int code, Parcel data, Parcel reply, int flags) throws RemoteException {
    int check = checkCallingOrSelfPermission("自定义权限名");
    if (check == PackageManager.PERMISSION_DENIED) {
        return false;
    }
    
    String packageName = null;
    String[] packageNames = getPackageManager().getPackageForUid(getCallingUid());
    if (packageNames != null && packageNames.length > 0) {
        packageName = packageNames[0];
    }
    if (!"指定包名".equals(packageName)) {
        return false;
    }
    
    // 执行AIDL中的方法
    return super.onTransact(code, data, reply, flags);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

其他验证方法

为Service指定android:permission属性,限制具有指定权限的客户端方可访问。

AIDL与线程

客户端调用服务端

客户端调用远程方法,被调用的方法运行在服务端的Binder线程池中,同时客户端线程会被挂起。如果服务端方法执行比较耗时,就会导致客户端线程长时间的阻塞,而如果这个客户端线程是UI线程,就会导致客户端发生ANR。

避免在客户端的UI线程中去调用远程方法。

客户端的onServiceConnected()onServiceDisconnected()都运行在UI线程中,所以不可以在它们里面直接调用远程方法。

因为服务端的方法被调用时是运行在Binder线程池中,本身就可以执行耗时操作,因此不需要另外再开线程去进行异步任务。

服务端回调客户端

服务端调用客户端的listener中的方法时,被调用的方法运行在客户端的Binder线程池中,同时服务端线程会被挂起,如果客户端方法执行比较耗时,就会导致服务端无法响应。

服务端应该另外开线程再调用客户端的listener中的方法。

客户端的listener中的方法被调用时并非运行在UI线程,因此不可直接操作UI。

AIDL升级

客户端上的AIDL文件应该保持与服务端上的一致。

在实际开发和生产的过程中,难免会出现这样一种情况:服务端升级了,其AIDL文件被修改了。但众多客户端仍未更新,其AIDL文件仍然是旧的。此时客户端通过旧AIDL调用新的服务端时,可能会出现异常情况。不过这种异常情况是可以通过技术手段来避免的。

AIDL升级必须遵循以下原则,方可避免旧客户端的AIDL调用出错。

  • 不可删除旧的公开函数,若实在要删除,只能通过屏蔽的方式,即保留函数,但里面是返回一个错误码或已停止使用等信息。

  • 新增的公开函数必须要位于旧函数的后面,不能改变原来接口的声明顺序。(这是因为AIDL是按照接口的声明顺序来定义每个对外函数的TRANSACTION_code,客户端和服务端交互的过程中是依靠这个code来找到对应的函数,而并非通过函数签名来匹配。)