一、简介
AIDL
是 Android Interface Definition Language 的缩写,是 Android 定义的一种专门用于进程间通信的接口语言,它的语法也和 java
比较类似。
二、特点
我们知道 Android 中还有一种轻量级的进程通信方式——Messenger
。
Messenger
类似于一个进程间使用的 Handler
,它适用于消息的传递,服务器只能以串行的方式一个一个地处理客户端的消息。无法处理大量的并发请求,也不能直接地调用服务器的方法,若想解决这种问题,则需要使用 AIDL。
关于 Messenger 的介绍可以戳: https://blog.csdn.net/afei__/article/details/83386759
AIDL
可以让我们定义一系列的接口,供客户端和服务端共同调用,区别于 Java 的接口,它只支持定义方法,不支持静态常量。对象本质还是不能跨进程传输的,Binder
会把客户端和服务端传递的对象序列化重新转化生成一个新的对象进行传递。
三、支持的数据类型
- 基本数据类型
String
和CharSequence
List
和Map
Parcelable
AIDL
接口本身
四、定向 Tag
定向 Tag 有三种,即:
in
: 表示输入参数,数据只能从客户端流入服务端out
: 表示输出参数,数据只能从服务端流入客户端inout
: 表示输入输出参数,即数据可以在客户端和服务端双向流通
AIDL
中除基本数据类型和 String
外,其余类型 必须 标注定向 Tag。
建议不要随意使用 inout
,因为会加大系统的消耗和降低效率。
我们应该保证,对于输入参数就使用 in
,输出参数就使用 out
。
五、实战
1. 创建一个自定义的序列化对象(可选)
public class TestData implements Parcelable {
private String mString;
private int mInt;
public TestData(String string, int anInt) {
mString = string;
mInt = anInt;
}
// 省略 get/set/toString/Parcelabl 等实现
// 可使用插件直接生成 Parcelable 的实现
}
2. 创建自定义序列化对象对应的 AIDL 文件(可选)
如果上一步做了,那这一步就是必须做的了。
操作步骤:src -> 右键 -> new -> AIDL -> AIDL File
,且包名、文件名一定和要类名一致,例如:
// TestData.aidl
package com.afei.androidipc;
parcelable TestData;
3. 创建 AIDL 接口(重点)
操作步骤:src -> 右键 -> new -> AIDL -> AIDL File
,文件名取一个合适的就行。实例:
// MyAidlInterface.aidl
package com.afei.androidipc;
// 在这里引用非默认的数据类型
import com.afei.androidipc.TestData;
// import com.afei.androidipc.aidl.TestDataCallback;
interface MyAidlInterface {
// 1. 基本数据类型
void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat, double aDouble);
// 2. String类 + CharSequence接口
void stringType(String aString);
// ****** 以下类型参数必须指明方向 (in / out / inout)
// 3. 序列化的类
void parcelableType(in Bundle aBundle);
// 4. List 和 Map, 并且里面的元素也是可序列化的
void listType(in List list);
void mapType(in Map map);
// 5. 自定义的序列化类
void setTestData(in TestData data);
TestData getTestData();
// 6. AIDL接口本身
// void registerListener(TestDataCallback listener);
// void unregisterListener(TestDataCallback listener);
// void testCallback();
}
这里有一点要注意的就是,List
和 Map
的泛型可以不用指定,List
可以写基本数据类型或实现 Parcelable
接口的类,Map
写明泛型反而编译报错(目前我使用中是这样的)。
上述操作,编译器会帮你在 build/generated/source/aidl/{debug,release}/{packagename}/
目录下帮你生成一个相应的接口类。
4. 服务端
服务端就是创建一个 MyAidlInterface.Stub
对象并实现之前声明的接口:
public class AIDLService extends Service {
private static final String TAG = "AIDLService";
private final MyAidlInterface.Stub mStub = new MyAidlInterface.Stub() {
@Override
public void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat, double
aDouble) throws RemoteException {
Log.d(TAG, "basicTypes: anInt: " + anInt);
Log.d(TAG, "basicTypes: aLong: " + aLong);
Log.d(TAG, "basicTypes: aBoolean: " + aBoolean);
Log.d(TAG, "basicTypes: aFloat: " + aFloat);
Log.d(TAG, "basicTypes: aDouble: " + aDouble);
}
@Override
public void stringType(String aString) throws RemoteException {
Log.d(TAG, "stringType: " + aString);
}
@Override
public void parcelableType(Bundle aBundle) throws RemoteException {
int anInt = aBundle.getInt("int");
Log.d(TAG, "parcelableType: get int: " + anInt);
}
@Override
public void listType(List list) throws RemoteException {
Log.d(TAG, "listType: list size: " + list.size());
Log.d(TAG, "listType: " + list.toString());
}
@Override
public void mapType(Map map) throws RemoteException {
Log.d(TAG, "mapType: map size: " + map.size());
Log.d(TAG, "mapType: " + map.toString());
}
@Override
public void setTestData(TestData data) throws RemoteException {
Log.d(TAG, "setTestData: " + data.toString());
}
@Override
public TestData getTestData() throws RemoteException {
return new TestData("I'm from AIDLService", 666);
}
};
@Override
public IBinder onBind(Intent intent) {
return mStub; // 在这里返回 mStub 对象实现绑定
}
}
注意我们需要将这个服务运行在一个单独的进程,即需要在 AndroidManifest.xml
中给服务添加 process
声明:
<service android:name=".AIDLService" android:process=":aidl" />
5. 客户端
客户端就是绑定服务,并发送和接收数据:
public class MainActivity extends AppCompatActivity implements View.OnClickListener {
private static final String TAG = "MainActivity";
private MyAidlInterface mAidlInterface;
private ServiceConnection mAidlServiceConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
Log.d(TAG, "onServiceConnected: " + name);
mAidlInterface = MyAidlInterface.Stub.asInterface(service);
}
@Override
public void onServiceDisconnected(ComponentName name) {}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
findViewById(R.id.aidl_btn).setOnClickListener(this);
// 绑定服务
Intent intent = new Intent(MainActivity.this, AIDLService.class);
bindService(intent, mAidlServiceConnection, Context.BIND_AUTO_CREATE);
}
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.aidl_btn:
testAidl();
break;
}
}
private void testAidl() {
if (mAidlInterface == null) {
Log.e(TAG, "testAidl: service not connect!");
return;
}
try {
// 1. 基本数据类型
mAidlInterface.basicTypes(1, 2, true, 3.0f, 4.0);
// 2. String类 + CharSequence接口
mAidlInterface.stringType("test string");
// 3. 序列化的类
Bundle bundle = new Bundle();
bundle.putInt("int", 1);
mAidlInterface.parcelableType(bundle);
// 4. List 和 Map, 并且里面的元素也是可序列化的
List<Integer> list = new ArrayList<>();
for (int i = 0; i < 5; i++) {
list.add(i);
}
mAidlInterface.listType(list);
Map<Integer, String> map = new HashMap<>();
map.put(1, "value1");
map.put(2, "value2");
map.put(3, "value3");
mAidlInterface.mapType(map);
// 5. 自定义的序列化类
mAidlInterface.setTestData(new TestData("I'm from MainActivity", 666));
Log.d(TAG, mAidlInterface.getTestData().toString());
} catch (RemoteException e) {
e.printStackTrace();
}
}
@Override
protected void onDestroy() {
unbindService(mAidlServiceConnection); // 解除服务绑定
super.onDestroy();
}
}
6. 运行结果
服务端 log:
客户端 log:
可以看到,所有数据都有正常的传递,并且我们可以看到它们的进程 id 不同,说明它们有运行在不同的进程里。
六、扩展
1. 接口回调
有时候处理一些耗时的操作,客户端那边只需要在任务执行完后得知结果就行,这就是一个典型的接口回调的场景。但是在跨进程的场景中,我们要怎么使用接口回调呢?
这里需要借助到 RemoteCallbackList
类,这是 Android 提供的专门用于跨进程使用的接口回调类。
首先:创建接口回调的 AIDL 文件
// TestDataCallback.aidl
package com.afei.androidipc.aidl;
import com.afei.androidipc.aidl.TestData;
interface TestDataCallback {
void onCallback(in TestData data);
}
其次:在主要的 AIDL 接口中添加注册和注销的方法
// MyAidlInterface.aidl
package com.afei.androidipc.aidl;
// ...(省略部分代码)
interface MyAidlInterface {
// ...(省略部分代码)
// 6. AIDL接口本身
void registerListener(TestDataCallback listener);
void unregisterListener(TestDataCallback listener);
void testCallback();
}
其三:在服务中实现
public class AIDLService extends Service {
private static final String TAG = "AIDLService";
// 这是重点!!!
private RemoteCallbackList<TestDataCallback> mListenerList = new RemoteCallbackList<>();
private final Binder mBinder = new MyAidlInterface.Stub() {
// ...(省略部分代码)
@Override
public void registerListener(TestDataCallback listener) throws RemoteException {
mListenerList.register(listener);
}
@Override
public void unregisterListener(TestDataCallback listener) throws RemoteException {
mListenerList.unregister(listener);
}
@Override
public void testCallback() throws RemoteException {
new ServiceWorker().start(); // 启动一个线程
}
};
@Override
public IBinder onBind(Intent intent) {
return mBinder; // 在这里返回 mBinder 对象实现绑定
}
private class ServiceWorker extends Thread {
@Override
public void run() {
try {
Thread.sleep(3000); // 模拟耗时任务
} catch (InterruptedException e) {
e.printStackTrace();
}
int n = mListenerList.beginBroadcast(); // beginBroadcast和finishBroadcast必须配对使用
for (int i = 0; i < n; i++) {
TestDataCallback listener = mListenerList.getBroadcastItem(i);
if (listener != null) {
try {
// 回调数据
listener.onCallback(new TestData("I'm callback test data", 200));
} catch (RemoteException e) {
e.printStackTrace();
}
}
}
mListenerList.finishBroadcast(); // finishBroadcast和beginBroadcast必须配对使用
}
}
}
最后,在客户端调用 testCallback()
即可
2. 权限控制
通常,我们不希望随随便便一个进程就可以直接使用我们的服务,那么我们可以通过添加一些权限来控制这一行为。
主要有两种方法,一种是在 AndroidManifest.xml
中申请相应的权限才可访问服务,另一种是限制使用指定的包名。
首先,在 AndroidManifest.xml
中声明和申请我们定义的权限
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.afei.androidipc">
<!--声明所需的权限-->
<permission
android:name="com.afei.androidipc.permission.ACCESS_SERVICE"
android:protectionLevel="normal"/>
<!--由于AIDL服务端做了权限控制,所有需要申请该权限-->
<uses-permission android:name="com.afei.androidipc.permission.ACCESS_SERVICE"/>
<!--...(省略部分代码)-->
</manifest>
其次,在服务端中进行权限检查
我们可以选择在 Service 的 onBind
方法中或者 Binder 的 onTransact
方法中进行。我们这里选择使用 onTransact
。
private final Binder mBinder = new MyAidlInterface.Stub() {
// ...(省略部分代码)
// 权限认证
@Override
public boolean onTransact(int code, Parcel data, Parcel reply, int flags) throws RemoteException {
// 方式1:验证客户端是否申请拥有 "com.afei.androidipc.permission.ACCESS_SERVICE" 权限
int check = checkCallingOrSelfPermission(PERMISSION);
if (check == PackageManager.PERMISSION_DENIED) {
Log.e(TAG, "onTransact: permission denied: " + PERMISSION);
return false;
}
// 方式2:验证包名
String packageName = null;
String[] packages = getPackageManager().getPackagesForUid(getCallingUid());
if (packages != null && packages.length > 0) {
packageName = packages[0];
}
// 判断包名是否符合要求
if (packageName == null || !packageName.startsWith("com.afei")) {
Log.e(TAG, "onTransact: packageName error: " + packageName);
return false;
}
return super.onTransact(code, data, reply, flags);
}
};
这样一来,就完成了服务端的访问权限控制了。
七、完整代码地址
上面为了篇幅考虑,省略了不少重复或者繁琐的代码实现,可以以下工程中找到完整的代码实现以及测试场景: