Android中AIDL详细分析
来源:互联网 发布:圆方软件视频教程 编辑:程序博客网 时间:2024/05/16 07:54
AIDL是什么
AIDL英文全称Android Interface Definition Language,中文Android接口定义语言,在Android中,AIDL定义了程序访问接口,并将对象进行序列化,通过该接口,使得进程间采用IPC(进程间通信机制,比如binder)进行交互、传输数据。
AIDL应用场合
a. 在不同应用程序之间,如果客户端需要访问服务端,并且想要处理多线程任务时,采用AIDL;
b. 如果不需要进行跨进程通信,就可以不使用AIDL,比如绑定本地服务,只需继承binder,在
onBind中返回一个binder对象即可;
c. 如果要跨进程通信,但又不需要多线程操作,就使用Messenger方式,实际上Messenger也是基于AIDL的,
只是把AIDL作为底层使用了。Messenger方式中所有的消息存放在一个队列中,每次只允许一个消息待处理。
如果想让服务能够处理多个请求,就使用AIDL。
AIDL和bindservice(又称为bound services)之间的关系
bindservice一般分为两种情况:绑定本地服务;绑定远程服务。
本地服务就是同一个进程中即同一个应用程序中,绑定远程服务指不同进程中,即不同应用程序中。
AIDL一般用在绑定远程服务中,因为这涉及到跨进程操作,需要通过AIDL定义统一接口,传输序列化数据。
注意:大部分应用程序不应该用AIDL来创建bindservice服务,因为多线程处理,让代码变得复杂,不易维护,特殊情况下才使用AIDL。(参考Android官方文档)
创建AIDL服务分为三步:
创建.aidl文件
实现aidl接口
客户端获取接口
为了更加清晰地描述这个过程,本文给出一个实例辅助分析。
案例
定义一个aidl接口displayInformation,用来显示个人的姓名和年龄信息
先看建立好的目录结构如图1所示
图1 案例app源码结构图
左边是服务端,右边是客户端,对比红色方框内的内容,aidl包中的文件结构都是一样的。
案例源码下载地址:http://yunpan.cn/cLippY2ib7RHH 访问密码 9ebb
a. 创建接口(创建aidl文件)
创建服务端IPersonInformation.aidl文件,包含一个displayInformation方法,显示个人的姓名和年龄
接口前面不能加访问权限修饰符public、private等。因为displayInformation传递的参数是对象,属于非基本数据类型,需要加上in、out、inout修饰符,其含义是:
如果在客户端输入本地数据,服务端接受数据,就用in修饰符;
如果在客户端接收数据,服务端把本地修改后的数据向客户端传输,就用out修饰符;
如果同时满足in和out,就用inout修饰。
本文中定义的接口displayInformation从客户端接收数据,在服务端组合后返回给客户端,客户端相当于输入,服务端是输出,用in修饰符,即:
除了原始基本数据类型(int, long, char, boolean等)、String 、CharSequence以及包含基本数据类型的list和map外,都需要用import语句把对象引用过来,因为person是对象类型,因此用import引入。
一般情况下,Android中远程接口类型名称都用大写的I开头加上接口的名字,这并不是硬性要求,但为了维护程序风格,这样写可读性较高。因为aidl接口都实现IInterface,字母前缀I的含义是IInterface类,表示这是一个可以提供远程服务的类。
b. 实现接口
主要实现核心代如下:
实现类名为IPersonInformationImpl,继承了IPersonInformation.Stub,IPersonInformation.Stub是一个binder接口,如果在eclipse中建立好aidl文件后,编译时会自动生成与aidl同名的java文件IPersonInformation.java,displayInformation为实现的方法。注意,该接口需要Person类,因此还必须把perons类接口文件import进来才能自动生成IPersonInformation.java,下文会提到Person类。
因为传输的对象为Person,非基本数据类型,需要对其序列化,Android中用Parcelable接口来实现,之所以用Parcelable接口,是因为更快、性能更好,Android中进程间通信中大量使用Parcelable接口。
Parcelable接口必须实现三个方法:
Parcelable.Creator // 用来产生Person对象
describeContents // 返回特殊对象类型,一般返回0
writeToParcel // 把数据写入到Parcel对象中,Parcel中文含义为包裹,形象地表明先把数据包装好,等待传输,在接收方接收后再解压包裹从中得到数据,起到安全性作用。
IPersonInformation.aidl在编译时需要用到待序列化对象Person,因此还需要创建Person.aidl文件,Person类没有具体方法,代码相对简单:
Person类前加parcelable修饰符,表示这是一个待序列化对象。
在eclipse中编译会自动生成IPersonInformation.java,位于gen目录下,这样服务端文件创建成功。
c. 获取接口
客户端要获取服务端接口,先把aidl目录整体拷贝到客户端,如图2所示:
图2 aidl目录结构
包名com.example.person.aidl也要一致,这个报名和AndroidManifest的包名不是一回事,前者就是文件目录,后者是APP的包名。
创建PersonClientActivity,继承于Activity:
packagecom.example.personclient.activity;
import
com.example.person.aidl.Person;
import
com.example.personclient.R;
import
com.example.person.aidl.IPersonInformation;
import
android.app.Activity;
import
android.content.ComponentName;
import
android.content.Context;
import
android.content.Intent;
import
android.content.ServiceConnection;
import
android.os.Bundle;
import
android.os.IBinder;
import
android.os.RemoteException;
import
android.util.Log;
import
android.view.View;
import
android.view.View.OnClickListener;
import
android.widget.Button;
import
android.widget.Toast;
publicclassPersonClientActivityextendsActivity{
protectedstaticfinalStringTAG=
"StockQuoteClient2"
;
privateIPersonInformation iPersonInformation=
null
;
privateButton bindBtn;
privateButton callBtn;
privateButton unbindBtn;
privateServiceConnection serviceConnection=newServiceConnection(){
@Override
publicvoidonServiceConnected(ComponentName name,IBinder service){
Log.v(TAG,
"---------------service: "
+service.getClass().toString());
iPersonInformation=IPersonInformation.Stub.asInterface(service);
Log.v(TAG,
"---------------stockService: "
+iPersonInformation.getClass().toString());
callService();
}
@Override
publicvoidonServiceDisconnected(ComponentName name){
iPersonInformation=
null
;
}
};
/** Called when the activity is first created. */
@Override
publicvoidonCreate(Bundle savedInstanceState){
super
.onCreate(savedInstanceState);
setContentView(R.layout.main);
bindBtn=(Button)findViewById(R.id.bindBtn);
bindBtn.setOnClickListener(buttonClickListener);
callBtn=(Button)findViewById(R.id.callBtn);
callBtn.setOnClickListener(buttonClickListener);
callBtn.setEnabled(
false
);
unbindBtn=(Button)findViewById(R.id.unbindBtn);
unbindBtn.setOnClickListener(buttonClickListener);
unbindBtn.setEnabled(
false
);
}
OnClickListener buttonClickListener=newOnClickListener(){
@Override
publicvoidonClick(Viewv){
// TODO Auto-generated method stub
if
(v.getId()==R.id.bindBtn){
bindService(newIntent(
"com.example.person.PersonInformationService"
),serviceConnection,
Context.BIND_AUTO_CREATE);
bindBtn.setEnabled(
false
);
callBtn.setEnabled(
true
);
unbindBtn.setEnabled(
true
);
}elseif(v.getId()==R.id.callBtn){
callService();
}elseif(v.getId()==R.id.unbindBtn){
unbindService(serviceConnection);
bindBtn.setEnabled(
true
);
callBtn.setEnabled(
false
);
unbindBtn.setEnabled(
false
);
}
}
};
privatevoidcallService(){
try
{
Person person=newPerson();
person.setAge(
32
);
person.setName(
"zhulf"
);
Stringresponse=iPersonInformation.displayInformation(person);
Toast.makeText(PersonClientActivity.
this
,
"Value get from service is: "
+response,
Toast.LENGTH_SHORT).show();
}
catch
(RemoteException ee){
Log.e(
"MainActivity"
,ee.getMessage(),ee);
}
}
}
核心代码为:
单击绑定按钮时调用binderservice把serviceConnection与PersonInformationService服务端绑定,当bindservice绑定成功后,系统会调用onServiceConnected方法,第二个参数service是一个IBinder接口对象,由服务端onBind方法返回给客户端的,IPersonInformation.Stub的asInterface返回一个客户端代理对象proxy,赋值给IPersonInformation对象,这样,客户端就获得了服务端接口。
d. 使用服务
既然客户端已经获得了服务端的接口对象,那么就可以调用服务端的方法来得到服务。
先设置person的内容,而后采用服务端对象iPersonInformation调用服务端的方法displayInformation来获得所需要的数据,这可以形象地描述成客户端得到了服务端的服务!
把生成的服务端、客户端apk安装后,打开客户端,如图3所示,点击bind按钮后显示如图4,客户端设置了姓名”jack”,年龄”100″已经通过远程服务接口displayInformation显示出来了。
图3 bind按钮点击前
图4 bind按钮点击后
点击CALL AGAIN按钮后重复显示toast,点击UNBIND按钮后停止服务。
AIDL服务的详细执行过程
a. PersonInformationService启动过程
bindService通过Intent的action——”com.example.person.PersonInformationService”启动PersonInformationService服务,服务启动成功后由onBind返回一个IPersonInformationImpl对象,IPersonInformationImpl继承了IPersonInformation.Stub,IPersonInformation.Stub又继承了Binder,Binder实现了IBinder接口,因此,onBind返回的是一个IBinder接口对象,通过添加Logcat打印发现实际返回的是一个BinderProxy对象,这是什么原因?
这是binder进程间通信机制所决定的,服务启动后,系统会生成一个binder代理对象binderProxy,用作桥梁,与C++层的BpBinder对应,在aidl服务中通过binderProxy生成prox客户端代理对象。
b. 客户端获取代理接口
上文提到binderProxy用来生成proxy对象,如何生成?上文提到aidl文件在编译时会自动生成java文件,即IPersonInformation.java,源码:
/*
* This file is auto-generated. DO NOT MODIFY.
* Original file: E:\\demos_from_internet\\PersonInformationClient\\PersonInformationClient\\src\\com\\example\\person\\aidl\\IPersonInformation.aidl
*/
packagecom.example.person.aidl;
publicinterfaceIPersonInformation extendsandroid.os.IInterface{
/** Local-side IPC implementation stub class. */
publicstaticabstractclassStub extendsandroid.os.Binder implementscom.example.person.aidl.IPersonInformation{
privatestaticfinaljava.lang.StringDESCRIPTOR="com.example.person.aidl.IPersonInformation";
/** Construct the stub at attach it to the interface. */
publicStub(){
this.attachInterface(this,DESCRIPTOR);
}
/**
* Cast an IBinder object into an
* com.example.person.aidl.IPersonInformation interface, generating a
* proxy if needed.
*/
publicstaticcom.example.person.aidl.IPersonInformation asInterface(android.os.IBinder obj){
if
((obj==
null
)){
returnnull;
}
android.os.IInterface iin=obj.queryLocalInterface(DESCRIPTOR);
if
(((iin!=
null
)&&(iin instanceofcom.example.person.aidl.IPersonInformation))){
return
((com.example.person.aidl.IPersonInformation)iin);
}
returnnewcom.example.person.aidl.IPersonInformation.Stub.Proxy(obj);
}
@Override
publicandroid.os.IBinder asBinder(){
returnthis;
}
@Override
publicbooleanonTransact(intcode,android.os.Parcel data,android.os.Parcel reply,intflags)
throws
android.os.RemoteException{
switch
(code){
caseINTERFACE_TRANSACTION:{
reply.writeString(DESCRIPTOR);
returntrue;
}
caseTRANSACTION_displayInformation:{
data.enforceInterface(DESCRIPTOR);
com.example.person.aidl.Person _arg0;
if
((
0
!=data.readInt())){
_arg0=com.example.person.aidl.Person.CREATOR.createFromParcel(data);
}
else
{
_arg0=
null
;
}
java.lang.String_result=
this
.displayInformation(_arg0);
reply.writeNoException();
reply.writeString(_result);
returntrue;
}
}
returnsuper.onTransact(code,data,reply,flags);
}
privatestaticclassProxy implementscom.example.person.aidl.IPersonInformation{
privateandroid.os.IBinder mRemote;
Proxy(android.os.IBinder remote){
mRemote=remote;
}
@Override
publicandroid.os.IBinder asBinder(){
returnmRemote;
}
publicjava.lang.StringgetInterfaceDescriptor(){
returnDESCRIPTOR;
}
@Override
publicjava.lang.StringdisplayInformation(com.example.person.aidl.Person requester)
throws
android.os.RemoteException{
android.os.Parcel _data=android.os.Parcel.obtain();
android.os.Parcel _reply=android.os.Parcel.obtain();
java.lang.String_result;
try
{
_data.writeInterfaceToken(DESCRIPTOR);
if
((requester!=
null
)){
_data.writeInt(
1
);
requester.writeToParcel(_data,
0
);
}
else
{
_data.writeInt(
0
);
}
mRemote.transact(Stub.TRANSACTION_displayInformation,_data,_reply,
0
);
_reply.readException();
_result=_reply.readString();
}
finally
{
_reply.recycle();
_data.recycle();
}
return_result;
}
}
staticfinalintTRANSACTION_displayInformation=(android.os.IBinder.FIRST_CALL_TRANSACTION+
0
);
}
publicjava.lang.StringdisplayInformation(com.example.person.aidl.Person requester)
throws
android.os.RemoteException;
}
文件自动生成后,系统分别创建两个对象:服务端Stub、客户端Proxy。服务端、客户端分别都由这两个对象代理操作,那客户端Proxy如何获得?这就是asInterface方法的作用。
queryLocalInterface返回IInterface接口对象,再判断该对象实际类型是不是IPersonInformation类型,如果是就返回该对象,否则就new一个Proxy对象。参数obj是一个binderProxy对象,其queryLocalInterface方法源码为:
该方法什么都没做,直接返回空值,那么iin为空,跳过了下面的if语句:
继续执行:
创建一个包含binderProxy对象的Proxy对象。
asInterface方法的主要作用:如果是多进程操作,参数obj是binderProxy对象,就new一个proxy接口;如果是当前进程操作,obj是本地binder对象,就返回本地binder对象,这种情况不存在多进程通信。
Proxy类构造方法为:
把binderProxy对象保存到mRemote变量,mRemote在aidl中是一个辅助变量。
这样就获得了客户端代理接口proxy。
c. 通过代理接口获得服务
标题叫“通过代理接口获得服务”,意思是proxy调用displayInformation方法获得结果的过程,当系统执行到:
iPersonInformation变量保存了proxy对象,displayInformation方法的“中间实现”情况为:
之所以叫中间实现,是因为displayInformation的最终实现代码在IPersonInformationImpl中,此处只是中间状态。静态方法obtain从Parcel池中获得一个Parcel对象分别保存到_data、_reply中,_data、_reply分别是发送数据和接受数据时的承载器、包裹。writeInterfaceToken写入一个字符串标记,writeInt写入一个标志位,writeToParcel把Person对象中的数据写入到parcel中,然后执行:
客户端对象mRemote(就是binderProxy)调用transact方法向服务端发送请求,第一个参数就是请求命令号,服务端根据这个命令号找到相应的执行方法。
transact方法进入到C++层,交由BpBinder处理,最终经过binder驱动处理后,转交给binder服务端,服务端层层调用到java层,由java层的服务端对象stub的onTransact方法处理:
根据客户端发送请求的命令号TRANSACTION_displayInformation找到case子句,首先由enforceInterface方法读出客户端写入的字符串,验证是否正确。readInt读出标志位,如果是1,就重新组建序列化的Person对象保存到_arg0中。
this表示当前正在运行的的对象,就是stub对象,在服务端IPersonInformationImpl实现了stub,因此,this就是IPersonInformationImpl对象,进程执行到了:
这个方法就是返回一段字符串,保存到_result变量中。
writeString方法把结果存到parcel对象中,再次通过进程间通信机制,穿过Binder驱动,C++层,返回到客户端的transact中,客户端从返回的parcel对象中读出返回值,最终返回到PersonClientActivity的这句话:
就是把结果保存到response里面。
这就完成了客户端、服务端进程全部通信过程,整个过程用图5来表示:
图5 aidl基本通信过程
途中双向箭头表示数据双向传输,发送到服务端后,服务端把处理后的数据又返回给客户端。
aild可以说是应用层为了实现两个不同应用程序通信来设计的,用户设计统一的接口方法,然后由系统自动生成java文件,并提供了服务端和客户端代理,客户端通过代理来访问服务端。
- Android中AIDL详细分析
- android中AIDL机制分析
- android中service和aidl详细整理
- android中service和aidl详细整理
- android中service和aidl详细整理
- android中service和aidl详细整理
- android中service和aidl详细整理
- Android AIDL分析
- Android AIDL分析
- Android AIDL源码分析
- Android AIDL分析
- android aidl文件分析
- Android AIDL简单分析
- android中AIDL
- android中使用AIDL
- android中AIDL
- Android中aidl基础
- Android中AIDL
- 【Lua】不进位保留小数点X位数
- cf#276-B - Maximum Value- (数学+暴力)/(二分)
- Android中的 mvp 开发模式
- dataguard的启动与关闭
- Qt creator的"纯c/c++项目"输出中文乱码的问题
- Android中AIDL详细分析
- 《老罗的Android之旅》导读PPT
- 线段树延迟标记精讲
- caffe adaboost
- JAVA版微信支付V3-完全版
- Java获得某目录下文件总大小
- ionic <ion-content>标签
- Configuration -Properties 文件 修改配置文件自动加载
- 计算机(Linux系统)启动流程