AIDL 基础知识 知识点总结

AIDL 是什么

IPC:InterProcess Communication,跨进程通信
AIDL:Android Interface Definition Language,Android接口定义语言
2
2
 
1
IPC:InterProcess Communication,跨进程通信
2
AIDL:Android Interface Definition Language,Android接口定义语言
在Android系统中,因为每个应用程序都运行在自己的进程中,所以进程之间一般是无法直接进行数据交换的, 为了实现跨进程,Android给我们提供了Binder机制,而这个机制使用的接口语言就是AIDL。
每一个进程都有自己的Dalvik VM实例,都有自己的一块独立的内存,都在自己的内存上存储自己的数据,执行着自己的操作,都在自己的那片狭小的空间里过完自己的一生。每个进程之间都你不知我,我不知你,就像是隔江相望的两座小岛一样,都在同一个世界里,但又各自有着自己的世界。而AIDL,就是两座小岛之间沟通的桥梁。
1
1
 
1
每一个进程都有自己的Dalvik VM实例,都有自己的一块独立的内存,都在自己的内存上存储自己的数据,执行着自己的操作,都在自己的那片狭小的空间里过完自己的一生。每个进程之间都你不知我,我不知你,就像是隔江相望的两座小岛一样,都在同一个世界里,但又各自有着自己的世界。而AIDL,就是两座小岛之间沟通的桥梁。

设计这门语言的目的就是为了实现进程间通信。


但是,如果仅仅是要进行跨进程通信的话,其实我们还有其他的一些选择,比如 BroadcastReceiver , Messenger 等,为什么要用AIDL呢?

因为 BroadcastReceiver 占用的系统资源比较多,如果是频繁的跨进程通信的话显然是不可取的;Messenger 进行跨进程通信时请求队列是同步进行的,无法并发执行,在有些要求多进程的情况下不适用,这种情况下就需要使用 AIDL 了。


总之,通过这门语言,我们可以愉快的在一个进程访问(还可以修改)另一个进程的数据,以及调用它的一些特定的方法。

AIDL 的基本语法

这种接口语言并非真正的编程语言,只是定义两个进程间的通信接口而已, 而据此生成的Java接口文件则是由Android SDK中platform-tools目录下的aidl.exe工具自动生成的。

其实AIDL的语法和 Java 中的接口定义的语法基本是一样的,只是在一些细微处有些许差别。


【基本语法】
AIDL中定义的接口名字需要与aidl文件名相同,后缀名是 .aidl,而不是 .java。
接口和方法前面不要加任何Java修饰符:如public 、private、protected、tatic、final等

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

【定向tag】
除了默认支持的数据类型之外的其他类型,均需要之定一个定向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是小写
5
 
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);
}
17
 
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 接口

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


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


Android中实现序列化有两种方式,一种是通过Java提供的Serializable接口,另一种是通过Android SDK提供的Parcelable接口。

相比Serializable,Parcelable更加轻量级,性能更好,速度更快。

Parcelable和Serializable的比较
    在内存的使用中,Parcelable在性能方面要强于Serializable。
    Serializable在序列化操作的时候会产生大量的临时变量(原因是使用了反射机制),从而导致GC的频繁调用,因此在性能上会稍微逊色。
    Parcelable是以Ibinder作为信息载体的,在内存上的开销比较小,因此在内存之间进行数据传递的时候,Android推荐使用Parcelable。
    在读写数据的时候,Parcelable是在内存中直接进行读写,而Serializable是通过使用IO流的形式将数据读写入在硬盘上。

虽然Parcelable的性能要强于Serializable,但是仍然有特殊的情况需要使用Serializable,而不去使用Parcelable,因为Parcelable无法将数据进行持久化,因此在将数据保存在磁盘的时候,仍然需要使用后者。
x
 
1
Parcelable和Serializable的比较
2
    在内存的使用中,Parcelable在性能方面要强于Serializable。
3
    Serializable在序列化操作的时候会产生大量的临时变量(原因是使用了反射机制),从而导致GC的频繁调用,因此在性能上会稍微逊色。
4
    Parcelable是以Ibinder作为信息载体的,在内存上的开销比较小,因此在内存之间进行数据传递的时候,Android推荐使用Parcelable。
5
    在读写数据的时候,Parcelable是在内存中直接进行读写,而Serializable是通过使用IO流的形式将数据读写入在硬盘上。
6
 
          
7
虽然Parcelable的性能要强于Serializable,但是仍然有特殊的情况需要使用Serializable,而不去使用Parcelable,因为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文件中,其中有这么一行错误提示:
795730-20171102103229623-1926407593.png

所以这个方法需要我们手动写,而且如果要支持为 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();
}
readFromParcel
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已经帮我们把这个集成进去了:

新建AIDL文件

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

建立AIDL文件后的项目目录

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


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

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


提示找不到 Java 类怎么办

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


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


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

795730-20171102103231045-1116323363.png

为什么会这样呢?因为 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']
    }
}
5
5
 
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中定义的方法接口的具体逻辑,然后在客户端调用这些方法接口,从而达到跨进程通信的目的。





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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值