Android进阶 ——— Android 深入理解AIDL进程间通信

前言

关于AIDL的资料,层出不穷,但是能让人简单明了理解的文章不多,那么我们就自己撸一遍,清晰明了的理解一下AIDL的原理。

准备

在理解AIDL原理之前,先写一个简单的使用AIDL进行进程间通信的例子,根据这个例子来由浅及深的理解AIDL。

先来定义实体类:UserBean.java
注意:如果要在AIDL中使用实体类,实体类必须要实现序列化接口,这里实现的是Android自带的Parcelable接口

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
package com.t9.news.Model;
import android.os.Parcel;
import android.os.Parcelable;
/**
* Created by dai
* Created time 2018/5/11
* function:com.t9.news.Model
*/
public class UserBean implements Parcelable{
private int age;
private String name;
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
protected UserBean(Parcel in) {
age = in.readInt();
name = in.readString();
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeInt(age);
dest.writeString(name);
}
@Override
public int describeContents() {
return 0;
}
public static final Creator<UserBean> CREATOR = new Creator<UserBean>() {
@Override
public UserBean createFromParcel(Parcel in) {
return new UserBean(in);
}
@Override
public UserBean[] newArray(int size) {
return new UserBean[size];
}
};
}

声明好了实体类,就需要额外在AIDL中声明实体类

1
2
3
4
// UserBean.aidl
package com.t9.news.Model;
parcelable UserBean;

在AIDL 中使用实体类

1
2
3
4
5
6
7
8
9
10
11
// IMyAidlInterface.aidl
package com.t9.news;
// Declare any non-default types here with import statements
import com.t9.news.Model.UserBean;
interface IUser {
List<UserBean> getUser();
void addUser(in UserBean user);
}

看一下项目的结构

然后Build 一下 Project,结束之后就生成了Binder代码,来看看Binder代码生成的位置

这时候可以点开看一下这个类,十有八九会被吓到,先不去管它,稍后再来分析。

来声明一个Service:

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
package com.t9.news;
import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
import android.os.RemoteException;
import android.support.annotation.Nullable;
import android.util.Log;
import com.t9.news.Model.UserBean;
import java.util.ArrayList;
import java.util.List;
/**
* Created by dai
* Created time 2018/5/11
* function:com.t9.news
*/
public class MyService extends Service {
private String TAG = this.getClass().getName();
@Override
public void onCreate() {
super.onCreate();
}
List<UserBean> list = new ArrayList<>();
private IBinder binder = new IUser.Stub() {
@Override
public List<UserBean> getUser() throws RemoteException {
if (list.size() <= 0){
for(int i = 0; i < 5; i++){
UserBean bean = new UserBean();
bean.setAge(5 * i);
bean.setName("android-" + i);
list.add(bean);
}
}
return list;
}
@Override
public void addUser(UserBean user) throws RemoteException {
Log.e(TAG, user.getName());
list.add(user);
}
};
@Nullable
@Override
public IBinder onBind(Intent intent) {
return binder;
}
}

定义的Service比较简单。new 了一个 IUser.Stub()并把它向上转型成了IBinder,最后在onBind方法中返回回去。在 IUser.Stub()的内部我们重写getUser()、addUser(UserBean user)方法,这就是AIDL中声明的IUser接口中的两个方法。
既然是跨进程通信,那么将Service设置到另一个进程中:

1
2
3
4
<service
android:name="com.t9.news.MyService"
android:process=":newProcess"
/>

定义为启动在新进程中,只需要在AndroidMainfest.xml中声明是加上一个process属性即可,不过这里有两个地方值得注意:
1.组件默认的进程名就是包名;
2.定义新的进程名的时候需要以包的形式(eg: com.xu.aidl)。

好,准备工作完成,在Activity中绑定Service

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
package com.t9.News.HomePage.View.Activity
import android.Manifest
import android.content.ComponentName
import android.content.Context
import android.content.Intent
import android.content.ServiceConnection
import android.os.IBinder
import android.os.RemoteException
import android.util.Log
import com.t9.news.Application
import com.t9.news.R
import com.t9.news.IUser
import com.t9.news.Model.UserBean
import com.t9.news.MyService
/**
* Created by dai
* Created time 19:06
* function:com.t9.news.Main.View.Activity
*/
class MainActivity : BaseActivity() {
private val TAG = this.javaClass.simpleName;
override fun getLayoutId(): Int {
return R.layout.activity_main
}
override fun initView() {
initService()
}
fun initService(){
val service = Intent(this@MainActivity, MyService::class.java)
bindService(service, serviceConnection, Context.BIND_AUTO_CREATE)
}
private var iUser : IUser? = null
private val serviceConnection = object : ServiceConnection {
override fun onServiceConnected(name: ComponentName, service: IBinder) {
iUser = IUser.Stub.asInterface(service)
Log.e(TAG,"连接Service成功")
try {
val list : List<UserBean> = iUser!!.user
for (user in list){
Log.e(TAG, "name = " + user.name + " age: = " + user.age)
}
val user:UserBean = UserBean();
user.name = "张三"
user.age = 111
iUser!!.addUser(user)
} catch (e: RemoteException) {
e.printStackTrace()
}
}
override fun onServiceDisconnected(name: ComponentName) {
Log.e(TAG,"连接Service断开")
}
}
}

由于主要是Service和Activity间的通信,所以为了让代码整洁就没有写UI了。

在onCreate(Bundle savedInstanceState)中,我们调用了自己定义的一个方法initService(),这个方法里面我们生成了一个Intent,然后 bindService了这个Intent传入了三个参数分别是Intent、ServiceConnection、Flag。

Intent我们就不用说了,我们看看后面两个参数:
在Activity中,我们new了一个ServiceConnection并实现了他的两个方法onServiceConnected、onServiceDisconnected。在onServiceConnected中我们通过IUser.Stub.asInterface(service)把传来的IBinder转换成了我们定义的iUser。然后我们调用了getUser方法,传递了个字符串和获取从MyService传来的字符串,并且打印了Log。
然后又new 一个UserBean对象,传递给MyService。

然后,我们的编码就完成了,运行并观察Log:

  • MainActivity 中 Log:

  • MyService中Log:

根据运行结果,在这两个不同的进程中都得到了我们想要的结果,所以,一个用aidl实现的跨进程通信就这样完成了。

AIDL的理解

回过头来,我们再来分析之前的 debug 目录下的 IUser.java 类

  • 先来看MyService 中 Binder

还记得我们在MyService中利用new IUser.Stub()向上转型成了IBinder然后在onBind方法中返回的。那我们就看看IUser.Stub()吧:

1
2
3
public static abstract class Stub extends android.os.Binder implements com.t9.news.IUser{
......
}

Stub 是 IUser 中静态抽象类,继承了 Binder,并且 实现 IUser接口,这就说明我们定义IUser.Stub的时候为什么需要实现IUser中的方法了,也说明了为什么我们可以把IUser.Stub向上转型成IBinder了。

  • Activity中的IMyInterface
    在Activity中,通过ServiceConnection连接MyService并成功回调onServiceConnected中我们把传回来的IBinder通过IUser.Stub.asInterface(service)转换成为IUser,那就来看看这里是如何转换的吧:
    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
    //这个接口里 有一个静态的抽象类Stub(注意这个名字是固定的 永远都是Stub 不会是其他)
    //并且这个Stub是Binder的子类,并且实现了IUser 这个接口
    public static abstract class Stub extends android.os.Binder implements com.t9.news.IUser{
    //这个东西就是唯一的binder标示 可以看到就是IUser的全路径名
    private static final java.lang.String DESCRIPTOR = "com.t9.news.IUser";
    /** Construct the stub at attach it to the interface. */
    /**
    * 这个就是Stub的构造方法,回顾一下 我们如果写好aidl文件以后 写的service里面 是怎么写的?
    * private final IUser.Stub mBinder = new IUser.Stub() {}
    * 我们都是这么写的 对吧~~所以想想我们的service里面的代码 就能辅助理解 这里的代码了
    */
    public Stub()
    {
    this.attachInterface(this, DESCRIPTOR);
    }
    //这个方法 其实就做了一件事,如果是同一个进程,那么就返回Stub对象本身
    //如果不是同一个进程,就返回Stub.Proxy这个代理对象了
    public static com.t9.news.IUser asInterface(android.os.IBinder obj){
    if ((obj==null)) {
    return null;
    }
    //检查Binder是不是在当前进程
    android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
    //如果是同1个进程,也就是说进程内通信的话 我们就返回括号内里的对象
    if (((iin!=null)&&(iin instanceof com.t9.news.IUser))) {
    return ((com.t9.news.IUser)iin);
    }
    //如果不是同一进程,是2个进程之间相互通信,那我们就得返回这个Stub.Proxy 看上去叫Stub 代理的对象了
    return new com.t9.news.IUser.Stub.Proxy(obj);
    }
    }

首先,我们因该明白的是,传回来的IBinder就是我们在Service的onBind( )方法所return的IBinder,然后我们调用Stub中的静态方法asInterface并把返回来的IBinder当参数传进去。
在asInterface方法中,首先判断了传进来的IBinder是不是null,如果为null就返回一个null;接着就判断传进来的IBinder是不是就在当前进程里面,如果是的话就直接返回IUser,不是的话就返回IUser.Stub.Proxy(obj)。
这里我觉得需要明白的是:直接返回的IUser是实现了定义的接口方法getUser、AddUser的。因为在IUser.Stub中所实现的。当然如果是在同一进程中,那么我们调用IUser的方法时就是在本地调用方法,直接调用就可以了。

如果没在同一进程,就会返回IUser.Stub.Proxy(obj):

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
//注意这里的Proxy 这个类名也是不变的,从前文我们知道 只有在多进程通信的情况下 才会返回这个代理的对象
private static class Proxy implements com.t9.news.IUser{
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;
}
//这里我们一共有2个方法 一个getUser 一个addUser 我们就分析一个方法就可以了
//并且要知道 这2个方法运行在客户端!!!!!!!!!!!!!!!!
//首先就是创建了3个对象_data 输入对象,_reply输出对象,_result返回值对象
//然后把参数信息 写入到_data里,接着就调用了transact这个方法 来发送rpc请求,然后接着
//当前线程挂起, 服务端的onTransace方法才被调用,调用结束以后 当前线程继续执行,直到
//从_reply中取出rpc的返回结果 然后返回_reply的数据
//所以这里我们就要注意了,客户端发起调用远程请求时,当前客户端的线程就会被挂起了,
//所以如果一个远程方法 很耗时,我们客户端就一定不能在ui main线程里在发起这个rpc请求,不然就anr了。
@Override public java.util.List<com.t9.news.Model.UserBean> getUser() throws android.os.RemoteException{
android.os.Parcel _data = android.os.Parcel.obtain();
android.os.Parcel _reply = android.os.Parcel.obtain();
java.util.List<com.t9.news.Model.UserBean> _result;
try {
_data.writeInterfaceToken(DESCRIPTOR);
//传送数据到服务端
mRemote.transact(Stub.TRANSACTION_getUser, _data, _reply, 0);
_reply.readException();
//接受从服务端传回的数据
_result = _reply.createTypedArrayList(com.t9.news.Model.UserBean.CREATOR);
}
finally {
_reply.recycle();
_data.recycle();
}
return _result;
}
@Override public void addUser(com.t9.news.Model.UserBean user) throws android.os.RemoteException{
android.os.Parcel _data = android.os.Parcel.obtain();
android.os.Parcel _reply = android.os.Parcel.obtain();
try {
_data.writeInterfaceToken(DESCRIPTOR);
if ((user!=null)) {
_data.writeInt(1);
user.writeToParcel(_data, 0);
}
else {
_data.writeInt(0);
}
mRemote.transact(Stub.TRANSACTION_addUser, _data, _reply, 0);
_reply.readException();
}
finally {
_reply.recycle();
_data.recycle();
}
}
}

在Proxy中,我们首先把Service连接成功返回的IBinder它的内部变量mRemote,这里在提一下,这里得IBinder还是是MyService中onBind所返回的。然后,当我们调用IMyInterface的方法的时候,其实就是调用的Proxy的方法了,这也是为什么这个类叫做Porxy的原因了。

当调用IUser.getUser() ,我们就看Proxy中的getInfor,先获取了两个Parcel对象 _data、_data,从变量名就可以看出,一个是传送数据的,另一个则是接受返回数据的。接着,向_data中写入了DESCRIPTOR(也就是这个类的全名),再写入了方法参数。然后就到了最重要的一步了,

mRemote.transact(Stub.TRANSACTION_getInfor, _data, _reply, 0);
这里我们调用了IBinder的transact方法,来把数据传给远端的服务器。然后在我们远程的MyService中,里面的Stub中就会回调onTransact()(因为你把数据传个远程的服务,远端的服务收到数据也就回调了)

注意:这里是在远程的服务里调用的。

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
//只有在多进程通信的时候 才会调用这个方法 ,同一个进程是不会调用的。
//首先 我们要明白 这个方法 一般情况下 都是返回true的,也只有返回true的时候才有意义,如果返回false了 就代表这个方法执行失败,
//所以我们通常是用这个方法来做权限认证的,其实也很好理解,既然是多进程通信,那么我们服务端的进程当然不希望谁都能过来调用
//所以权限认证是必须的,关于权限认证的代码 以后我再讲 先略过。
//除此之外 ,onTransact 这个方法 就是运行在Binder线程池中的,一般就是客户端发起请求,然后android底层代码把这个客户端发起的
//请求 封装成3个参数 来调用这个onTransact方法,第一个参数code 就代表客户端想要调用服务端 方法的 标志位。
//其实也很好理解 服务端可能有n个方法 每个方法 都有一个对应的int值来代表,这个code就是这个int值,用来标示客户端想调用的服务端的方法
//data就是方法参数,reply就是方法返回值。都很好理解
//其实隐藏了很重要的一点,这个方法既然是运行在binder线程池中的,所以在这个方法里面调用的服务器方法也是运行在Binder线程池中的,
//所以我们要记得 如果你的服务端程序 有可能和多个客户端相联的话,你方法里使用的那些参数 必须要是支持异步的,否则的话
//值就会错乱了!这点一定要记住!结论就是Binder方法 一定要是同步方法!!!!!!
@Override public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException{
switch (code){
case INTERFACE_TRANSACTION:{
reply.writeString(DESCRIPTOR);
return true;
}
case TRANSACTION_getUser:{
data.enforceInterface(DESCRIPTOR);
// 远程服务调用自己本地实现的方法获取返回值
java.util.List<com.t9.news.Model.UserBean> _result = this.getUser();
reply.writeNoException();
//写入返回值
reply.writeTypedList(_result);
return true;
}
case TRANSACTION_addUser:{
data.enforceInterface(DESCRIPTOR);
com.t9.news.Model.UserBean _arg0;
if ((0!=data.readInt())) {
_arg0 = com.t9.news.Model.UserBean.CREATOR.createFromParcel(data);
}
else {
_arg0 = null;
}
this.addUser(_arg0);
reply.writeNoException();
return true;
}
}
return super.onTransact(code, data, reply, flags);
}

nTransact方法是在Stub的内部实现的。

先看一下它的四个参数:

  • code:每个方法都有一个int类型的数字用来区分(后面中的swicth),在我们例子中也就是我们Proxy中的Stub.TRANSACTION_geIUser。
  • data:传过来的数据,其中包含我们的参数,以及类的描述。
  • reply:传回的数据,我们要写入是否发生了Exception,以及返回值
  • flags:该方法是否有返回值 ,0表示有返回值。

调用onTransact就表示有数据传来,首先就会通过swicth判断是哪个方法,然后取出方法参数,调用本地实现的方法获取返回值,写入返回值到reply。最后,返回true,才会把数据发送出去,发挥false就不会把结果返回给Activity了。这里也就是说,只有返回true,我们Proxy中才能接受从远端传回的数据。

注意:Service也是把数据发送出来,让客户端接受的。

Service发出了数据,客户端接收到了,就会一层一层返回去。所以,当我们简单的调用IUser的getUser时候,先是Proxy的transact发送出数据,然后服务端的onTransact接受并处理传来的数据,再把处理得到的数据写入返回值并发送给客户端,客户端读取值后就成为调用方法的返回值返回了。

到这里 相信大家 至少在应用层上面,就对Binder就一个很直观的理解了,对于进程间通信来说,具体的流程就分为如下几步:

  • 1.Client 发起远程调用请求 也就是RPC 到Binder。同时将自己挂起,挂起的原因是要等待RPC调用结束以后返回的结果

  • 2.Binder 收到RPC请求以后 把参数收集一下,调用transact方法,把RPC请求转发给service端。

  • 3.service端 收到rpc请求以后 就去线程池里 找一个空闲的线程去走service端的 onTransact方法 ,实际上也就是真正在运行service端的 方法了,等方法运行结束 就把结果 写回到binder中。

  • 4.Binder 收到返回数据以后 就唤醒原来的Client 线程,返回结果。至此,一次进程间通信 的过程就结束了

关于AIDL的基本原理就是这样了,看明白了AIDL,才发现原来AIDL不过就是帮我们生成了那些数据写入,传送,读取的方法而已。


参考
https://www.cnblogs.com/punkisnotdead/p/5163464.html
https://blog.csdn.net/u011974987/article/details/51243539

文章目录
  1. 1. 前言
  2. 2. 准备
  3. 3. AIDL的理解
|