Android AIDL使用

一. 概述

Android 接口定义语言 (AIDL) 与您可能使用过的其他接口语言 (IDL) 类似。您可以利用它定义客户端与服务均认可的编程接口,以便二者使用进程间通信 (IPC) 进行相互通信。在 Android 中,一个进程通常无法访问另一个进程的内存。因此,为进行通信,进程需将其对象分解成可供操作系统理解的原语,并将其编组为可供您操作的对象。

二. 定义 AIDL 接口

在构建每个包含 .aidl 文件的应用时,Android SDK 工具会生成基于该 .aidl 文件的 IBinder 接口,并将其保存到项目的 gen/ 目录中。服务必须视情况实现 IBinder 接口。然后,客户端应用便可绑定到该服务,并调用 IBinder 中的方法来执行 IPC

AIDL 使用一种简单语法,允许您通过一个或多个方法(可接收参数和返回值)来声明接口。参数和返回值可为任意类型,甚至是 AIDL 生成的其他接口。

您必须使用 Java 编程语言构建 .aidl 文件。每个 .aidl 文件均须定义单个接口,并且只需要接口声明和方法签名。

默认情况下,AIDL 支持下列数据类型:

1. Java 编程语言中的8中基本类型(如 byte  char  boolean  short   int   float  long   double)

2. CharSequence类型,如String、SpannableString等;

3. List
List 中的所有元素必须是以上列表中支持的数据类型,或者您所声明的由 AIDL 生成的其他接口或 Parcelable 类型。您可选择将 List 用作“泛型”类(例如,List<String>)。尽管生成的方法旨在使用 List 接口,但另一方实际接收的具体类始终是 ArrayList。

4. Map
Map 中的所有元素必须是以上列表中支持的数据类型,或者您所声明的由 AIDL 生成的其他接口或 Parcelable 类型。不支持泛型 Map(如 Map<String,Integer> 形式的 Map)。尽管生成的方法旨在使用 Map 接口,但另一方实际接收的具体类始终是 HashMap。

5. 所有Parceable接口的实现类,因为跨进程传输对象时,本质上是序列化与反序列化的过程;

6. AIDL接口,所有的AIDL接口本身也可以作为可支持的数据类型;

有两个需要注意的地方:

1、在Java中,如果一个对象和引用它的类在同一个package下,是不需要导包的,即不需要import,而在AIDL中自定义的Parceable对象和AIDL接口定义的对象必须在所引用的AIDL文件中显式import进来,不管这些对象和所引用它们的AIDL文件是否在同一个包下

2、如果AIDL文件中使用到自定义的Parceable对象,则必须再创建一个与Parceable对象同名的AIDL文件,声明该对象为Parceable类型,并且根据上一条语法规定,在AIDL文件中进行显式import。

定义服务接口时,请注意:

1)方法可带零个或多个参数,返回值或空值方法前不能用 public/private/protected/final 等关键字修饰.
2)所有非原基本类型参数均需要指示数据走向的方向标记。这类标记可以是 in、out 或 inout(见下方示例) , 比如Parcelable 对象.

3)定向tag正确使用
AIDL中,除了基本数据类型,其他类型的方法参数都必须标上数据在跨进程通信中的流向:in、out或inout:

1、in表示输入型参数:只能由客户端流向服务端,服务端收到该参数对象的完整数据,但服务端对该对象的后续修改不会影响到客户端传入的参数对象;(表现为服务端修改此参数,不会影响客户端的对象)

2、out表示输出型参数:只能由服务端流向客户端,服务端收到该参数的空对象,服务端对该对象的后续修改将同步改动到客户端的相应参数对象;(表现为服务端收到的参数是空对象,并且服务端修改对象后客户端会同步变动)

3、inout表示输入输出型参数:可在客户端与服务端双向流动,服务端接收到该参数对象的完整数据,且服务端对该对象的后续修改将同步改动到客户端的相应参数对象;

4.  oneway 关键字用于修改远程调用的行为,被oneway修饰了的方法不可以有返回值,也不可以有带out或inout的参数。

本地调用(同步调用)
远程调用(异步调用,即客户端不会被阻塞)
使用oneway时,远程调用不会阻塞;它只是发送事务数据并立即返回。接口的实现最终接收此调用时,是以正常远程调用形式将其作为来自 Binder 线程池的常规调用进行接收。

其特点:

oneway可以用来修饰在interface之前,这样会造成interface内所有的方法都隐式地带上oneway;

带oneway的方法,不会生成局部变量_reply。且Proxy中transact中第四个参数必为android.os.IBinder.FLAG_ONEWAY

如果tag用的inout的话,在写自定义的Parcelable类时,必须要重写此方法,不然就会报错

public void readFromParcel(Parcel parcel)

定向tag需要一定的开销,根据实际需要去确定选择什么tag,不能滥用。
 

写到这里也顺便复习一下 Parcelable 基础知识

Parcelable 是 Android 特有的序列化接口:

public interface Parcelable {
    //writeToParcel() 方法中的参数,用于标识当前对象作为返回值返回
    //有些实现类可能会在这时释放其中的资源
    public static final int PARCELABLE_WRITE_RETURN_VALUE = 0x0001;

    //writeToParcel() 方法中的第二个参数,它标识父对象会管理内部状态中重复的数据
    public static final int PARCELABLE_ELIDE_DUPLICATES = 0x0002;

    //用于 describeContents() 方法的位掩码,每一位都代表着一种对象类型
    public static final int CONTENTS_FILE_DESCRIPTOR = 0x0001;

    //描述当前 Parcelable 实例的对象类型
    //比如说,如果对象中有文件描述符,这个方法就会返回上面的 CONTENTS_FILE_DESCRIPTOR
    //其他情况会返回一个位掩码
    public int describeContents();

    //将对象转换成一个 Parcel 对象
    //参数中 dest 表示要写入的 Parcel 对象
    //flags 表示这个对象将如何写入
    public void writeToParcel(Parcel dest, int flags);

    //实现类必须有一个 Creator 属性,用于反序列化,将 Parcel 对象转换为 Parcelable 
    public interface Creator<T> {

        public T createFromParcel(Parcel source);

        public T[] newArray(int size);
    }

    //对象创建时提供的一个创建器
    public interface ClassLoaderCreator<T> extends Creator<T> {
        //使用类加载器和之前序列化成的 Parcel 对象反序列化一个对象
        public T createFromParcel(Parcel source, ClassLoader loader);
    }
}

写一个自定义的ParcelableObj类

public class ParcelableObj implements Parcelable {

    //元素name
    private String mName;

    //构造法方法
    public ParcelableObj(String name){
        mName = name;
    }

    protected ParcelableObj(Parcel in) {
        mName = in.readString();
    }

    //序列化
    @Override
    public void writeToParcel(Parcel dest, int flags) {
        dest.writeString(mName);
    }

    /*内容描述*/
    @Override
    public int describeContents() {
        return 0;
    }

    //反序列化
    public static final Creator<ParcelableObj> CREATOR = new Creator<ParcelableObj>() {
       //反序列创建对象
        @Override
        public ParcelableObj createFromParcel(Parcel in) {
            return new ParcelableObj(in);
        }

        //反序列创建对象数组
        @Override
        public ParcelableObj[] newArray(int size) {
            return new ParcelableObj[size];
        }
    };
}

通过writeToParcel将你的对象映射成Parcel对象,再通过createFromParcel将Parcel对象映射成你的对象。也可以将Parcel看成是一个流,通过writeToParcel把对象写到流里面,在通过createFromParcel从流里读取对象,只不过这个过程需要你来实现,因此写的顺序和读的顺序必须一致.

三. 编写AIDL文件

AS中可以通过 new AIDL 自动创建一个aidl文件, 内容根据需求自定义编写.

在AndroidStudio中工程目录的Android视图下,右键new一个AIDL文件,

默认将创建一个与java文件夹同级的aidl文件夹用于存放AIDL文件aidl文件夹下的包名与build.gradle中配置的applicationId一致applicationId默认值是应用的包名

例子一 :普通的基本类型数据

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

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

interface IMyAidlInterface {

    String querybyID(int n);
}

注意事项:  aidl 接口方法中 不能加 public/ private/ protected/final 修饰符;否则就会报错

例子二:  aidl中 用到了自定义Parcelable对象

new 一个完整的app工程, 然后在new 一个 AIDL文件, 工程结构图如下:

PathParceTest.java  implement 实现  Parcelable类

public class PathParceTest implements Parcelable

对于用到自定义Parcelable对象的aidl文件, 创建的规则如下:

1.  自定义的Parcelable对象和AIDL对象必须要显示的import进来,不管他们是否和当前的AIDL文件位于同一个包内.

2. 如果AIDL文件中用到了自定义的Parcelable对象, 那么必须新建一个和它同名的AIDL文件, 并在其中声明它为Parcelable类型.

第2条在代码中体现如下:

比如ITest.aidl 用到了  PathParceTest对象, 那么我们先得写一个PathParceTest类, 然后创建 一个同名的PathParceTest.aidl文件

 上面是创建AIDL文件的语法规则, 按照规则书写代码即可,不然会报错.

对于例子二, 假如你在服务端已经写好了这些aidl代码, 现在要拷贝到客户端, 根据规则, 创建的aidl 必须保持包名一致拷贝过去,  但是你的PathParceTest.java是在服务端的src/main/java/com.example.myaidl/ 路径下,   如果要拷贝过去的话, 文件包名(路径名)在客户端又得新建,然后把PathParceTest.java放置在此路径下.  那么客户端的代码结构非常的难看.

那么,有没有什么更好优化解决办法呢?目的把aidl包所有文件整体拷贝就完事

如果我们把PathParceTest.java放置到同aidl包下,  在编译的时候会报错,提示这个类找不到.

 原因:

我们是在src/main/aidl文件夹下创建PathParceTest.java的,实际上这将因为找不到PathParceTest.java而报错,因为在AndroidStudio中使用Gradle构建项目时,默认是在src/main/java文件夹中查找java文件的,如果把PathParceTest.java放在src/main/aidl对应包名下,自然就会找不到这个文件了,所以需要修改app的build.gradle文件,在sourceSets下添加对应的源文件路径,即src/main/aidl

android {
    compileSdkVersion 33

    //加上这段代码 把 src/main/aidl/路径下的java文件也编译进来
    sourceSets {
        main {
            java.srcDirs = ["src/main/java", "src/main/aidl"]
        }
    }
    //加上这段代码 把 src/main/aidl/路径下的java文件也编译进来

    defaultConfig {
        applicationId "com.example.myaidl"
        minSdkVersion 16
        targetSdkVersion 33
        versionCode 1
        versionName "1.0"

        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"


    ........
    ........

这样子就解决了上述的问题, 当然客户端中也得加上这段代码, 接下来我们就可以直接拷贝整个aidl包了.

四. 总结

        本文主要讲解了AIDL的语法规则和创建AIDL文件的注意点, 特别是引用自定义Parcelable对象的aidl文件创建注意事项. 也是为后续写 进程间通信机制 相关的文章做个铺垫.


 

Android AIDLAndroid Interface Definition Language)是一种用于定义客户端和服务之间通信接口的语言。AIDL 是一个 Android 特有的 RPC(远程过程调用)机制。 下面是使用 AIDL 的基本步骤: 1. 定义 AIDL 接口 在服务端创建一个 AIDL 文件,定义服务的接口方法。例如,创建一个名为 IMyService.aidl 的文件,其中包含以下内容: ``` interface IMyService { void sayHello(); } ``` 2. 实现 AIDL 接口 在服务端实现 AIDL 接口,例如: ``` public class MyService extends Service { private final IMyService.Stub binder = new IMyService.Stub() { @Override public void sayHello() throws RemoteException { Log.i("MyService", "Hello World"); } }; @Nullable @Override public IBinder onBind(Intent intent) { return binder; } } ``` 3. 绑定服务 在客户端绑定服务,例如: ``` private ServiceConnection connection = new ServiceConnection() { @Override public void onServiceConnected(ComponentName name, IBinder service) { IMyService myService = IMyService.Stub.asInterface(service); try { myService.sayHello(); } catch (RemoteException e) { e.printStackTrace(); } } @Override public void onServiceDisconnected(ComponentName name) { } }; private void bindService() { Intent intent = new Intent(this, MyService.class); bindService(intent, connection, Context.BIND_AUTO_CREATE); } ``` 通过上述步骤,就可以实现客户端与服务端之间的通信。需要注意的是,AIDL 接口中定义的方法必须是可序列化的。如果方法参数或返回值类型不支持序列化,可以通过 Parcelable 接口实现序列化。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值