Android四大组件Service之AIDL详解
Android四大组件Service之AIDL详解
- 前言
- 简介
- 基础知识
- AIDL 服务端
- 定义AIDL文件规则
- 创建 .aidl 文件
- 清单注册
- 通过 IPC 传递对象
- 调用 IPC 方法
- Android Studio AIDL文件定义的两种方式
- 第一种方式:默认目录:src\mian\aidl
- 第二种方式:配置build.gradle
- 友情链接
- 代码示例:
前言
如果没有看过上篇的同学建议看看上篇的《Android四大组件之绑定Service详解》
简介
AIDL(Android 接口定义语言)与您可能使用过的其他 IDL 类似。 您可以利用它定义客户端与服务使用进程间通信 (IPC) 进行相互通信时都认可的编程接口。 在 Android 上,一个进程通常无法访问另一个进程的内存。 尽管如此,进程需要将其对象分解成操作系统能够识别的原语,并将对象编组成跨越边界的对象。 编写执行这一编组操作的代码是一项繁琐的工作,因此 Android 会使用 AIDL 来处理。
注:只有允许不同应用的客户端用 IPC 方式访问服务,并且想要在服务中处理多线程时,才有必要使用 AIDL。 如果您不需要执行跨越不同应用的并发 IPC,就应该通过实现一个 Binder 创建接口;或者,如果您想执行 IPC,但根本不需要处理多线程,则使用 Messenger 类来实现接口。无论如何,在实现 AIDL 之前,请您务必理解绑定服务。
基础知识
在您开始设计 AIDL 接口之前,要注意 AIDL 接口的调用是直接函数调用。 您不应该假设发生调用的线程。 视调用来自本地进程还是远程进程中的线程,实际情况会有所差异。 具体而言:
- 来自本地进程的调用在发起调用的同一线程内执行。如果该线程是您的主 UI 线程,则该线程继续在 AIDL 接口中执行。 如果该线程是其他线程,则其便是在服务中执行您的代码的线程。 因此,只有在本地线程访问服务时,您才能完全控制哪些线程在服务中执行(但如果真是这种情况,您根本不应该使用 AIDL,而是应该通过实现 Binder 类创建接口)。
- 来自远程进程的调用分派自平台在您的自有进程内部维护的线程池。 您必须为来自未知线程的多次并发传入调用做好准备。 换言之,AIDL 接口的实现必须是完全线程安全实现。
- oneway 关键字用于修改远程调用的行为。使用该关键字时,远程调用不会阻塞;它只是发送事务数据并立即返回。接口的实现最终接收此调用时,是以正常远程调用形式将其作为来自 Binder 线程池的常规调用进行接收。 如果 oneway 用于本地调用,则不会有任何影响,调用仍是同步调用。
AIDL 服务端
定义AIDL文件规则
您必须使用 Java 编程语言语法在 .aidl 文件中定义 AIDL 接口,然后将它保存在托管服务的应用以及任何其他绑定到服务的应用的源代码(src/ 目录)内。
您开发每个包含 .aidl 文件的应用时,Android SDK 工具都会生成一个基于该 .aidl 文件的 IBinder 接口,并将其保存在项目的 gen/ 目录中。服务必须视情况实现 IBinder 接口。然后客户端应用便可绑定到该服务,并调用 IBinder 中的方法来执行 IPC。
如需使用 AIDL 创建绑定服务,请执行以下步骤:
创建 .aidl 文件
此文件定义带有方法签名的编程接口。实现接口
Android SDK 工具基于您的 .aidl 文件,使用 Java 编程语言生成一个接口。此接口具有一个名为 Stub 的内部抽象类,用于扩展 Binder 类并实现 AIDL 接口中的方法。您必须扩展 Stub 类并实现方法。向客户端公开该接口
实现 Service 并重写 onBind() 以返回 Stub 类的实现。
注意:在 AIDL 接口首次发布后对其进行的任何更改都必须保持向后兼容性,以避免中断其他应用对您的服务的使用。 也就是说,因为必须将您的 .aidl 文件复制到其他应用,才能让这些应用访问您的服务的接口,因此您必须保留对原始接口的支持。
创建 .aidl 文件
AIDL 使用简单语法,使您能通过可带参数和返回值的一个或多个方法来声明接口。 参数和返回值可以是任意类型,甚至可以是其他 AIDL 生成的接口。
您必须使用 Java 编程语言构建 .aidl 文件。每个 .aidl 文件都必须定义单个接口,并且只需包含接口声明和方法签名。
默认情况下,AIDL 支持下列数据类型:
- Java 编程语言中的所有基本类型(如 int、long、char、boolean 等等)
- String
- CharSequence
- List
List 中的所有元素都必须是以上列表中支持的数据类型、其他 AIDL 生成的接口或您声明的可打包类型。 可选择将 List 用作“通用”类(例如,List)。另一端实际接收的具体类始终是 ArrayList,但生成的方法使用的是 List 接口。 - Map
Map 中的所有元素都必须是以上列表中支持的数据类型、其他 AIDL 生成的接口或您声明的可打包类型。 不支持通用 Map(如 Map<String,Integer> 形式的 Map)。 另一端实际接收的具体类始终是 HashMap,但生成的方法使用的是 Map 接口。 - 实现Parcelable的自定义类
您必须为以上未列出的每个附加类型加入一个 import 语句,即使这些类型是在与您的接口相同的软件包中定义。
定义服务接口时,请注意:
- 方法可带零个或多个参数,返回值或空值。
- 所有非原语(除了Parcelable的类都称原语)参数都需要指示数据走向的方向标记。可以是 in、out 或 inout(见以下示例)。
原语默认为 in,不能是其他方向。
注意:您应该将方向限定为真正需要的方向,因为编组参数的开销极大。
- .aidl 文件中包括的所有代码注释都包含在生成的 IBinder 接口中(import 和 package 语句之前的注释除外)
- 只支持方法;您不能公开 AIDL 中的静态字段。
以下是一个 .aidl 文件示例:
package com.hofo.aidl_service;// 使用import语句在此声明任何非默认类型interface IPerson {String getName();void setName(String name);/*** 演示一些可用作参数的基本类型,并在AIDL中返回值。*/void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat,double aDouble, String aString);
}
只需将您的 .aidl 文件保存在项目的 src/ 目录内,当您开发应用时,SDK 工具会在项目的 gen/ 目录中生成 IBinder 接口文件。生成的文件名与 .aidl 文件名一致,只是使用了 .java 扩展名(例如,IPerson .aidl 生成的文件名是 IPerson .java)。
如果您使用 Android Studio,增量编译几乎会立即生成 Binder 类。 如果您不使用 Android Studio,则 Gradle 工具会在您下一次开发应用时生成 Binder 类 — 您应该在编写完 .aidl 文件后立即用 gradle assembleDebug (或 gradle assembleRelease)编译项目,以便您的代码能够链接到生成的类。
- 实现接口
当您开发应用时,Android SDK 工具会生成一个以 .aidl 文件命名的 .java 接口文件。生成的接口包括一个名为 Stub 的子类,这个子类是其父接口(例如,YourInterface.Stub)的抽象实现,用于声明 .aidl 文件中的所有方法。
注:Stub 还定义了几个帮助程序方法,其中最引人关注的是 asInterface(),该方法带 IBinder(通常便是传递给客户端 onServiceConnected() 回调方法的参数)并返回存根接口实例。 如需了解如何进行这种转换的更多详细信息,请参见调用 IPC 方法一节。
如需实现 .aidl 生成的接口,请扩展生成的 Binder 接口(例如,YourInterface.Stub)并实现从 .aidl 文件继承的方法。
以下是一个使用匿名实例实现名为 IRemoteService 的接口(由以上 IRemoteService.aidl 示例定义)的示例:
IPerson.Stub person = new IPerson.Stub() {public String getName() throws RemoteException {return name;}public void setName(String name) throws RemoteException {PersonService.this.name = name;}public void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat, double aDouble, String aString) throws RemoteException {}
};
现在,mBinder 是 Stub 类的一个实例(一个 Binder),用于定义服务的 RPC 接口。 在下一步中,将向客户端公开该实例,以便客户端能与服务进行交互。
在实现 AIDL 接口时应注意遵守以下这几个规则:
- 由于不能保证在主线程上执行传入调用,因此您一开始就需要做好多线程处理准备,并将您的服务正确地编译为线程安全服务。
- 默认情况下,RPC 调用是同步调用。如果您明知服务完成请求的时间不止几毫秒,就不应该从 Activity 的主线程调用服务,因为这样做可能会使应用挂起(Android 可能会显示“Application is Not Responding”对话框)— 您通常应该从客户端内的单独线程调用服务。
- 您引发的任何异常都不会回传给调用方。
- 向客户端公开该接口
您为服务实现该接口后,就需要向客户端公开该接口,以便客户端进行绑定。 要为您的服务公开该接口,请扩展 Service 并实现 onBind(),以返回一个类实例,这个类实现了生成的 Stub(见前文所述)。以下是一个向客户端公开 IRemoteService 示例接口的服务示例。
public class PersonService extends Service {String name;IPerson.Stub person = new IPerson.Stub() {public String getName() throws RemoteException {return name;}public void setName(String name) throws RemoteException {PersonService.this.name = name;}public void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat, double aDouble, String aString) throws RemoteException {}};public IBinder onBind(Intent intent) {return person;}
}
现在,当客户端(如 Activity)调用 bindService() 以连接此服务时,客户端的 onServiceConnected() 回调会接收服务的 onBind() 方法返回的 mBinder 实例。
客户端还必须具有对 interface 类的访问权限,因此如果客户端和服务在不同的应用内,则客户端的应用 src/ 目录内必须包含 .aidl 文件(它生成 android.os.Binder 接口 — 为客户端提供对 AIDL 方法的访问权限)的副本。
清单注册
需要在清单文件中注册隐世启动意图,否则其他程序可能无法启动服务。
<serviceandroid:name=".service.PersonService"android:enabled="true"android:exported="true"><intent-filter><action android:name="com.hofo.aidlservice.service.PersonService" /></intent-filter></service>
当客户端在 onServiceConnected() 回调中收到 IBinder 时,它必须调用 YourServiceInterface.Stub.asInterface(service) 以将返回的参数转换成 YourServiceInterface 类型。例如:
IPerson iPerson ;Intent service = new Intent("com.hofo.aidlservice.service.PersonService");
service.setPackage("com.hofo.aidlservice");
bindService(service, new ServiceConnection() {@Overridepublic void onServiceConnected(ComponentName name, IBinder service) {iPerson = IPerson.Stub.asInterface(service);}@Overridepublic void onServiceDisconnected(ComponentName name) {iPerson = null;}
}, BIND_AUTO_CREATE);
通过 IPC 传递对象
通过 IPC 接口把某个类从一个进程发送到另一个进程是可以实现的。 不过,您必须确保该类的代码对 IPC 通道的另一端可用,并且该类必须支持 Parcelable 接口。支持 Parcelable 接口很重要,因为 Android 系统可通过它将对象分解成可编组到各进程的原语。
如需创建支持 Parcelable 协议的类,您必须执行以下操作:
- 让您的类实现 Parcelable 接口。
- 实现 writeToParcel,它会获取对象的当前状态并将其写入 Parcel。
- 为您的类添加一个名为 CREATOR 的静态字段,这个字段是一个实现 Parcelable.Creator 接口的对象。
- 最后,创建一个声明可打包类的 .aidl 文件(按照下文 Rect.aidl 文件所示步骤)。
如果您使用的是自定义编译进程,切勿在您的编译中添加 .aidl 文件。 此 .aidl 文件与 C 语言中的头文件类似,并未编译。
AIDL 在它生成的代码中使用这些方法和字段将您的对象编组和取消编组。
例如,以下这个 Eye.aidl 文件可创建一个可打包的 Eye类:
package com.hofo.aidl_service;
// 声明Eye,以便AIDL可以找到它并知道它实现了parcelable协议。
parcelable Eye;
以下示例展示了 Eye类如何实现 Parcelable 协议。
import android.os.Parcel;
import android.os.Parcelable;public final class Eye implements Parcelable {public static final Creator<Eye> CREATOR = new Creator<Eye>() {@Overridepublic Eye createFromParcel(Parcel in) {return new Eye(in);}@Overridepublic Eye[] newArray(int size) {return new Eye[size];}};String eyeColor;public Eye(String eyeColor) {this.eyeColor = eyeColor;}protected Eye(Parcel in) {eyeColor = in.readString();}@Overridepublic int describeContents() {return 0;}@Overridepublic void writeToParcel(Parcel dest, int flags) {dest.writeString(eyeColor);}public String getEyeColor() {return eyeColor;}public void setEyeColor(String eyeColor) {this.eyeColor = eyeColor;}
}
Eye类中的编组相当简单。看一看 Parcel 上的其他方法,了解您可以向 Parcel 写入哪些其他类型的值。
调用 IPC 方法
调用类必须执行以下步骤,才能调用使用 AIDL 定义的远程接口:
- 在项目 src/ 目录中加入 .aidl 文件。
- 声明一个 IBinder 接口实例(基于 AIDL 生成)。
- 实现 ServiceConnection。
- 调用 Context.bindService(),以传入您的 ServiceConnection 实现。
- 在您的 onServiceConnected() 实现中,您将收到一个 IBinder 实例(名为 service)。调用 YourInterfaceName.Stub.asInterface((IBinder)service),以将返回的参数转换为 YourInterface 类型。
- 调用您在接口上定义的方法。您应该始终捕获 DeadObjectException 异常,它们是在连接中断时引发的;这将是远程方法引发的唯一异常。
- 如需断开连接,请使用您的接口实例调用 Context.unbindService()。
有关调用 IPC 服务的几点说明:
- 对象是跨进程计数的引用。
- 您可以将匿名对象作为方法参数发送。
Android Studio AIDL文件定义的两种方式
第一种方式:默认目录:src\mian\aidl
第二种方式:配置build.gradle
- 把 adil 文件拷贝到libs文件夹下
- 在build.gradle文件中配置
android {...sourceSets {main {aidl.srcDirs = ['libs/aidl']//你要配置的路径}}...
}
好了,关于四大组件之Service系列的内容就以上几点了。
友情链接
Android四大组件之Service详解
Android四大组件之绑定Service详解
Android四大组件Service之AIDL详解
代码示例:
本片文章的源码(点我!点我!点我)
Android四大组件Service之AIDL详解相关推荐
- Android四大组件-Service
目录 启动方式 独立启动 使用场景 生命周期 绑定启动 使用场景 生命周期 特性 多次启动 版本适配 5.0(21) 8.0(26) 9.0(28) 12(31) 后台启动服务思路 广播启动 省电白名 ...
- android studio四大组件,Android 四大组件 Service 完全理解
瞎忙到头晕... Service主要使用在后台比较耗时和运行时间较长的逻辑.但是在service中使用 Thread.currentThread().getId() 获取得到的却是1,也就是主线程.如 ...
- Android 四大组件 Service
1. 适用范围 Service主要使用在后台比较耗时和运行时间较长的逻辑.但是在service中使用 Thread.currentThread().getId() 获取得到的却是1,也就是主线程.如果 ...
- Service与AIDL详解
Service是android中的服务组件, 经常用来执行一些运行在后台的耗时操作. 使用一个Service需要继承Service类, 并根据需要重写生命周期方法. Service的生命周期如下: p ...
- android组件启动,Android四大组件Service之StartService启动
对于Service两种方式在概述里已经介绍了,下面是学习是StartService的代码实例. service启动代码: package com.example.service01; import a ...
- Android四大组件——Service
前言 Service可以理解为没有布局的Activity,可以进行音乐播放,后台下载等操作. 注意:Service是运行于主线程中的,不能进行耗时操作. 如何创建一个Service Service从创 ...
- Android Jetpack组件之数据库Room详解(三)
本文涉及Library的版本如下: androidx.room:room-runtime:2.1.0-alpha03 androidx.room:room-compiler:2.1.0-alpha03 ...
- android线性布局设置控件固定在底部,Android UI组件LinearLayout线性布局详解
LinearLayout 线性布局,该布局的继承关系: 1. 什么是线性布局 通俗的说感觉起来和线有关,参照线的特点,有么是横向的,要么是竖向的. LinearLayout是线性布局控件,它包含的子控 ...
- Android Jetpack组件之数据库Room详解(二)
本文涉及Library的版本如下: androidx.room:room-runtime:2.1.0-alpha03 androidx.room:room-compiler:2.1.0-alpha03 ...
最新文章
- pat1011-1020
- cocos2d-x游戏实例(17)-纵版射击游戏(4)
- 【招聘(北京武汉)】北京高远华信科技 .NET 高级工程师
- leetcode 1370. 上升下降字符串
- [比赛]2015/12/25BNU新生赛
- MyEclipse内存不足配置
- 【转载】Python tips: 什么是*args和**kwargs?
- git添加远程库遇到的问题
- B2C电子商务开发的网店管理系统
- 分享一下我在东方时尚学车的经历
- 【vue】imitate-beautiful-thing
- 解决Eclipse保存web.xml卡的问题
- ODM、JDM、OEM、OBM
- 火狐的可配置信息(about:config)
- Docker部署服务(二)上传镜像至Habor
- 离散数学:n元素上的各种关系数目推导
- autojs零散知识点-循环找图
- SSM项目————整合微信支付
- 【每天一个Linux命令】09. Linux中chown的用法
- 模糊控制洗衣机MATLAB模型,基于matlab的洗衣机模糊控制仿真