Android AIDL

  1. 请介绍什么是AIDL⭐⭐⭐⭐⭐

  1. 有几种AIDL文件?⭐⭐⭐

  1. 一个程序AIDL文件的数量? ⭐⭐

  1. 你有没有使用过AIDL?谈谈你如何实现的? ⭐⭐⭐⭐

  1. 知道AIDL语法中的定向tag吗?⭐⭐⭐

  1. 你能不能简单描述AIDL实现的原理?⭐⭐⭐

目录

  • 1、什么是AIDL

  • 1.1 AIDL支持的基础数据

  • 1.2 两种AIDL文件

  • 1.2.1 定义parcalable对象

  • 1.2.2 定义接口

  • 1.3 定向Tag

  • 2、使用AIDL实现跨进程通讯

  • 2.1 AIDL文件的数量

  • 2.2 应用程序中使用AIDL

  • 2.2.1 parcalable对象对象定义和实现

  • 2.2.2 AIDL接口定义文件

  • 2.2.3 服务端

  • 2.2.4 客户端

  • 2.2.5 几个重要类和方法的解析

  • 2.2.6 AIDL实现原理

  • 2.2.6.1 客户端

  • 2.2.6.2 服务端

  • 3、系统服务中使用AIDL(引导)

1、什么是AIDL

AIDL全称Android Interface Definition Language,也就是Android接口定义语言。因为AIDL的文件内容都是只有定义,而没有真正的实现。设计了这门语言是为了方便实现跨进程通讯,AIDL文件的后缀是“.aidl“。

当然AIDL只是一种辅助工具,可以让我们少写代码同时少出错。如果不用AIDL,一样可以实现跨进程通讯,只是比较麻烦。AIDL 的原理是Binder,真正有跨进程通讯能力的也是 Binder。

1.1 AIDL支持的基础数据

AIDL作为跨进程的方式之一,数据传输功能并不可少,AIDL默认支持的数据类型有:

  • Java中的八种基本数据类型,包括 int、long、float、double、byte,short,boolean,char

  • String 类型

  • CharSequence类型

  • List类型:List中的所有元素必须是AIDL支持的类型之一,或者是一个其他AIDL生成的接口,或者是AIDL是定义的parcelable对象(见1.2小节),List可以使用泛型

  • Map类型:Map中的所有元素必须是AIDL支持的类型之一,或者是一个其他AIDL生成的接口,或者是AIDL是定义的parcelable对象(见1.2小节),Map是不支持泛型

1.2 两种AIDL文件

1.2.1 定义parcalable对象

如果传输的对象是自定义的类,难道AIDL无法支持?显然官方也考虑到该问题,AIDL第一种文件类型是用于定义parcelable对象(AIDL 只能传送继承 Parcelable 接口的类)。如需要传输一个Person类,需要在同个包下定义以下2个文件:

  • Step1:需要先定义Person.aidl文件

package com.android.aidl.demo;

// Declare any non-default types here with import statements
// parcelable使用小写字母
parcelable Person;
  • Step2:然后再编写Person的实现类:

public class Person implements Parcelable {
    public int id;
    public String name;

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

1.2.2 定义接口

AIDL另一种文件用于定义方法接口,以提供给其他进程调用本进程的能力。

// IMyService.aidl
package com.android.aidl.demo;

import com.android.aidl.demo.Person; // 1
// Declare any non-default types here with import statements

interface IMyService {
    void addPerson(in Person person); // 2

    List<Person> getPersonList();
}

其中,有一个知识点,Person实现类Person.java文件和定义接口的IMyService.aidl文件虽然在同个包里,但IMyService.aidl文件还是必须手动导入Person类,如[注释1]所示。

编写IMyService.aidl文件后,在Android Studio编译一下工程,就会在下图路径自动编程生成IMyService.java文件:

1.3 定向Tag

仔细看[注释2],会发现相比于普通函数参数,这里多了一个‘in’符号,这是AIDL的语法之一,称之为“定向Tag”,共包含3种定向Tag:in、out、inout。这三个tag是用于控制客户端和服务端数据流向,且数据流向的主体以客户端传入服务端的对象为标准。具体看看如下代码:

void addPerson(in Person person);
void addPerson(out Person person);
void addPerson(inout Person person);

//调用
Person person = new Person("xurui");
addPerson(person);

此时数据流向的主体就是Person对象,在此假设Person对象里面就一个字符变量“name”,三个tag具体作用如下:

  • in:数据只能从客户端流向服务端,服务端会收到客户端传入的一个完整的对象,即收到一个Person对象,对象内部的name属性的值为“xurui”,但此时服务端对该Person对象进行的修改不会同步返回到客户端;

  • out:数据只能由服务端流向客户端,服务端只能收到客户端传入的对象的空值对象,即服务端收到的Person对象里面的name属性是空值,而非“xurui”,但服务端对接收到的空对象的任何修改都会同步返回到客户端;

  • inout:数据在客户端和服务端双向流通;服务端可以收到客户端传入一个Person对象,里面的name属性数值为“xurui”,此时服务端对该对象做的任何修改也会同步返回给客户端。

2、使用AIDL实现跨进程通讯

在日常开发中,一般使用到AIDL的情况就是自定义了一个Service,想提供该Service的能力供其他进程或者其他模块调用。而自定义Service最常见的方法有两种:

  • 通过Android Studio创建一个包含Service的应用程序;

  • 创建自定义系统服务;

所以本节以创建自定义应用程序和自定义系统服务为例,讲解如果AIDL的使用方法。

2.1 AIDL文件的数量

在实现跨进程通讯时,如果AIDL文件仅包含AIDL所支持的基础数据类型,则只需要1个AIDL文件。但如果像1.2.1小节的图片所示,需要支持自定义的Person类,则需要1+1=2个AIDL文件,即有N个非默认支持的基础数据类型,就需要N+1个AIDL文件。

2.2 应用程序中使用AIDL

在应用程序中使用AIDL,常见的就是一个Activity绑定一个Service,并调用Service提供的函数方法。无论Activity和Service在同个应用程序,还是分别在两个应用程序,对AIDL的使用都是大同小异的。不过有一点要注意,如果是同在一个应用程序,那么只需要一份AIDL文件。如果Activity和Service在两个程序,则两个程序各要有一份AIDL文件。我们以同在一个应用程序进行讲解。主要包括四大部分:parcalable对象定义和实现、AIDL接口定义文件、服务端、客户端;

2.2.1 parcalable对象对象定义和实现

本案例程序框架图和parcalable对象定义和实现(Person类),都在1.2.1小节详细说过,这里不重复叙述。

2.2.2 AIDL接口定义文件

本案例AIDL接口定义文件也在1.2.2小节介绍了,即IMyService.aidl文件,这里也不重复。

2.2.3 服务端

直接上代码:

public class MyPersonServer extends Service {

    class MyServiceNative extends IMyService.Stub { //3

        List<Person> myPersonList = new ArrayList<>();

        @Override
        public void addPerson(Person person) {
            myPersonList.add(person);
        }

        @Override
        public List<Person> getPersonList() {
            return myPersonList;
        }

        @Override
        public boolean onTransact(int code, Parcel data, Parcel reply, int flags) throws RemoteException {
            return super.onTransact(code, data, reply, flags);
        }
    }

    private MyServiceNative mMyServiceNative = new MyServiceNative();

    @Override
    public IBinder onBind(Intent intent) {
        return mMyServiceNative; //4
    }
}

在MyPersonServer.java代码中,主要做了两件事。

  • 在[注释3]创建MyServiceNative内部类继承了Android Studio自动生成的IMyService.java文件里的Stub类,并重新实现了IMyService.aidl文件定义的两个接口addPerson()和getPersonList();

  • 在[注释4],通过onBind()返回MyServiceNative的实例。

2.2.4 客户端

客户端代码就在Activity里实现:

public class ClientActivity extends AppCompatActivity implements View.OnClickListener {
    Button btnBind;
    Button btnAddPerson;
    Button btnGetSize;
    TextView tvResult;
    IMyService myService;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        btnBind = (Button) findViewById(R.id.btn_bind);
        btnAddPerson = (Button) findViewById(R.id.btn_add_person);
        btnGetSize = (Button) findViewById(R.id.btn_get_size);

        btnBind.setOnClickListener(this);
        btnAddPerson.setOnClickListener(this);
        btnGetSize.setOnClickListener(this);

        tvResult = (TextView) findViewById(R.id.txt_result);
    }

    @Override
    protected void onDestroy() {
        unbindService(mConn);
        super.onDestroy();
    }
    private ServiceConnection mConn = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            myService = IMyService.Stub.asInterface(service); // 5
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {
            myService = null;
        }
    };

    @Override
    public void onClick(View v) {
        switch (v.getId()) {
            case R.id.btn_bind:
                bindService(); // 6
                break;
            case R.id.btn_add_person:
                if (null != myService) {
                    try {
                        myService.addPerson(new Person(100, "xurui")); // 7
                    } catch (RemoteException e) {
                        e.printStackTrace();
                    }
                } else {
                    Toast.makeText(ClientActivity.this, "先绑定 Service 才能调用方法", Toast.LENGTH_LONG).show();
                }
                break;
            case R.id.btn_get_size:
                if (null != myService) {
                    try {
                        List<Person> personList = myService.getPersonList(); // 8
                        tvResult.setText("getPersonList size:" + personList.size());
                    } catch (RemoteException e) {
                        e.printStackTrace();
                    }
                } else {
                    Toast.makeText(ClientActivity.this, "先绑定 Service 才能调用方法", Toast.LENGTH_LONG).show();
                }
                break;
            default:
        }
    }

    private void bindService() {
        Intent intent = new Intent();
        intent.setAction("com.android.aidl.demo");
        intent.setComponent(new ComponentName("com.android.aidl.demo", "com.android.aidl.demo.MyPersonServer"));

        bindService(intent, mConn, Context.BIND_AUTO_CREATE);
    }
}

上面的代码稍微长一点,但其实就只做了一件事:在[注释6]发起绑定服务端,并在[注释5]将服务端返回的binder变量得到IMyService实例,接着就可以在[注释7-8]使用服务端提供的方法。

介绍一下asInterface()方法,该方法在编译器自动生成的IMyService.java文件,作用是将服务端的 Binder 对象转换成客户端所需的 AIDL 接口类型对象。代码如下:

    public static com.android.aidl.demo.IMyService asInterface(android.os.IBinder obj)
    {
        if ((obj==null)) {
            return null;
        }
        android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
        if (((iin!=null)&&(iin instanceof com.android.aidl.demo.IMyService))) {
            return ((com.android.aidl.demo.IMyService)iin);
        }
        return new com.android.aidl.demo.IMyService.Stub.Proxy(obj);
    }

代码比较简单,先通过queryLocalInterface(DESCRIPTOR)查找到对应的IInterface对象,这是IBinder接口中定义的方法。然后判断对象的类型,如果是同一个进程调用则返回IMyService对象,如果是跨进程调用则返回Proxy对象,即Binder类的代理对象。

以上源码是通过学习:BinderDemo开源项目后修改的,读者可以直接下载试试。

2.2.5 几个重要类和方法的解析

仔细看看代码,有几个与Binder相关的类和方法需要重点介绍一下。建议看下面几个类解析时,在页面上搜索高亮对应的类名,看看这些类都在代码的哪个位置使用到:

  • Binder:代表Binder本地对象,提供Binder服务的本地对象的基类,实现了IBinder接口:

// Binder.java
public class Binder implements IBinder {

}

所有本地对象都要继承这个类,服务端代码会继承Binder类,如下面[注释10]。

// IMyService.java文件
public interface IMyService extends android.os.IInterface //9:IMyService继承IInterface
{
    public static abstract class Stub extends android.os.Binder implements com.android.aidl.demo.IMyService //10
    {

    }
}
  • IInterface:这也是个Base接口,用来表示Server提供了哪些能力,代码如下:

//IInterface.java
package android.os;

public interface IInterface {
    IBinder asBinder();
}

这个接口只有一个asBinder()方法,某个类继承IInterface接口并实现 asBinder()方法,说明这个类具有跨进程传输的能力或者持有能够跨进程传输的对象的引用。因此,声明AIDL性质的接口都需要继承该接口,如IMyService.aidl文件经过编译器编译后自动生成的IMyService.java文件里的IMyService类就继承了IInterface,如上面[注释9]所示:

  • Stub:这个类在编译IMyService.aidl文件后自动生成,对应上面[注释10],它继承自Binder,表示它是一个Binder本地对象;它是一个抽象类,实现了IInterface接口,表明它的子类需要实现服务端将要提供的具体能力(即aidl文件中声明的方法)

  • Proxy:它实现了IInterface接口,也是在自动生成的IMyService.java文件里定义的。Proxy是Binder通信过程的一部分;它实现了aidl中声明的方法,但最终还是交由其中的mRemote成员来处理,说明它是一个代理对象。

private static class Proxy implements com.android.aidl.demo.IMyService {
        private android.os.IBinder mRemote;

        Proxy(android.os.IBinder remote) {
            mRemote = remote;
        }

        @Override
        public android.os.IBinder asBinder() { //实现了IInterface接口
            return mRemote;
        }
        
        // 实现aidl中声明的方法
        @Override public void addPerson(com.android.aidl.demo.Person person) throws android.os.RemoteException
        {
            ...
        }
        
        // 实现aidl中声明的方法
        @Override public java.util.List<com.android.aidl.demo.Person> getPersonList() throws android.os.RemoteException
        {
            ...
        }
    }
  • IBinder:IBinder本质上是一个接口类,声明了跨进程通信需要实现的一系列抽象方法和属性,如下所示:

public interface IBinder {
    int TWEET_TRANSACTION = 1599362900;
    ...
    @RecentlyNullable
    String getInterfaceDescriptor() throws RemoteException;

    boolean pingBinder();
    ...
    }

实现了这个接口就说明可以进行跨进程通信,因此服务端和客户端都要实现此接口。看看上面服务端的代码(服务端代码指我们自定义的 MyPersonServer.java文件,而不是指编译器根据IMyService.aidl文件生成的IMyService.java文件):

// MyPersonServer.java
public class MyPersonServer extends Service {
    class MyServiceNative extends IMyService.Stub { // 声明MyServiceNative内部类,继承IMyService.Stub类
        ...
    }
   
    private MyServiceNative mMyServiceNative = new MyServiceNative(); //定义mMyServiceNative变量
    @Override
    public IBinder onBind(Intent intent) { // 11:返回mMyServiceNative变量
        return mMyServiceNative; 
    }
}

在[注释11]返回mMyServiceNative变量,从函数命名知道onBinder()方法返回的就是IBinder类型,也就是mMyServiceNative变量有继承过IBinder接口,代码才能这么写。其实mMyServiceNative变量是MyServiceNative内部类的实例,MyServiceNative内部类继承了IMyService.Stub,IMyService.Stub是编译器自动生成的服务端代码IMyService.java文件里一个类,该类又继承了android.os.Binder,android.os.Binder则实现了IBinder接口。因此,mMyServiceNative变量有继承过IBinder接口,整个逻辑大概如此。

在跨进程数据流经Binder驱动时,Binder驱动会根据IBinder数据类型,自动完成不同进程Binder本地对象和Binder代理对象的转换;

2.2.6 AIDL实现原理

2.2.6.1 客户端

客户端绑定服务端后,客户端运行

Person person = new Person(100, "xurui");
myService.addPerson(person); //调用服务端提供的方法

就可以调用服务端提供的addPerson()方法,那么我们以客户端ClientActivity.java执行myService.addPerson(person)如何调用到自定义服务MyPersonServer.java里面自定义的addPerson()方法为思路,理一遍AIDL调用的实现原理,以下内容和上面讲述过的内容强相关,当作重新复习一遍。

首先myService的定义是:myService = IMyService.Stub.asInterface(service);,因此跳到asInterface()方法,源码在2.2.4小节最后,如上所述最后会返回return new com.android.aidl.demo.IMyService.Stub.Proxy(obj);。关键看看Proxy类的实现:

//IMyService.java
    private static class Proxy implements com.android.aidl.demo.IMyService
    {
        private android.os.IBinder mRemote;
        Proxy(android.os.IBinder remote)
        {
            mRemote = remote; //12
        }
        @Override public android.os.IBinder asBinder()
        {
            return mRemote;
        }
        public java.lang.String getInterfaceDescriptor()
        {
            return DESCRIPTOR; //13
        }
        @Override public void addPerson(com.android.aidl.demo.Person person) throws android.os.RemoteException
        {
            android.os.Parcel _data = android.os.Parcel.obtain(); //14
            android.os.Parcel _reply = android.os.Parcel.obtain();//15
            try {
                _data.writeInterfaceToken(DESCRIPTOR);
                if ((person!=null)) {
                    _data.writeInt(1);
                    person.writeToParcel(_data, 0);
                }
                else {
                    _data.writeInt(0);
                }
                //调用 transact() 方法将方法id和两个 Parcel 容器传过去,并挂起当前线程
                mRemote.transact(Stub.TRANSACTION_addPerson, _data, _reply, 0); //16
                //从_reply中取出服务端执行方法的结果
                _reply.readException();
            }
            finally {
                _reply.recycle();
                _data.recycle();
            }
        }
        @Override public java.util.List<com.android.aidl.demo.Person> getPersonList() throws android.os.RemoteException
        {
            ...
        }
    }

介绍代码前需要认识以下几个概念:

  • remote:对应[注释12],这个remote就是myService = IMyService.Stub.asInterface(service);里面的服务端service变量;

  • DESCRIPTOR:对应[注释13],该变量所在的IMyService.java文件的唯一标识,其值为"com.android.aidl.demo.IMyService";

  • _data 与 _reply 对象:应[注释14-15],方法的传参的数据存入_data 中,而将方法的返回值的数据存入 _reply 中;

  • transact() 方法:客户端和服务端通讯的关键方法,客户端执行transact() 方法后,客户端会挂起当前线程,服务端的onTransact()方法会被调用;

其实客户端从myService.addPerson(person); 到运行Proxy类中[注释16]的mRemote.transact(Stub.TRANSACTION_addPerson, _data, _reply, 0); 就基本理通了,接下来看看服务端如何反应。不过啰嗦一下,这里的Proxy类就是服务端在客户端的本地代理,通过这个类可以调用服务端提供的方法;

2.2.6.2 服务端

服务端完整代码在2.2.3小节,其中onTransact()方法就只是执行了super.onTransact()

@Override
        public boolean onTransact(int code, Parcel data, Parcel reply, int flags) throws RemoteException {
            return super.onTransact(code, data, reply, flags);
        }

也就是调用父类的onTransact()。从服务端代码知道其父类就是IMyService.Stub。没错,又跑到了IMyService.java文件。看看里面的onTransact()方法:

@Override public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException {
        java.lang.String descriptor = DESCRIPTOR;
        switch (code) {
            case INTERFACE_TRANSACTION: {
                reply.writeString(descriptor);
                return true;
            }
            case TRANSACTION_addPerson: { //17
                data.enforceInterface(descriptor);
                com.android.aidl.demo.Person _arg0;
                if ((0 != data.readInt())) {
                    _arg0 = com.android.aidl.demo.Person.CREATOR.createFromParcel(data);
                } else {
                    _arg0 = null;
                }
                this.addPerson(_arg0); //18
                reply.writeNoException();
                return true;
            }
            ...
        }
    }

[注释16]的 TRANSACTION_addPerson对应[注释17],因此最终执行了[注释18]addPerson()方法,而该方法正是在我们自定义的服务端MyPersonServer.java里面重写了。如此,客户端ClientActivity.java执行myService.addPerson(person)就最终调用到自定义服务MyPersonServer.java里面自定义的addPerson()。

3、系统服务中使用AIDL(引导)

新增加系统服务,一般都会用到AIDL。比如手机原生系统中,手指在手机左右边缘向手机中心滑动的时候,会出现一个黑色的曲线图案,本人曾禁用该功能,并重新自定义实现了各种边缘手势处理。做法是:

  • 新增加一个系统服务,实现边缘图标绘制、触摸事件处理,通过AIDL将本系统服务的能力对外提供;

  • SystemApp中,有边缘手势捕捉,在此拦截流向原生系统的手势动作处理,调用自定义的系统服务,在自定义系统服务中处理当前手势动作;

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值