AIDL学习之路(一)

1. 简介

AIDL全称为(Android Interface Definition Language),由此可以看出这应当是一种定义接口的语言

2. 具体场景

客户端与服务端进行通信时,需要定义双方都认可的接口AIDL这是用来完成这件工作的工具

AIDL主要应用于IPC,即进程间通信

2.1. 你是否真的需要AIDL

首先,如果并不需要实现IPC,可以直接通过继承Binder类实现client-sever间的通信

其次,如果的确需要实现IPC,但是无需处理多线程,那么应该通过Messager实现,因为Messager能够保证信息是串行的(其实内部也是通过AIDL实现的)

综上,真正需要使用到AIDL的场景是IPC并发处理多个请求并存

3. 使用步骤

  1. 创建.aidl后缀名的文件,定义需要用到的接口
  2. 实现定义的接口
  3. 将接口暴露给客户端使用

3.1. 创建.aidl文件,定义接口

使用AS新建一个空项目,在当前模块的src目录下直接创建一个AIDL文件

image-20220618164309730

然后重命名为IRemoteService,因为这是一个接口

image-20220618164447861

然后点击Finish完成创建

image-20220618174858440

现在项目结构中多了一个aidl目录,下面有一级和java包下包名一样的目录,里面正好就是刚刚创建的aidl文件

以下为默认的aidl模板,我们来定义自己的

1
2
3
4
5
6
7
8
9
10
11
12
13
// IRemoteService.aidl
package com.minos.aidldemo;

// Declare any non-default types here with import statements

interface IRemoteService {
/**
* Demonstrates some basic types that you can use as parameters
* and return values in AIDL.
*/
void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat,
double aDouble, String aString);
}

在此之前,需要先了解一下AIDL中支持的基础类型

3.1.1. 支持类型

  • 所有的基本类型(如int、float等)
  • String
  • CharSequence
  • List
  • Map

其中,对于ListMap这种复合类型的要求是其中的元素类型必须为①前面的支持类型 ,或者是②其他的AIDL接口类型,或者是③已经声明的Parcelable类型

List -> ArrayList(实例) [支持指定泛型]

Map -> HashMap(实例) [不支持指定泛型]

知道了AIDL可以支持的类型,接下来我们开始定义自己的接口

首先,我们需要定义一个要用到的数据类型,这是需要在进程进行传递的数据类型

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
public class Msg {

private String msg;
private int pid;

public Msg(String msg, int pid) {
this.msg = msg;
this.pid = pid;
}

public String getMsg() {
return msg;
}

public void setMsg(String msg) {
this.msg = msg;
}

public int getPid() {
return pid;
}

public void setPid(int pid) {
this.pid = pid;
}
}

这里直接定义了一个常见的Bean类

但是,如果需要这个数据对象能够在进程间进行传递,那么还需要这个类实现Parcelable接口

这是Android提供的一种序列化方式

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
47
48
49
50
51
52
53
54
55
56
57
58
import android.os.Parcel;
import android.os.Parcelable;

public class Msg implements Parcelable {

private String msg;
private int pid;

public Msg(String msg, int pid) {
this.msg = msg;
this.pid = pid;
}

public String getMsg() {
return msg;
}

public void setMsg(String msg) {
this.msg = msg;
}

public int getPid() {
return pid;
}

public void setPid(int pid) {
this.pid = pid;
}

@Override
public int describeContents() {
return 0;
}

@Override
public void writeToParcel(Parcel parcel, int i) {
parcel.writeString(this.msg);
parcel.writeInt(this.pid);
}

protected Msg(Parcel in) {
this.msg = in.readString();
this.pid = in.readInt();
}

public static final Parcelable.Creator<Msg> CREATOR = new Parcelable.Creator<Msg>() {
@Override
public Msg createFromParcel(Parcel source) {
return new Msg(source);
}

@Override
public Msg[] newArray(int size) {
return new Msg[size];
}
};

}

把数据对象的结构定义完成后,还需要为这个新定义的数据类型设置一个同名的AIDL文件

1
2
3
package com.minos.aidldemo;

parcelable Msg;

这个文件只声明了刚刚的类型,但是最终并不会参与编译

由于IRemoteService会用到这个类型,因此还需要import导入一下这个类型

尽管在同一个包下,也依然需要写import

1
2
3
4
5
6
7
8
9
10
11
12
13
// IRemoteService.aidl
package com.minos.aidldemo;

import com.minos.aidldemo.Msg; // 非默认都要导入
// Declare any non-default types here with import statements

interface IRemoteService {
/**
* Demonstrates some basic types that you can use as parameters
* and return values in AIDL.
*/
Msg getResponse(); // 定义方法,返回自定义类型
}

至此,完成AIDL文件的编写工作,开始make对应module

image-20220618234933788

image-20220618235036304

接下来,SDK会为我们生成对应的Java文件

image-20220618235316304

对应module下build的生成文件中可以找到

大概看一下结构

image-20220618235400683

这里系统生成了IRemoteService.java这个文件

可以看到,里面有一个Stub

image-20220619085855544

这是一个静态抽象类,并且继承了Binder类,并且实现了IRemoteService接口

image-20220619090614521

asInterface()方法接收一个Binder类型参数,并且将其转化为当前的接口类型返回,即IRemoteService

3.2. 实现定义的接口

1
2
3
4
5
6
7
8
val mBinder = object : IRemoteService.Stub() {
override fun getResponse(): Msg {
return Msg(
"msg from service at ${Thread.currentThread().name}\ntid is ${Thread.currentThread().id}\nmain thread id is ${mainLooper.thread.id}",
myPid()
)
}
}

实现需要用到的接口,即之前定义的,消息中会有线程的信息和进程id

3.3. 将接口暴露给客户端使用

接下来创建一个服务,叫RemoteService

image-20220619092515055

随后在onBind()中返回之前实现的接口

1
2
3
4
5
6
7
8
9
10
11
12
val mBinder = object : IRemoteService.Stub() {
override fun getResponse(): Msg {
return Msg(
"msg from service at ${Thread.currentThread().name}\ntid is ${Thread.currentThread().id}\nmain thread id is ${mainLooper.thread.id}",
Process.myPid()
)
}
}

override fun onBind(intent: Intent): IBinder {
return mBinder // 返回Binder
}

然后编写Activity模仿一下客户端

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
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">


<TextView
android:id="@+id/textView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="20dp"
android:layout_marginTop="100dp"
android:text="等待..."
android:textSize="24sp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />

<Button
android:id="@+id/button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="显示进程ID"
app:layout_constraintStart_toStartOf="@+id/textView"
app:layout_constraintTop_toBottomOf="@+id/textView" />

<Button
android:id="@+id/button2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:capitalize="sentences"
android:text="GetResponse"
android:textAllCaps="false"
app:layout_constraintStart_toStartOf="@+id/button"
app:layout_constraintTop_toBottomOf="@+id/button" />
</androidx.constraintlayout.widget.ConstraintLayout>

image-20220619093406935

然后,编写一下业务

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
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
package com.minos.aidldemo

import android.annotation.SuppressLint
import android.content.ComponentName
import android.content.Intent
import android.content.ServiceConnection
import android.os.Bundle
import android.os.IBinder
import android.os.Process.myPid
import android.util.Log
import android.widget.Button
import android.widget.TextView
import androidx.appcompat.app.AppCompatActivity


class MainActivity : AppCompatActivity() {

private var mRemoteService: IRemoteService? = null
private var mBind = false
private lateinit var mPidText: TextView
private lateinit var mBtnShowPid: Button
private lateinit var mBtnGetResponse: Button

private val mConnection: ServiceConnection = object : ServiceConnection {
override fun onServiceConnected(name: ComponentName, service: IBinder) {
// 返回接口的引用,后面通过该引用就可以进行操作了
mRemoteService = IRemoteService.Stub.asInterface(service)
mBind = true
}

override fun onServiceDisconnected(name: ComponentName) {
mRemoteService = null
mBind = false
}
}

@SuppressLint("SetTextI18n")
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
mPidText = findViewById(R.id.textView)
mPidText.text = "the client pid is ${myPid()}"
mBtnShowPid = findViewById(R.id.button)
mBtnShowPid.setOnClickListener {
if (mBind) {
Log.i("RemoteServiceMsg", "the service pid is ${mRemoteService?.response?.pid}")
}
}
mBtnGetResponse = findViewById(R.id.button2)
mBtnGetResponse.setOnClickListener {
if (mBind) {
Log.i("RemoteServiceMsg", "${mRemoteService?.response?.msg}")
}
}
}

override fun onStart() {
super.onStart()
val intent = Intent(this, RemoteService::class.java)
bindService(intent, mConnection, BIND_AUTO_CREATE)
}

override fun onStop() {
super.onStop()
unbindService(mConnection)
mBind = false
}


}

mRemoteService = IRemoteService.Stub.asInterface(service)这一句是关键所在,客户端的操作主要是利用该接口的引用执行方法的调用

如果是非跨进程的情况下,这里可以直接使用强制类型转换

接下来,再到清单文件中修改注册条件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<service
android:name=".RemoteService"
android:process=":remote" <!--指定运行到另一进程-->
android:enabled="true"
android:exported="true" />

<activity
android:name=".MainActivity"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />

<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>

现在Activity扮演客户端,而Service扮演另一进程的服务端

接下来运行项目

image-20220619100247704

这边是我们的两个进程,3010是Activity所在的进程,而3052是Service所在进程

image-20220619100418790

点击一下“显示进程ID”按钮,这是另一进程中Service返回的消息

image-20220619100504780

接着,再点击”GetResponse“

image-20220619100922653

如果非跨进程通信,一定为主线程,跨进程的时候处理的线程便无法预测了