AIDL开发记录

AIDL是指Android Interface definition language(安卓接口定义语言)。通过编写简单的AIDL文件,编译后可自动生java模版接口文件,来进行进程间的通信。

创建aidl文件

首先需要在和java同级的目录中建一个aidl文件夹,然后创建包和aidl文件。

image.png

新建的aidl文件默认会创建一个方法,告诉我们支持的基础类型有哪些

interface IMyAidlInterface {
    /**
     * 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);
}
复制代码

除了int、long、boolean 、float 、double 、String 等类型之外,还可以使用继承Parcelable的复杂数据类型,及它们的集合(List),下面详细说到。

aidl的自定义一个接口

使用基础类型,创建一个方法getName

interface IMyAidlInterface {
   
   String getName(int age);
   
   void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat,
           double aDouble, String aString);
}
复制代码

因为aidl是用于进程间通信的,所以需要把这个aidl拷贝到另一个进程中(可以新建工程),需要保证两个进程中aidl文件的包名一致,不然会报错。

build项目后,会发现在build目录下自动生成了相应的java接口

image.png

aidl的简单使用

  • 服务端进程 创建Service和返回Binder
public class MyService extends Service {

    @Override
    public IBinder onBind(Intent intent) {
        return new MyBinder();
    }

    class MyBinder extends IMyAidlInterface.Stub {

        @Override
        public String getName(int age) throws RemoteException {
            return age > 30 ? "老王" : "小王";
        }

        @Override
        public void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat, double aDouble, String aString)
                throws RemoteException {

        }
    }

}
复制代码

在AndroidManifest.xml中注册Service

<service android:name=".MyService">
    <intent-filter>
        <actaion android:name="com.xfz.aidlapplication.MyService123" />
    </intent-filter>
</service>
复制代码

可以通过设置android:process让service单独一个进程,
可以通过设置android:permission设置权限,让有权限的进程才能访问

  • 客户端创建ServiceConnection并绑定服务

首先创建intent:

可以使用Component,传入包名和服务类名

Intent intent = new Intent();
intent.setComponent(new ComponentName("com.xfz.aidlapplication", "com.xfz.aidlapplication.MyService"));
复制代码

也可以使用Action和Package

Intent intent = new Intent();
intent.setAction("com.xfz.aidlapplication.MyService123");
intent.setPackage("com.xfz.aidlapplication");
复制代码

绑定和调用

public class MainActivity extends AppCompatActivity {

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

        Intent intent = new Intent();
        intent.setComponent(new ComponentName("com.xfz.aidlapplication", "com.xfz.aidlapplication.MyService"));
        bindService(intent, mConnection, Service.BIND_AUTO_CREATE);
    }

    private ServiceConnection mConnection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            //在服务连接成功后进行调用
            IMyAidlInterface mIntentionPlus = IMyAidlInterface.Stub.asInterface(service);
            try {
                Log.d("xfz", "name = " + mIntentionPlus.getName(35));
            } catch (RemoteException e) {
                e.printStackTrace();
            }
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {

        }
    };

}
复制代码

创建继承Parcelable的复杂数据类型

继承Parcelable的数据对象可以使用插件生成,首先需要定义一个简单的数据模型

public class People {
    String mName;
    int mAge;
}
复制代码

安装android-parcelable-intellij-plugin插件,可以一步自动生成parcelable对象。

注:最新的android studio中遇到问题无法安装该插件,后来在issue中找到支持的插件

安装好插件后,光标选在类中,右键选择Generate → parcelable

插件自动创建.png

最终生成一个parcelable的对象

public class People implements Parcelable {
    public String mName;
    public int mAge;

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

    @Override
    public void writeToParcel(Parcel dest, int flags) {
        dest.writeString(this.mName);
        dest.writeInt(this.mAge);
    }

    public People() {
    }

    protected People(Parcel in) {
        this.mName = in.readString();
        this.mAge = in.readInt();
    }

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

        @Override
        public People[] newArray(int size) {
            return new People[size];
        }
    };
}
复制代码

使用继承Parcelable的复杂数据类型

  1. 首先需要在和该People的java文件夹同路径下,在aidl中创建一个People.aidl,内容如下:
package com.xfz.aidlapplication.model;

parcelable People;
复制代码

项目结构.png

这样就可以在aidl中引用该People类型了。

  1. 修改aidl文件,写一个方法来使用People类型,需要注意的是aidl中不支持重载。

新写的方法为getAge,具体如下:

  • 首先需要手动import进来People
  • 使用People时,前面添加in的标记(in的标记下面会介绍)
package com.xfz.aidlapplication;

import com.xfz.aidlapplication.model.People;

interface IMyAidlInterface {

    String getName(int age);

    int getAge(in People people);

    void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat,
            double aDouble, String aString);
}
复制代码

修改MyBinder

    class MyBinder extends IMyAidlInterface.Stub {

        @Override
        public String getName(int age) throws RemoteException {
            return age > 30 ? "老王" : "小王";
        }

        @Override
        public int getAge(People people) throws RemoteException {
            return people.mAge;
        }

        @Override
        public void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat, double aDouble, String aString)
                throws RemoteException {
        }
    }
复制代码

同样将People.java和相应aidl同步到客户端进程中,这样,在客户端进程就可以使用

    private ServiceConnection mConnection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            //在服务连接成功后进行调用
            IMyAidlInterface iMyAidlInterface = IMyAidlInterface.Stub.asInterface(service);
            try {
                People people = new People();
                people.mAge = 35;
                people.mName = "小王";
                Log.e("xfz", "age = " + iMyAidlInterface.getAge(people) + " name = " + people.mName);
            } catch (RemoteException e) {
                e.printStackTrace();
            }
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {

        }
    };
复制代码

AIDL定向tag:in、out、inout

基本类型(int、long、boolean 、float 、double 、String )参数的定向tag是in,并且只能是in。
而复杂的数据类型参数都需要一个定向tag,用于确定数据流向 。

数据流向:

  • in 表示数据只能由客户端流向服务端
  • out 表示数据只能由服务端流向客户端
  • inout 表示数据可在服务端与客户端之间双向流通

再具体点说:数据流向是针对在客户端中的那个传入方法的对象而言的。

  • in 为定向tag的情况下,服务端将会接收到一个那个对象的完整数据,但是客户端的那个对象不会因为服务端对传参的修改而发生变动;
  • out 为定向tag的情况下,服务端将会接收到那个对象的空对象,但是在服务端对接收到的空对象有任何修改之后客户端将会同步变动;
  • inout 为定向tag的情况下,服务端将会接收到客户端传来对象的完整信息,并且客户端将会同步服务端对该对象的任何变动。

对上面MyBinder进行改造后来验证,getAge中对name进行赋值:

    class MyBinder extends IMyAidlInterface.Stub {

        @Override
        public String getName(int age) throws RemoteException {
            return age > 30 ? "老王" : "小王";
        }

        @Override
        public int getAge(People people) throws RemoteException {
            people.mName = getName(people.mAge);
            return people.mAge;
        }

        @Override
        public void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat, double aDouble, String aString)
                throws RemoteException {
        }
    }
复制代码

如果People还是in,则输出还是小王,因为服务端对传参的修改不会影响客户端,
需要把People改成inout,这样输出就会变成老王。

interface IMyAidlInterface {
    ……
    int getAge(inout People people);
    ……
}
复制代码

需要注意,改成out/inout后,会发现aidl生成的java文件找不到readFromParcel方法,需要自己加上,和函数体和构造函数一致

public class People implements Parcelable {
    ……

    protected People(Parcel in) {
        this.mName = in.readString();
        this.mAge = in.readInt();
    }

    public void readFromParcel(Parcel in) {
        this.mName = in.readString();
        this.mAge = in.readInt();
    }
    ……
}
复制代码

in /out /inout 生成的java文件不同点

使用in的时候,调用了writeToParcel方法,将数据从客户端传到了服务端

@Override
public int getAge(com.vivo.aiengine.abilityhub.People people,
        com.vivo.aiengine.abilityhub.ICallbackInterface1 callback) throws android.os.RemoteException {
    android.os.Parcel _data = android.os.Parcel.obtain();
    android.os.Parcel _reply = android.os.Parcel.obtain();
    int _result;
    try {
        _data.writeInterfaceToken(DESCRIPTOR);
        //------------------in------------------
        if ((people != null)) {
            _data.writeInt(1);
            people.writeToParcel(_data, 0);
        } else {
            _data.writeInt(0);
        }
        //------------------in------------------
        _data.writeStrongBinder((((callback != null)) ? (callback.asBinder()) : (null)));
        boolean _status = mRemote.transact(Stub.TRANSACTION_getAge, _data, _reply, 0);
        if (!_status && getDefaultImpl() != null) {
            return getDefaultImpl().getAge(people, callback);
        }
        _reply.readException();
        _result = _reply.readInt();
    } finally {
        _reply.recycle();
        _data.recycle();
    }
    return _result;
}
复制代码

out的时候,调用了readFromParcel方法,将数据从服务端传到了客户端

@Override
public int getAge(com.vivo.aiengine.abilityhub.People people,
        com.vivo.aiengine.abilityhub.ICallbackInterface1 callback) throws android.os.RemoteException {
    android.os.Parcel _data = android.os.Parcel.obtain();
    android.os.Parcel _reply = android.os.Parcel.obtain();
    int _result;
    try {
        _data.writeInterfaceToken(DESCRIPTOR);
        _data.writeStrongBinder((((callback != null)) ? (callback.asBinder()) : (null)));
        boolean _status = mRemote.transact(Stub.TRANSACTION_getAge, _data, _reply, 0);
        if (!_status && getDefaultImpl() != null) {
            return getDefaultImpl().getAge(people, callback);
        }
        _reply.readException();
        _result = _reply.readInt();
        //------------------out------------------
        if ((0 != _reply.readInt())) {
            people.readFromParcel(_reply);
        }
        //------------------out------------------
    } finally {
        _reply.recycle();
        _data.recycle();
    }
    return _result;
}
复制代码

//inout是既有writeToParcel方法,又有readFromParcel方法

AIDL中的oneway 关键字

oneway 主要有两个特性:异步调用和串行化处理。

  • 异步调用是指应用向 binder 驱动发送数据后不需要挂起线程等待 binder 驱动的回复,而是直接结束。像一些系统服务调用应用进程的时候就会使用 oneway,比如 AMS 调用应用进程启动 Activity,这样就算应用进程中做了耗时的任务,也不会阻塞系统服务的运行。
  • 串行化处理是指对于一个服务端的 AIDL 接口而言,所有的 oneway 方法不会同时执行,binder 驱动会将他们串行化处理,排队一个一个调用。

oneway可以用来修饰interface,这样interface内所有的方法都隐式地带上oneway。
oneway可以修饰interface里的各个方法,被oneway修饰的方法不可以有返回值,也不可以带out或inout的参数。

使用oneway进行异步回调

首先新增一个用于回调的接口,ICallbackInterface.aidl

interface ICallbackInterface {
    oneway void callback(int totalName);
}
复制代码

再IMyAidlInterface中新增个oneway方法 getTotalAgeAsync。

这里再顺便结合使用上面说的List,需要手动import进ICallbackInterface和List类路径

package com.xfz.aidlapplication;

import com.xfz.aidlapplication.model.People;
import com.xfz.aidlapplication.ICallbackInterface;

import java.util.List;

interface IMyAidlInterface {

    String getName(int age);

    int getAge(inout People people);

    oneway void getTotalAgeAsync(in List<People> peopleList, ICallbackInterface callback);

    void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat,
            double aDouble, String aString);
}
复制代码

然后MyBinder中进行实现

    class MyBinder extends IMyAidlInterface.Stub {
……
        @Override
        public void getTotalAgeAsync(List<People> peopleList, ICallbackInterface callback) throws RemoteException {
            if (peopleList == null || peopleList.size() == 0 || callback == null) {
                return;
            }
            //这里也可以扔到异步线程中sleep几秒后再处理,也不会阻塞客户端进程
            int totalAge = 0;
            for (People people : peopleList) {
                totalAge += people.mAge;
            }
            callback.callback(totalAge);
        }
……
    }
复制代码

将上方修改后的aild文件同步到客户端进程后,进行调用即可

一个Service返回多种Binder的能力

为了减少Service占用资源,应该尽量减少Service的数量,而Service的onBind方法被绑定后会返回一个Binder,如果没有解绑,就不会再执行onBind方法了,所以即使第二次在intent中传入了不同类型的来获取新的Binder,也是不成功的。

解决的方法:Service返回一个工厂类binder,通过这个工厂类binder来生成不同的binder即可。

首先创建一个IBinderFactory.aidl

interface IBinderFactory {
    IBinder generateBinder(int binderType);
}
复制代码

在Binder实现类中,根据binderType返回不同的Binder,具体代码如下:

public class MyService extends Service {

    public static final int TYPE_MY_BINDER = 0;

    @Override
    public IBinder onBind(Intent intent) {
        return new BinderFactory();
    }

    class BinderFactory extends IBinderFactory.Stub {

        @Override
        public IBinder generateBinder(int binderType) throws RemoteException {
            IBinder binder;
            switch (binderType) {
                case TYPE_MY_BINDER:
                default:
                    binder = new MyBinder();

            }
            return binder;
        }
    }

……
}
复制代码

客户端先获得BinderFactory,再获得MyBinder

    private ServiceConnection mConnection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            //在服务连接成功后进行调用
            IBinderFactory iBinderFactory = IBinderFactory.Stub.asInterface(service);
            IMyAidlInterface iMyAidlInterface = null;
            try {
                iMyAidlInterface = IMyAidlInterface.Stub.asInterface(iBinderFactory.generateBinder(TYPE_MY_BINDER));
            } catch (RemoteException e) {
                e.printStackTrace();
            }
……
    }

复制代码

参考:

你真的懂AIDL的oneway嘛

看你简历上写熟悉 AIDL,说一说 oneway 吧

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享