深入浅出Android底层(五)-Android中的IPC-Binder通信机制-AIDL-DEMO

95 阅读5分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第10天,点击查看活动详情

一、前言

说了这么多关于binder通信的机制,啃了这么多源代码,我们来简单用一个aidl的demo来深入了解一下关于Android的进程间通信

二、AIDL

AIDL(Android Interface Define Language):

是Android的一种接口定义语言,类似于IDL(Interactive Data Language)

借助它,你可以定义接口,使得客户端和服务端之间实现进程间通信.对android来说,一个进程无法通过正常的方法来访问另一个进程的内存空间.可以说,它们需要把对象分解成操作系统可以理解的原子类型,然后使对象突破在不同进程之间进行传输.具体实现这种机制的代码写起来非常枯燥,所以android为你提供了AIDL语言来进行处理.

三、AIDL定义服务端接口

首先我们需要定义我们的服务端,声明服务端可以提供的具体方法.

as已经提供了AIDL的实现模块

image.png

出现的错误

1、忘记导包

com.example.testapt.bean: couldn't find import for class com.example.testapt.bean

aidl记得一定要手动导包

import com.example.testapt.bean.Person;

2、定义方法输入流类型

'com.example.testapt.bean.Person' can be an out type,
so you must declare it as in, out, or inout.

有些类型缺少in\out\inout标签就会报错,in 表示数据只能由客户端流向服务端; out 表示数据只能由服务端流向客户端;inout 表示数据可在服务端和客户端双向流通。

解决办法:在传递对象bean前加个in

// IMyAidlInterface.aidl
package com.example.testapt;

// Declare any non-default types here with import statements
import com.example.testapt.bean.Person;

interface IMyAidlInterface  {

    void addPeople(in Person testString);

    List<Person> getPeopleList();
}

3.1 服务端接口

// MyPersonService.aidl
package com.example.testapt;

// Declare any non-default types here with import statements
import com.example.testapt.bean.Person;
interface MyPersonService {
    /**
     * Demonstrates some basic types that you can use as parameters
     * and return values in AIDL.
     */

    void addPeople(in Person person);

    List<Person> getPeopleList();
}

这里我们声明了一个序列化的对象Person,需要注意的是,Java的基本类型都是支持直接传递的,而对于我们自定义的对象,需要手动将其序列化,并且在对应的aidl接口中进行声明

3.2 自定义序列化对象

package com.example.testapt.bean;

parcelable Person;//声明Person对象被序列化了,否则也不能被AIDL识别
public class Person implements Parcelable {
    private int id;
    private String name;

    protected Person(Parcel in) {
        id = in.readInt();
        name = in.readString();
    }


    public Person(int id, String name) {
        this.id = id;
        this.name = name;
    }

    public static final Creator<Person> CREATOR = new Creator<Person>() {
        @Override
        public Person createFromParcel(Parcel in) {
            return new Person(in);
        }

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

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

    @Override
    public void writeToParcel(Parcel dest, int flags) {
        dest.writeInt(id);
        dest.writeString(name);
    }

    @Override
    public String toString() {
        return "Person{" +
                "id=" + id +
                ", name='" + name + ''' +
                '}';
    }
}

3.4 build服务端

声明好相应的aidl文件后,build我们的as工程,可以在build的output目录下看到生成的MyPersonService.java文件

image.png

3.5 服务端声明服务

此时我们可以在服务端自定义一个远程服务,对自己声明的服务端接口进行相应的处理,这里我们就对addPeople以及getPeopleList进行相应的处理.

public class AIDLService extends Service {
    public static final String TAG = "AIDLService";
    private List<Person> personList = new ArrayList<>();

    private IBinder iBinder = new MyPersonService.Stub() {
        @Override
        public void addPeople(Person person) throws RemoteException {
            Log.i(TAG, person.toString());
            personList.add(person);

        }

        @Override
        public List<Person> getPeopleList() throws RemoteException {
            Log.i(TAG, "getPeopleList");
            return personList;
        }
    };

    @Override
    public void onCreate() {
        Log.i(TAG,"onCreate");
        super.onCreate();
    }

    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        Log.i(TAG,"onBind");
        return iBinder;
    }

    @Override
    public boolean onUnbind(Intent intent) {
        return super.onUnbind(intent);
    }
    
}

3.6 声明服务

最后,我们需要在manifest里注册我们提供的服务

<service
    android:name=".AIDLService"
    android:enabled="true"
    android:exported="true" />

四、AIDL客户端使用接口

4.1 复制服务端接口

首先,我们需要将服务端声明的aidl拷贝到客户端包路径下,并且是完全保留服务器完整包路径.

image.png

4.2 build客户端

此时build客户端,也可以在output文件夹下看到生成相应的java代码

image.png

4.3 客户端获取服务

我们在bindService获得的ServiceConnection连接对象中连接方法里对IBinder进行相应类型的强转就可以得到服务端的MyPersonService对象了

serviceConnection = new ServiceConnection() {
    @Override
    public void onServiceConnected(ComponentName name, IBinder service) {
        Log.i(TAG, "onServiceConnected");
        myPersonService = MyPersonService.Stub.asInterface(service);
        try {
            myPersonService.addPeople(new Person(1, "vivian"));
            myPersonService.addPeople(new Person(2, "Tom"));
            List<Person> peopleList = myPersonService.getPeopleList();
            for (int i = 0; i < peopleList.size(); i++) {
                Log.i(TAG, peopleList.get(i).toString());
            }
        } catch (RemoteException e) {
            e.printStackTrace();
        }

    }

    @Override
    public void onServiceDisconnected(ComponentName name) {
        Log.i(TAG, "onServiceDisconnected");
    }
};

4.4 客户端启动服务

我们用bindService启动服务,需要注意的是,setClassName时指定的服务得是完整路径

binding.fab.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        Intent service = new Intent();
        service.setClassName("com.example.testapt", "com.example.testapt.AIDLService");
        bindService(service, serviceConnection, Context.BIND_AUTO_CREATE);
    }
});

五、log日志

5.1 客户端日志

2022-10-08 16:12:26.611 14137-14137/com.example.testbinder I/AIDLService--客户端: onServiceConnected
2022-10-08 16:12:26.614 14137-14137/com.example.testbinder I/AIDLService--客户端: Person{id=1, name='vivian'}
2022-10-08 16:12:26.614 14137-14137/com.example.testbinder I/AIDLService--客户端: Person{id=2, name='Tom'}

5.2 服务端日志

2022-10-08 16:12:26.609 13927-13927/com.example.testapt I/AIDLService: onCreate
2022-10-08 16:12:26.610 13927-13927/com.example.testapt I/AIDLService: onBind
2022-10-08 16:12:26.612 13927-13946/com.example.testapt I/AIDLService: Person{id=1, name='vivian'}
2022-10-08 16:12:26.613 13927-13941/com.example.testapt I/AIDLService: Person{id=2, name='Tom'}
2022-10-08 16:12:26.614 13927-13941/com.example.testapt I/AIDLService: getPeopleList

六、AIDL总结

可以看到,AIDL已经隐藏了很多实现细节,我们只要模板化的实现相应的接口,就可以轻松地实现进程间的数据传输.

image.png

需要注意的是,我们在获取代理对象时,提供了三种返回

public static com.example.testapt.MyPersonService asInterface(android.os.IBinder obj)
{
  if ((obj==null)) {
    return null;
  }
  android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
  if (((iin!=null)&&(iin instanceof com.example.testapt.MyPersonService))) {
    return ((com.example.testapt.MyPersonService)iin);
  }
  return new com.example.testapt.MyPersonService.Stub.Proxy(obj);
}
  • 1、如果传入的IBinder是空,返回null,这个很好理解
  • 2、如果在同一进程下,那么asInterface()将返回服务端的Stub对象本身,因为此时根本不需要跨进程通信,直接调用Stub对象的接口就可以了,返回的实现就是服务端的Stub实现,没有跨进程通信
  • 3、如果不是同一进程,此时返回的是Stub.Proxy对象,该对象持有远程的Binder引用,此时是IPC调用,会通过调用transact方法与服务端通信

可以看到,Service的作用就是为我们创建了Binder驱动,并且添加了判断,如果是同一个进程调用,就返回本地的服务端对象,如果是IPC通信,就提供了一个代理类.