IT技术博客大学习 共学习 共进步

Android中AIDL详细分析

Android相关技术 2016-03-22 16:24:00 累计浏览 1,202 次
本机暂存
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所示

PersonInformationServicePersonInformationClient

图1 案例app源码结构图

左边是服务端,右边是客户端,对比红色方框内的内容,aidl包中的文件结构都是一样的。

案例源码下载地址:http://yunpan.cn/cLippY2ib7RHH  访问密码 9ebb
a. 创建接口(创建aidl文件)

创建服务端IPersonInformation.aidl文件,包含一个displayInformation方法,显示个人的姓名和年龄

packagecom.example.person.aidl;
import com.example.person.aidl.Person;
 
interfaceIPersonInformation
{
     StringdisplayInformation(inPerson requester);
}

接口前面不能加访问权限修饰符public、private等。因为displayInformation传递的参数是对象,属于非基本数据类型,需要加上in、out、inout修饰符,其含义是:

如果在客户端输入本地数据,服务端接受数据,就用in修饰符;

如果在客户端接收数据,服务端把本地修改后的数据向客户端传输,就用out修饰符;

如果同时满足in和out,就用inout修饰。

本文中定义的接口displayInformation从客户端接收数据,在服务端组合后返回给客户端,客户端相当于输入,服务端是输出,用in修饰符,即:

StringdisplayInformation(inPerson requester);

除了原始基本数据类型(int, long, char, boolean等)、String 、CharSequence以及包含基本数据类型的list和map外,都需要用import语句把对象引用过来,因为person是对象类型,因此用import引入。

一般情况下,Android中远程接口类型名称都用大写的I开头加上接口的名字,这并不是硬性要求,但为了维护程序风格,这样写可读性较高。因为aidl接口都实现IInterface,字母前缀I的含义是IInterface类,表示这是一个可以提供远程服务的类。

b. 实现接口

packagecom.example.person.service;
 
// This file is StockQuoteService2.java
 
import com.example.person.R;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
import android.os.RemoteException;
 
import com.example.person.activity.TestActivity;
import com.example.person.aidl.Person;
import com.example.person.aidl.IPersonInformation;
 
publicclassPersonInformationServiceextendsService{
privateNotificationManager notificationMgr;
 
publicclassIPersonInformationImpl extendsIPersonInformation.Stub{
publicStringdisplayInformation(Person person)
throwsRemoteException{
return"Hello "+person.getName()+"! Your age is: "
+person.getAge();
}
}
 
@Override
publicvoidonCreate(){
super.onCreate();
 
notificationMgr=(NotificationManager)getSystemService(NOTIFICATION_SERVICE);
 
displayNotificationMessage("onCreate() called in StockQuoteService2");
}
 
@Override
publicvoidonDestroy(){
displayNotificationMessage("onDestroy() called in StockQuoteService2");
// Clear all notifications from this service
notificationMgr.cancelAll();
super.onDestroy();
}
 
publicintonStartCommand(Intent intent,intflags,intstartId){
returnsuper.onStartCommand(intent,flags,startId);
}
 
@Override
publicIBinder onBind(Intent intent){
displayNotificationMessage("onBind() called in StockQuoteService2");
returnnewIPersonInformationImpl();
}
 
privatevoiddisplayNotificationMessage(Stringmessage){
Notification notification=newNotification(R.drawable.emo_im_happy,
message,System.currentTimeMillis());
 
PendingIntent contentIntent=PendingIntent.getActivity(this,0,
newIntent(this,TestActivity.class),0);
 
notification.setLatestEventInfo(this,"StockQuoteService2",message,
contentIntent);
 
notification.flags=Notification.FLAG_NO_CLEAR;
 
notificationMgr.notify(R.id.app_notification_id,notification);
}
}

主要实现核心代如下:

publicclassIPersonInformationImpl extendsIPersonInformation.Stub{
publicStringdisplayInformation(Person person)
throwsRemoteException{
return"Hello "+person.getName()+"! Your age is: "
+person.getAge();
}
}

实现类名为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接口。

packagecom.example.person.aidl;
 
import android.os.Parcel;
import android.os.Parcelable;
 
publicclassPersonimplementsParcelable{
 
privateintage;
privateStringname;
publicstaticfinalParcelable.Creator<Person>CREATOR=
newParcelable.Creator<Person>(){
        publicPerson createFromParcel(Parcel in){
            returnnewPerson(in);
        }
 
        publicPerson[]newArray(intsize){
            returnnewPerson[size];
        }
    };
    publicPerson(){
    }
 
privatePerson(Parcel in){
readFromParcel(in);
}
 
@Override
publicintdescribeContents(){
// TODO Auto-generated method stub
return0;
}
 
@Override
publicvoidwriteToParcel(Parcel out,intflags){
// TODO Auto-generated method stub
        out.writeInt(age);
        out.writeString(name);
}
 
publicvoidreadFromParcel(Parcel in){
        age=in.readInt();
        name=in.readString();
}
 
    publicintgetAge(){
        returnage;
    }
 
    publicvoidsetAge(intage){
        this.age=age;
    }
 
    publicStringgetName(){
        returnname;
    }
 
    publicvoidsetName(Stringname){
        this.name=name;
    }
}

Parcelable接口必须实现三个方法:

Parcelable.Creator // 用来产生Person对象

describeContents // 返回特殊对象类型,一般返回0

writeToParcel // 把数据写入到Parcel对象中,Parcel中文含义为包裹,形象地表明先把数据包装好,等待传输,在接收方接收后再解压包裹从中得到数据,起到安全性作用。

IPersonInformation.aidl在编译时需要用到待序列化对象Person,因此还需要创建Person.aidl文件,Person类没有具体方法,代码相对简单:

packagecom.example.person.aidl;
parcelable Person;

Person类前加parcelable修饰符,表示这是一个待序列化对象。

在eclipse中编译会自动生成IPersonInformation.java,位于gen目录下,这样服务端文件创建成功。

c. 获取接口

客户端要获取服务端接口,先把aidl目录整体拷贝到客户端,如图2所示:

aidl文件

图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);
}
}
  
}

核心代码为:

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;
}
};

bindService(newIntent("com.example.person.PersonInformationService"),serviceConnection,
Context.BIND_AUTO_CREATE);

单击绑定按钮时调用binderservice把serviceConnection与PersonInformationService服务端绑定,当bindservice绑定成功后,系统会调用onServiceConnected方法,第二个参数service是一个IBinder接口对象,由服务端onBind方法返回给客户端的,IPersonInformation.Stub的asInterface返回一个客户端代理对象proxy,赋值给IPersonInformation对象,这样,客户端就获得了服务端接口。

d. 使用服务

既然客户端已经获得了服务端的接口对象,那么就可以调用服务端的方法来得到服务。

Person person=newPerson();
person.setAge(32);
person.setName("zhulf");
Stringresponse=iPersonInformation.displayInformation(person);

先设置person的内容,而后采用服务端对象iPersonInformation调用服务端的方法displayInformation来获得所需要的数据,这可以形象地描述成客户端得到了服务端的服务!

把生成的服务端、客户端apk安装后,打开客户端,如图3所示,点击bind按钮后显示如图4,客户端设置了姓名”jack”,年龄”100″已经通过远程服务接口displayInformation显示出来了。

bind前

图3 bind按钮点击前

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方法的作用。

iPersonInformation=IPersonInformation.Stub.asInterface(service);


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);
}

queryLocalInterface返回IInterface接口对象,再判断该对象实际类型是不是IPersonInformation类型,如果是就返回该对象,否则就new一个Proxy对象。参数obj是一个binderProxy对象,其queryLocalInterface方法源码为:

publicIInterface queryLocalInterface(Stringdescriptor){
        returnnull;
}

该方法什么都没做,直接返回空值,那么iin为空,跳过了下面的if语句:

if(((iin!=null)&&(iin instanceofcom.example.person.aidl.IPersonInformation))){
return((com.example.person.aidl.IPersonInformation)iin);
}

继续执行:

returnnewcom.example.person.aidl.IPersonInformation.Stub.Proxy(obj);

创建一个包含binderProxy对象的Proxy对象。

asInterface方法的主要作用:如果是多进程操作,参数obj是binderProxy对象,就new一个proxy接口;如果是当前进程操作,obj是本地binder对象,就返回本地binder对象,这种情况不存在多进程通信。

Proxy类构造方法为:

Proxy(android.os.IBinder remote){
mRemote=remote;
}

把binderProxy对象保存到mRemote变量,mRemote在aidl中是一个辅助变量。

这样就获得了客户端代理接口proxy。

c. 通过代理接口获得服务

标题叫“通过代理接口获得服务”,意思是proxy调用displayInformation方法获得结果的过程,当系统执行到:

Stringresponse=iPersonInformation.displayInformation(person);

iPersonInformation变量保存了proxy对象,displayInformation方法的“中间实现”情况为:

@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;
}

之所以叫中间实现,是因为displayInformation的最终实现代码在IPersonInformationImpl中,此处只是中间状态。静态方法obtain从Parcel池中获得一个Parcel对象分别保存到_data、_reply中,_data、_reply分别是发送数据和接受数据时的承载器、包裹。writeInterfaceToken写入一个字符串标记,writeInt写入一个标志位,writeToParcel把Person对象中的数据写入到parcel中,然后执行:

mRemote.transact(Stub.TRANSACTION_displayInformation,_data,_reply,0);

客户端对象mRemote(就是binderProxy)调用transact方法向服务端发送请求,第一个参数就是请求命令号,服务端根据这个命令号找到相应的执行方法。

transact方法进入到C++层,交由BpBinder处理,最终经过binder驱动处理后,转交给binder服务端,服务端层层调用到java层,由java层的服务端对象stub的onTransact方法处理:

@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);
}

根据客户端发送请求的命令号TRANSACTION_displayInformation找到case子句,首先由enforceInterface方法读出客户端写入的字符串,验证是否正确。readInt读出标志位,如果是1,就重新组建序列化的Person对象保存到_arg0中。

java.lang.String_result=this.displayInformation(_arg0);

this表示当前正在运行的的对象,就是stub对象,在服务端IPersonInformationImpl实现了stub,因此,this就是IPersonInformationImpl对象,进程执行到了:

publicStringdisplayInformation(Person person)throwsRemoteException{
return"Hello "+person.getName()+"! Your age is: "+person.getAge();
}

这个方法就是返回一段字符串,保存到_result变量中。

reply.writeString(_result);

writeString方法把结果存到parcel对象中,再次通过进程间通信机制,穿过Binder驱动,C++层,返回到客户端的transact中,客户端从返回的parcel对象中读出返回值,最终返回到PersonClientActivity的这句话:

Stringresponse=iPersonInformation.displayInformation(person);

就是把结果保存到response里面。

这就完成了客户端、服务端进程全部通信过程,整个过程用图5来表示:

aidl通信机制流程图

图5 aidl基本通信过程

途中双向箭头表示数据双向传输,发送到服务端后,服务端把处理后的数据又返回给客户端。

aild可以说是应用层为了实现两个不同应用程序通信来设计的,用户设计统一的接口方法,然后由系统自动生成java文件,并提供了服务端和客户端代理,客户端通过代理来访问服务端。

建议继续学习

  1. 无锁消息队列 (累计阅读 14,202)
  2. 玩转Protocol Buffers (累计阅读 6,560)
  3. 解开 phprpc 序列化性能高于 hessian 的秘密 (累计阅读 5,263)
  4. Nginx的master和worker进程间的通信 (累计阅读 4,740)
  5. 对protostuff和java序列化的小测试 (累计阅读 4,540)
  6. Proto Buffers in Lua (累计阅读 4,341)
  7. PHP 序列化与 .NET 中其它方式序列化的效率对比 (累计阅读 3,380)
  8. python与c-跨语言级别的进程间通信 (累计阅读 3,361)
  9. 关于PHP加速器APC的使用 (累计阅读 3,201)
  10. C 语言的数据序列化 (累计阅读 3,141)