AIDL 基础知识 知识点总结

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了AIDL 基础知识 知识点总结相关的知识,希望对你有一定的参考价值。

AIDL 简介

AIDL是一个缩写,全称是android Interface Definition Language,也就是Android接口定义语言

设计这门语言的目的是为了实现进程间通信,尤其是在涉及多进程并发情况下的进程间通信。


每一个进程都有自己的Dalvik VM实例,都有自己的一块独立的内存,都在自己的内存上存储自己的数据,执行着自己的操作,都在自己的那片狭小的空间里过完自己的一生。每个进程之间都你不知我,我不知你,就像是隔江相望的两座小岛一样,都在同一个世界里,但又各自有着自己的世界而AIDL,就是两座小岛之间沟通的桥梁。相对于它们而言,我们就好像造物主一样,我们可以通过AIDL来制定一些规则,规定它们能进行哪些交流——比如,它们可以在我们制定的规则下传输一些特定规格的数据。

总之,通过这门语言,我们可以愉快的在一个进程访问另一个进程的数据,甚至调用它的一些方法,当然,只能是特定的方法。


但是,如果仅仅是要进行跨进程通信的话,其实我们还有其他的一些选择,比如 BroadcastReceiver , Messenger 等。但是 BroadcastReceiver 占用的系统资源比较多,如果是频繁的跨进程通信的话显然是不可取的;Messenger 进行跨进程通信时请求队列是同步进行的,无法并发执行,在有些要求多进程的情况下不适用,这种时候就需要使用 AIDL 了。


AIDL 基本语法

其实AIDL这门语言非常的简单,基本上它的语法和 Java 是一样的,只是在一些细微处有些许差别,毕竟它只是被创造出来简化Android程序员工作的,太复杂不好。这里我就着重的说一下它和 Java 不一样的地方。


【文件类型】
用AIDL书写的文件的后缀是 .aidl,而不是 .java。

【数据类型】
AIDL默认支持一些数据类型,在使用这些数据类型的时候是不需要导包的,但是除了这些类型之外的数据类型,在使用之前必须导包,就算目标文件与当前正在编写的 .aidl 文件在同一个包下(在 Java 中,这种情况是不需要导包的)。
默认支持的数据类型包括: 
  • 八种基本数据类型:byte,short,int,long,float,double,boolean,char。
  • CharSequence、String
  • List:List中的所有元素必须是AIDL支持的类型之一,或者是一个其他AIDL生成的接口,或者是定义的parcelableList可以使用泛型。
  • Map:Map中的所有元素必须是AIDL支持的类型之一,或者是一个其他AIDL生成的接口,或者是定义的parcelable。Map不支持泛型。

【定向tag】
AIDL中的定向 tag 表示了在跨进程通信中数据的流向,其中 in 表示数据只能由客户端流向服务端, out 表示数据只能由服务端流向客户端,而 inout 则表示数据可在服务端与客户端之间双向流通。Java 中的基本类型和 String、CharSequence 的定向 tag 默认且只能是 in 。
注意,数据流向是针对在客户端中的那个传入方法的对象而言的:
  • 使用 in 作为定向 tag 的话表现为,服务端将会接收到一个那个对象的完整数据,但是客户端的那个对象不会因为服务端对传参的修改而发生变动
  • out 的话表现为,服务端将会接收到那个对象的的空对象,但是在服务端对接收到的空对象有任何修改之后客户端将会同步变动
  • 使用 inout 为定向 tag 的情况下,服务端将会接收到客户端传来对象的完整信息,并且客户端将会同步服务端对该对象的任何变动

两种类型的AIDL文件

在我的理解里,AIDL文件可以分为两类,一类是用来定义parcelable对象,以供其他AIDL文件使用AIDL中的;一类是用来定义方法接口,以供系统使用来完成跨进程通信的。可以看到,这两类AIDL文件都是在"定义"些什么,而不涉及具体的实现,这就是为什么它叫做"Android接口定义语言" 

下面是两个例子,对于常见的AIDL文件都有所涉及:

//第一类AIDL文件的例子。这个文件的作用是引入了一个序列化对象 Book 供其他的AIDL文件使用
//文件名为【Book.aidl】,同时还需定义一个实现Parcelable接口的【Book.java】的普通Java类
package com.bqt.aidlservice;//注意:Book.aidl与Book.java的包名应当是一样的

parcelable Book;//注意parcelable是小写
 
1
//第一类AIDL文件的例子。这个文件的作用是引入了一个序列化对象 Book 供其他的AIDL文件使用
2
//文件名为【Book.aidl】,同时还需定义一个实现Parcelable接口的【Book.java】的普通Java类
3
package com.bqt.aidlservice;//注意:Book.aidl与Book.java的包名应当是一样的
4
5
parcelable Book;//注意parcelable是小写
//第二类AIDL文件的例子。文件名为【BookManager.aidl】
package com.bqt.aidlservice;
import com.bqt.aidlservice.Book;//必须导入所有需要使用的非默认支持数据类型的类的包,即使他们在同一包下

interface BookManager {
    //所有的返回值前都不需要加任何东西,不管是什么数据类型
    List<Book> getBooks();
    Book getBook();
    int getBookCount();

    //定义参数时,除了Java基本类型以及String、CharSequence之外的类型,都需要在前面加上定向tag
    void setBookPrice(in Book book , int price)
    void setBookName(in Book book , String name)
    void addBookIn(in Book book);
    void addBookOut(out Book book);
    void addBookInout(inout Book book);
}
x
 
1
//第二类AIDL文件的例子。文件名为【BookManager.aidl】
2
package com.bqt.aidlservice;
3
import com.bqt.aidlservice.Book;//必须导入所有需要使用的非默认支持数据类型的类的包,即使他们在同一包下
4
5
interface BookManager {
6
    //所有的返回值前都不需要加任何东西,不管是什么数据类型
7
    List<Book> getBooks();
8
    Book getBook();
9
    int getBookCount();
10
11
    //定义参数时,除了Java基本类型以及String、CharSequence之外的类型,都需要在前面加上定向tag
12
    void setBookPrice(in Book book , int price)
13
    void setBookName(in Book book , String name)
14
    void addBookIn(in Book book);
15
    void addBookOut(out Book book);
16
    void addBookInout(inout Book book);
17
}

为何要实现 Parcelable 接口

由于不同的进程有着不同的内存区域,并且它们只能访问自己的那一块内存区域,所以我们不能像平时那样,传一个句柄过去就完事了(句柄指向的是一个内存区域),现在目标进程根本不能访问源进程的内存,那把它传过去又有什么用呢?所以我们必须将要传输的数据转化为能够在内存之间流通的形式。这个转化的过程就叫做序列化与反序列化


简单来说是这样的:比如现在我们要将一个对象的数据从客户端传到服务端去,我们就可以在客户端对这个对象进行序列化的操作,将其中包含的数据转化为序列化流,然后将这个序列化流传输到服务端的内存中去,再在服务端对这个数据流进行反序列化的操作,从而还原其中包含的数据。通过这种方式,我们就达到了在一个进程中访问另一个进程的数据的目的。


通常,在我们通过AIDL进行跨进程通信的时候,选择的序列化方式是实现 Parcelable 接口。

注:若AIDL文件中涉及到的所有数据类型均为默认支持的数据类型,则无此步骤,因为默认支持的那些数据类型都是可序列化的。


如何实现 Parcelable 接口

方法一:使用AS的提示半自动创建
  • 首先,创建一个类,正常的书写其成员变量,建立getter和setter并添加一个无参构造方法。
  • 然后 implements Parcelable ,接着 as 就会报错,将鼠标移到那里,按下 alt+enter 让它自动解决错误,在弹出来的框里选择所有的成员变量,然后确定。
  • 你会发现类里多了一些代码,但是现在还是会报错,Book下面仍然有一条小横线,再次将鼠标移到那里,按下 alt+enter 让它自动解决错误。
  • 这次解决完错误之后就不会报错了,这个 Book 类也基本上实现了 Parcelable 接口,可以执行序列化操作了。

方法二:使用【Android Parcelable Code Generator】等AS插件,你只需定义其成员变量,他就可以全自动的帮你将当前JavaBean修改为Parcelable接口的实现类。

注意,这里有一个坑:以上这两种方式生成的模板类的对象只支持为 in 的定向 tag 。
为什么呢?因为默认生成的类里面只有writeToParcel() 方法,而如果要支持为 out 或者 inout 的定向 tag 的话,还需要实现 readFromParcel() 方法,但是而这个方法其实并没有在 Parcelable 接口里面定义,而只是AIDL规范!
如果你不写的话,可以发现,在自动生成的AIDL接口文件对于的Java文件中,其中有这么一行错误提示:
技术分享

所以这个方法需要我们手动写,而且如果要支持为 out 或者 inout 的定向 tag 的话,必须写。

那么这个 readFromParcel() 方法应当怎么写呢?其实,你把以上两种方式自动生成的有参构造方法中的代码拷贝过来就行了,例如:

public Book(Parcel in) {
	name = in.readString();
	price = in.readInt();
}

public void readFromParcel(Parcel in) {
	name = in.readString();
	price = in.readInt();
}
x
1
public Book(Parcel in) {
2
    name = in.readString();
3
    price = in.readInt();
4
}
5
6
public void readFromParcel(Parcel in) {
7
    name = in.readString();
8
    price = in.readInt();
9
}

像上面这样添加了 readFromParcel() 方法之后,我们的 Book 类的对象在AIDL文件里就可以用 out 或者 inout 来作为它的定向 tag 了。


如何书写AIDL文件

首先我们需要一个 Book.aidl 文件来将 Book 类引入,以使其他的 AIDL 文件中可以使用 Book 对象。那么第一步,如何新建一个 AIDL 文件呢?Android Studio已经帮我们把这个集成进去了:

技术分享

鼠标移到app上面去,点击右键,然后 new->AIDL->AIDL File,确认后就会生成一个AIDL文件了。生成AIDL文件之后,项目的目录会变成这样的:

技术分享

比起以前多了一个叫做 aidl 的包,而且他的层级是和 java 包相同的


如果你用的是 Eclipse 或者较低版本的 as ,编译器没有这个选项怎么办呢?没关系,我们也可以自己写。

打开项目文件夹,依次进入 app->src->main,在 main 包下新建一个和 java 文件夹平级的 aidl 文件夹,然后我们手动在这个文件夹里面新建和 java 文件夹里面的默认结构一样的文件夹结构,再在最里层新建 .aidl 文件就可以了。


提示找不到类怎么办

注意:这里又有一个巨大的坑!


大家可能注意到了,在 Book.aidl 文件中,我一直在强调:Book.aidl与Book.java的包名应当是一样的。这似乎理所当然的意味着这两个文件应当是在同一个包里面的,事实上,很多比较老的文章里就是这样说的,他们说最好都在 aidl 包里同一个包下,方便移植。


然而在 Android Studio 里并不是这样!如果这样做的话,系统根本就找不到 Book.java 文件,从而在其他的AIDL文件里面使用 Book 对象的时候会报 Symbol not found 的错误。

技术分享

为什么会这样呢?因为 Gradle 。大家都知道,Android Studio 是默认使用 Gradle 来构建 Android 项目的,而 Gradle 在构建项目的时候会通过 sourceSets 来配置不同文件的访问路径,从而加快查找速度,问题就出在这里,Gradle 默认是将 java 代码的访问路径设置在 java 包下的,这样一来,如果 java 文件是放在 aidl 包下的话那么理所当然系统是找不到这个 java 文件的。那应该怎么办呢?


方法一:(推荐)修改 build.gradle 文件,在 android{} 中间加上下面的内容:
sourceSets {
    main {
        java.srcDirs = [‘src/main/java‘, ‘src/main/aidl‘]
    }
}
 
1
sourceSets {
2
    main {
3
        java.srcDirs = [‘src/main/java‘, ‘src/main/aidl‘]
4
    }
5
}

也就是把 java 代码的访问路径设置成了 java 包和 aidl 包,这样一来系统就会到 aidl 包里面去查找 java 文件,也就达到了我们的目的。


方法二:(不推荐)Book.java 文件放到 java 目录下的包里,保持其包名与 Book.aidl 一致。只要它的包名不变,Book.aidl 就能找到 Book.java ,而只要 Book.java 在 java 包下,那么系统也是能找到它的。
但是这样做的话也有一个问题,就是在移植相关 .aidl 文件和 .java 文件的时候没那么方便,不能直接把整个 aidl 文件夹拿过去完事儿了,还要单独将 .java 文件放到 java 文件夹里去


如何把服务端代码拷贝到客户端

我们需要保证,在客户端和服务端中都有我们需要用到的所有 .aidl 文件和其中涉及到的 .java 文件,因此不管在哪一端写的这些东西,写完之后我们都要把这些文件复制到另一端去。

如果是用的上面两个方法中的方法一解决找不到 .java 文件的问题的,那么直接将 aidl 文件夹复制到另一端的 main 目录下就可以了;

如果是使用的方法二的话,除了要把整个 aidl 文件夹拿过去,还要单独将需要的 .java 文件按照包目录放到 java 文件夹里去。


通过上面几步,我们已经完成了AIDL及其相关文件的全部内容,那么我们究竟应该如何利用这些东西来进行跨进程通信呢?

其实,在我们写完AIDL文件并 clean 或者 rebuild 项目之后,编译器会根据AIDL文件为我们生成一个与AIDL文件同名的 .java 文件,这个 .java 文件才是与我们的跨进程通信密切相关的东西

事实上,基本的操作流程就是:在服务端实现AIDL中定义的方法接口的具体逻辑,然后在客户端调用这些方法接口,从而达到跨进程通信的目的。




















































以上是关于AIDL 基础知识 知识点总结的主要内容,如果未能解决你的问题,请参考以下文章

线程学习知识点总结

Android获取各个应用程序的缓存文件代码小片段(使用AIDL)

Android Service aidl分析

Android进阶笔记:AIDL内部实现详解

AIDL中的传参及inoutinout

Android 中的 Service 全面总结详解下