AIDL 基础知识 知识点总结
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了AIDL 基础知识 知识点总结相关的知识,希望对你有一定的参考价值。
AIDL 简介
设计这门语言的目的是为了实现进程间通信,尤其是在涉及多进程并发情况下的进程间通信。
每一个进程都有自己的Dalvik VM实例,都有自己的一块独立的内存,都在自己的内存上存储自己的数据,执行着自己的操作,都在自己的那片狭小的空间里过完自己的一生。每个进程之间都你不知我,我不知你,就像是隔江相望的两座小岛一样,都在同一个世界里,但又各自有着自己的世界。而AIDL,就是两座小岛之间沟通的桥梁。相对于它们而言,我们就好像造物主一样,我们可以通过AIDL来制定一些规则,规定它们能进行哪些交流——比如,它们可以在我们制定的规则下传输一些特定规格的数据。
总之,通过这门语言,我们可以愉快的在一个进程访问另一个进程的数据,甚至调用它的一些方法,当然,只能是特定的方法。
但是,如果仅仅是要进行跨进程通信的话,其实我们还有其他的一些选择,比如 BroadcastReceiver , Messenger 等。但是 BroadcastReceiver 占用的系统资源比较多,如果是频繁的跨进程通信的话显然是不可取的;Messenger 进行跨进程通信时请求队列是同步进行的,无法并发执行,在有些要求多进程的情况下不适用,这种时候就需要使用 AIDL 了。
AIDL 基本语法
其实AIDL这门语言非常的简单,基本上它的语法和 Java 是一样的,只是在一些细微处有些许差别,毕竟它只是被创造出来简化Android程序员工作的,太复杂不好。这里我就着重的说一下它和 Java 不一样的地方。
【数据类型】
- 八种基本数据类型:byte,short,int,long,float,double,boolean,char。
- CharSequence、String
- List:List中的所有元素必须是AIDL支持的类型之一,或者是一个其他AIDL生成的接口,或者是定义的parcelable。List可以使用泛型。
- Map:Map中的所有元素必须是AIDL支持的类型之一,或者是一个其他AIDL生成的接口,或者是定义的parcelable。Map不支持泛型。
注意,数据流向是针对在客户端中的那个传入方法的对象而言的:
- 使用 in 作为定向 tag 的话表现为,服务端将会接收到一个那个对象的完整数据,但是客户端的那个对象不会因为服务端对传参的修改而发生变动;
- out 的话表现为,服务端将会接收到那个对象的的空对象,但是在服务端对接收到的空对象有任何修改之后客户端将会同步变动;
- 使用 inout 作为定向 tag 的情况下,服务端将会接收到客户端传来对象的完整信息,并且客户端将会同步服务端对该对象的任何变动。
两种类型的AIDL文件
下面是两个例子,对于常见的AIDL文件都有所涉及:
//第一类AIDL文件的例子。这个文件的作用是引入了一个序列化对象 Book 供其他的AIDL文件使用
//文件名为【Book.aidl】,同时还需定义一个实现Parcelable接口的【Book.java】的普通Java类
package com.bqt.aidlservice;//注意:Book.aidl与Book.java的包名应当是一样的
parcelable Book;//注意parcelable是小写
//第一类AIDL文件的例子。这个文件的作用是引入了一个序列化对象 Book 供其他的AIDL文件使用
//文件名为【Book.aidl】,同时还需定义一个实现Parcelable接口的【Book.java】的普通Java类
package com.bqt.aidlservice;//注意:Book.aidl与Book.java的包名应当是一样的
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
//第二类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);
}
为何要实现 Parcelable 接口
由于不同的进程有着不同的内存区域,并且它们只能访问自己的那一块内存区域,所以我们不能像平时那样,传一个句柄过去就完事了(句柄指向的是一个内存区域),现在目标进程根本不能访问源进程的内存,那把它传过去又有什么用呢?所以我们必须将要传输的数据转化为能够在内存之间流通的形式。这个转化的过程就叫做序列化与反序列化。
简单来说是这样的:比如现在我们要将一个对象的数据从客户端传到服务端去,我们就可以在客户端对这个对象进行序列化的操作,将其中包含的数据转化为序列化流,然后将这个序列化流传输到服务端的内存中去,再在服务端对这个数据流进行反序列化的操作,从而还原其中包含的数据。通过这种方式,我们就达到了在一个进程中访问另一个进程的数据的目的。
通常,在我们通过AIDL进行跨进程通信的时候,选择的序列化方式是实现 Parcelable 接口。
注:若AIDL文件中涉及到的所有数据类型均为默认支持的数据类型,则无此步骤,因为默认支持的那些数据类型都是可序列化的。
如何实现 Parcelable 接口
- 首先,创建一个类,正常的书写其成员变量,建立getter和setter并添加一个无参构造方法。
- 然后 implements Parcelable ,接着 as 就会报错,将鼠标移到那里,按下 alt+enter 让它自动解决错误,在弹出来的框里选择所有的成员变量,然后确定。
- 你会发现类里多了一些代码,但是现在还是会报错,Book下面仍然有一条小横线,再次将鼠标移到那里,按下 alt+enter 让它自动解决错误。
- 这次解决完错误之后就不会报错了,这个 Book 类也基本上实现了 Parcelable 接口,可以执行序列化操作了。
![技术分享](https://image.cha138.com/20210825/fe9634e20d0644f298cd6891ec802f1e.jpg)
那么这个 readFromParcel() 方法应当怎么写呢?其实,你把以上两种方式自动生成的有参构造方法中的代码拷贝过来就行了,例如:
public Book(Parcel in) {
name = in.readString();
price = in.readInt();
}
public void readFromParcel(Parcel in) {
name = in.readString();
price = in.readInt();
}
x
public Book(Parcel in) {
name = in.readString();
price = in.readInt();
}
public void readFromParcel(Parcel in) {
name = in.readString();
price = in.readInt();
}
像上面这样添加了 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 的错误。
![技术分享](https://image.cha138.com/20210825/13a06acf22b94d1190559e942e5d5a72.jpg)
为什么会这样呢?因为 Gradle 。大家都知道,Android Studio 是默认使用 Gradle 来构建 Android 项目的,而 Gradle 在构建项目的时候会通过 sourceSets 来配置不同文件的访问路径,从而加快查找速度,问题就出在这里,Gradle 默认是将 java 代码的访问路径设置在 java 包下的,这样一来,如果 java 文件是放在 aidl 包下的话那么理所当然系统是找不到这个 java 文件的。那应该怎么办呢?
sourceSets {
main {
java.srcDirs = [‘src/main/java‘, ‘src/main/aidl‘]
}
}
sourceSets {
main {
java.srcDirs = [‘src/main/java‘, ‘src/main/aidl‘]
}
}
也就是把 java 代码的访问路径设置成了 java 包和 aidl 包,这样一来系统就会到 aidl 包里面去查找 java 文件,也就达到了我们的目的。
如何把服务端代码拷贝到客户端
我们需要保证,在客户端和服务端中都有我们需要用到的所有 .aidl 文件和其中涉及到的 .java 文件,因此不管在哪一端写的这些东西,写完之后我们都要把这些文件复制到另一端去。
如果是用的上面两个方法中的方法一解决找不到 .java 文件的问题的,那么直接将 aidl 文件夹复制到另一端的 main 目录下就可以了;
如果是使用的方法二的话,除了要把整个 aidl 文件夹拿过去,还要单独将需要的 .java 文件按照包目录放到 java 文件夹里去。
通过上面几步,我们已经完成了AIDL及其相关文件的全部内容,那么我们究竟应该如何利用这些东西来进行跨进程通信呢?
其实,在我们写完AIDL文件并 clean 或者 rebuild 项目之后,编译器会根据AIDL文件为我们生成一个与AIDL文件同名的 .java 文件,这个 .java 文件才是与我们的跨进程通信密切相关的东西。
事实上,基本的操作流程就是:在服务端实现AIDL中定义的方法接口的具体逻辑,然后在客户端调用这些方法接口,从而达到跨进程通信的目的。
以上是关于AIDL 基础知识 知识点总结的主要内容,如果未能解决你的问题,请参考以下文章