1.引言
打开Android源码,会发现在有些包的里面,在各个java类下面总是会有若干白色图标的文件,后缀为aidl。双击打开,也是一篇黑白,没有java代码的关键字变色突示,写法似java又总有些不同。这个文件好生怪异!
它到底是什么?有什么作用?怎么使用?
2.释义
AIDL(Android Interface Definition Language,Android接口定义语言)。
很多文章在引出AIDL之前,都会问“Android进程间如何通信?”,然后说出“是的,用AIDL的方式可以做到”。可是,每遇到这里脑中便会弹出一个问题“什么时候进程之间需要相互通信?”可能是碍于编程经验还很不足,才会问出后面这个问题。不过,我倒是真的很想知道这一点先。
2.1进程间通信
那么先认识进程间通信。随着人们对应用程序的要求越来越高,单进程应用在许多场合已不能满足人们的要求。编写多进程/多线程程序成为现代程序设计的一个重要特点,在多进程程序设计中,进程间的通信是不可避免的。
进程间通信就是在不同进程之间传播或交换信息,那么不同进程之间存在着什么双方都可以访问的介质呢?进程的用户空间是互相独立的,一般而言是不能互相访问的,唯一的例外是共享内存区。但是,系统空间却是”公共场所”,所以内核显然可以提供这样的条件。除此以外,那就是双方都可以访问的外设了。在这个意义上,两个进程当然也可以通过磁盘上的普通文件 交换信息,或者通过”注册表 “或其它数据库中的某些表项和记录交换信息。广义上这也是进程间通信的手段,但是一般都不把这算作”进程间通信”。因为那些通信手段的效率太低了,而人们对进程间通信的要求是要有一定的实时性。
进程间通信主要包括管道,系统IPC(包括消息队列,信号量,共享存储),SOCKET。
2.2AIDL服务
Java中不允许跨进程内存共享。因此传递对象,只能把对象拆分成操作系统能理解的简单形式,以达到跨界对象访问的目的。在Android系统中,采用了远程过程调用(Remote Procedure Call,RPC)方式来实现。与很多其他的基于RPC的解决方案已于,Android使用一种借口定义语言(Interface Definition Language,IDL)来公开服务的接口。因此,可以将这种跨进程访问的服务称为AIDL(Android Interface Definition Language)服务。
编译器可以通过AIDL文件生成一段代码,通过预先定义的接口达到两个进程内部通信的目的。如果需要在一个Activity中,访问另一个Service中的某个对象,需要先将对象转化成AIDL可识别的参数(可能是多个参数),然后使用AIDL来传递这些参数,在消息的接收端使用这些参数组装成自己需要的对象。
3.语法
先讲AIDL的语法,这样就可以解释为什么看到的aidl文件有些怪异了。
它并不是一门真正的编程语言,只是定义两个进程之间的通信接口,因此语法非常简单。它的语法与Java接口很相似,但存在以下几点差异:
3.1支持的数据类型:
- Java的简单类型(int、char、boolean等)。不需要导入(import)。
- String和CharSequence。不需要导入(import)。
- List和Map。但要注意,List和Map对象的元素类型必须是AIDL服务支持的数据类型。不需要导入(import)。
- AIDL自动生成的接口。需要导入(import)。
- 实现android.os.Parcelable接口的类。需要导入(import)。
3.2导包
aidl文件中的package和import语句需要自己添加,eclipse不会和.java文件一样自动添加。后两种数据类型需要使用import进行导入,即使它们在同一个包中也需要导包。
3.3注释
跟 java的一样,AIDL文件可以有注释,在package以前的注释将会被忽略,方法和变量以前的注释都会被加入到生产java代码中。
3.4实现接口时的几个原则:
- 抛出的异常不要返回给调用者,跨进程抛异常处理是不可取的;
- IPC调用是同步的,如果你知道一个IPC服务需要超过几毫秒的时间才能完成的话,你应该避免在Activity的主线程中调用。也就是IPC调用会挂起应用程序导致界面失去响应,这种情况应该考虑单起一个线程来处理;
- 不能在AIDL接口中声明静态属性。
4.使用
讲完语法,再讲怎么从无到有建立并使用它。
IPC的调用步骤:
- 声明一个接口类型的变量,该接口类型在.aidl文件中定义。
- 实现ServiceConnection。
- 调用ApplicationContext.bindService(),并在ServiceConnection实现中进行传递。
- 在ServiceConnection.onServiceConnected()实现中,你会接收一个IBinder实例(被调用的 Service)。调用YourInterfaceName.Stub.asInterface((IBinder)service)将参数转换为
YourInterface类型。
- 调用接口中定义的方法。你总要检测到DeadObjectException异常,该异常在连接断开时被抛出。它只会被远程方法抛出。
- 断开连接,调用接口实例中的ApplicationContext.unbindService()。
建立AIDL服务要比建立普通的服务复杂一些,具体步骤如下:
- 在EclipseAndroid工程的Java包目录中建立一个扩展名为aidl的文件。
- 如果aidl文件的内容是正确的,ADT会自动在gen目录下生成一个Java接口文件(*.java)。
- 建立一个服务类(Service的子类)。
- 实现由aidl文件生成的Java接口。
- 在AndroidManifest.xml文件中配置AIDL服务,尤其要注意的事,标签中android:name的属性值就是客户端要引用该服务的ID,也就是Intent类的参数值。
5.简单实例
接下来让我们动手实现。
我们建立一个简单的AIDL服务。这个服务只有一个getValue方法,该方法返回一个String类型的值。在安装完服务后,会在客户端调用这个getValue方法,并将返回值在TextView组建中输出。
1.先建立服务端,工程名为AidlService2。
2.新建IMyService.aidl,如下图:
图1 新建aidl文件
3.键入代码如下:
package com.eyelike.mobile.aidl;
interface IMyService {
String getValue();
}
4.此时,ADT会自动在gen/com.eyelike.mobile.aidl目录下面生成一个IMyService.java的文件,如下图所示。我们一般并不需要关心这个文件的具体内容,也不需要维护它。
图2 根据aidl文件自动生成接口类
5.接下来定义一个Service实现类MyService.java。MyService继承Service,在MyService类中定义一个内部类MyServiceImpl,该类是IMyService.Stub的子类。MyService代码如下:
package com.eyelike.mobile.aidl;
import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
import android.os.RemoteException;
public class MyService extends Service {
public class MyServiceImpl extends IMyService.Stub {
@Override
public String getValue() throws RemoteException {
return "Android AIDL Study by zhaozhao.li";
}
}
/*
// method2:继承Stub,也就是实现了IMyService接口,并实现了IBinder接口
// 需要import com.eyelike.mobile.aidl.IMyService.Stub;
private IMyService.Stub MyServiceImpl2 = new Stub() {
@Override
public String getValue() throws RemoteException {
return "Android AIDL Study by zhaozhao.li";
}
};
*/
@Override
public IBinder onBind(Intent arg0) {
// onBind方法必须返回MyServiceImpl类的对象实例,否则客户端无法获得服务对象
return new MyServiceImpl();
/*
// method2:
// 如果使用第二种方式,则return这里需要稍做修改为下面形式。
return MyServiceImpl2;
*/
}
}
6.接着,需要在AndroidManifest.xml文件中配置该Service,代码如下:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.eyelike.mobile.aidl"
android:versionCode="1"
android:versionName="1.0" >
<uses-sdk android:minSdkVersion="15" />
<application
android:icon="@drawable/ic_launcher"
android:label="@string/app_name" >
<activity
android:name=".AidlService2Activity"
android:label="@string/app_name" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<service android:name=".MyService">
<intent-filter>
<action android:name="com.eyelike.mobile.aidl.IMyService"/>
</intent-filter>
</service>
</application>
</manifest>
7.以上,就完成类AIDL服务的建立。接下来,就可以实现客户端进行调用该服务了。我们先新建一个工程AidlClient2作为客户端。
8.AIDL定义了两个进程之间的通信接口,因此,不仅服务器端需要AIDL接口,客户端同样需要前面定义的AIDL接口,因此新建号客户端工程后的第一步就是将Service端的AIDL接口文件IMyService.java复制到客户端应用中,复制到客户端后ADT工具会为AIDL接口生成相应的实现。
因为服务端的接口文件所在的包与客户端的包名不同,因此复制的时候,先在客户端建立该接口类IMyService.java相同包名的包。如下图所示:
图3 新建包
9.然后将服务端gen/com.eyelike.mobile.aidl目录下的IMyService.java文件拷贝至刚才新建的包的下面。如下图:
图4 拷贝IMyService.java文件
10.接下来我们编写客户端的调用代码,在AidlClient2Activity.java中键入下面代码:
package com.eyelike.mobile;
import com.eyelike.mobile.aidl.IMyService;
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.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.TextView;
public class AidlClient2Activity extends Activity implements OnClickListener {
private IMyService mMyService = null;
private Button mBtnInvokeAIDLService;
private Button mBtnBindAIDLService;
private TextView mTextView;
private ServiceConnection mServiceConnection = new ServiceConnection() {
@Override
public void onServiceDisconnected(ComponentName name) {
}
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
// 获得服务对象
mMyService = IMyService.Stub.asInterface(service);
mBtnInvokeAIDLService.setEnabled(true);
}
};
/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
mBtnInvokeAIDLService = (Button) findViewById(R.id.btn_call_service);
mBtnBindAIDLService = (Button) findViewById(R.id.btn_bind_service);
mBtnInvokeAIDLService.setEnabled(false);
mTextView = (TextView) findViewById(R.id.tv_show);
mBtnInvokeAIDLService.setOnClickListener(this);
mBtnBindAIDLService.setOnClickListener(this);
}
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.btn_bind_service:
// bind AIDL service
bindService(new Intent("com.eyelike.mobile.aidl.IMyService"),
mServiceConnection, Context.BIND_AUTO_CREATE);
break;
case R.id.btn_call_service:
try {
// 调用服务端的getValue方法
mTextView.setText(mMyService.getValue());
} catch (Exception e) {
}
default:
break;
}
}
}
在编写上面的代码时需要特别注意以下两点:
- 使用bindService方法来绑定AIDL服务。其中需要使用Intent对象指定AIDL服务的ID,该ID也就是我们之前在服务端的AndroidManifest.xml文件中定义的标签中android:name属性的值
- 在绑定时需要一个ServiceConnection对象。创建ServiceConnection对象的过程中如果绑定成功,系统会调用onServiceConnected方法,通过该方法的service参数值可以获得AIDL服务对象。
11.首先运行AIDL服务程序,然后运行客户端程序,单击【绑定AIDL服务】按钮,如果绑定成功,【调用AIDL服务】按钮会变为可选状态,单击这个按钮,会输出getValue方法的返回值,如下图所示:
图5 实例运行效果
6.进阶实例-传递复杂数据的AIDL服务
在前面讲AIDL支持的数据类型的时候提到AIDL服务只支持有限的数据类型,上面的例子比较简单,如果用AIDL服务传递一些复杂的数据就需要做更一步处理。传递不需要import的数据类型的值方式与上面的例子相同。传递一个需要import的数据类型的值(例如实现android.os.Parcelable接口的类)的步骤要复杂一些。除了要建立一个实现android.os.Parcelable接口的类外,还需要为这个类单独建立一个aidl文件,并使用parcelable关键字进行定义。
具体的实现步骤如下:
1.建立复杂数据传递的服务端,工程名为AidlService3。
2.建立一个IMyService.aidl文件,键入如下代码:
package com.eyelike.mobile.complex.type.aidl;
import com.eyelike.mobile.complex.type.aidl.Product;
interface IMyService {
Map getMap(in String country, in Product product);
Product getProduct();
}
在编写上面的代码时需要注意如下两点:
- Product是一个实现android.os.Parcelable接口的类,需要使用import导入这个类。
- 如果方法的类型是非简单类型,例如String、List或自定义的类,需要使用in、out或inout修士。其中in表示这个值被客户端设置;out表示这个值被服务端设置;inout表示这个值既被客户端设置,又被服务端设置。
3.编写Product类。该类是用于传递的数据类型,代码如下:
package com.eyelike.mobile.complex.type.aidl;
import android.os.Parcel;
import android.os.Parcelable;
public class Product implements Parcelable {
private int id;
private String name;
private float price;
public static final Parcelable.Creator<Product> CREATOR = new Creator<Product>() {
@Override
public Product[] newArray(int size) {
return new Product[size];
}
@Override
public Product createFromParcel(Parcel in) {
return new Product(in);
}
};
public Product() {
}
public Product(Parcel in) {
readFromParcel(in);
}
private void readFromParcel(Parcel in) {
id = in.readInt();
name = in.readString();
price = in.readFloat();
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeInt(id);
dest.writeString(name);
dest.writeFloat(price);
}
public int getId() {
return id;
}
public void setId(int aId) {
this.id = aId;
}
public String getName() {
return name;
}
public void setName(String aName) {
this.name = aName;
}
public float getPrice() {
return price;
}
public void setPrice(float aPrice) {
this.price = aPrice;
}
}
在编写Product类时应注意如下3点:
- Product类必须实现android.os.Parcelable接口。该接口用于序列化对象。在Android中之所以使用Parcelable接口序列化,而不是java.io.Serializable接口,是因为Google在开发Android时发现Serializable序列化的效率并不高,因此特意提供了一个Parcelable接口来序列化对象。
- 在Product类中必须有一个静态常量CREATOR,它的名字必须是CREATOR,而且数据类型必须是Parcelable.Creator。
- 在writeToParcelable方法中需要将要序列化的值写入Parcel对象。
4.建立一个Product.aidl文件,键入下面代码:
parcelable Product;
这里特别说明:由于这个aidl文件中并未声明接口,所以,并不会在gen目录下面自动生成和它相关的Product.java文件。而且回过头来看我们第3步的工作,我们自己已经将其实现了。
5.编写一个MyService类,代码如下:
package com.eyelike.mobile.complex.type.aidl;
import java.util.HashMap;
import java.util.Map;
import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
import android.os.RemoteException;
/**
* AIDL Service
* */
public class MyService extends Service {
public class MyServiceImpl extends IMyService.Stub {
@Override
public Map getMap(String country, Product product)
throws RemoteException {
Map mMap = new HashMap<String, String>();
mMap.put("country", country);
mMap.put("id", product.getId());
mMap.put("name", product.getName());
mMap.put("price", 31000);
mMap.put("product", product);
return mMap;
}
@Override
public Product getProduct() throws RemoteException {
Product mProduct = new Product();
mProduct.setId(1234);
mProduct.setName("汽车");
mProduct.setPrice(31000);
return mProduct;
}
}
@Override
public IBinder onBind(Intent intent) {
return new MyServiceImpl();
}
}
6.在AndroidManifest.xml文件中配置MyService类,代码如下:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.eyelike.mobile.complex.type.aidl"
android:versionCode="1"
android:versionName="1.0" >
<uses-sdk android:minSdkVersion="15" />
<application
android:icon="@drawable/ic_launcher"
android:label="@string/app_name" >
<activity
android:name=".AidlService3Activity"
android:label="@string/app_name" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<service android:name=".MyService">
<intent-filter>
<action android:name="com.eyelike.mobile.complex.type.aidl.IMyService"/>
</intent-filter>
</service>
</application>
</manifest>
7.至此,传递复杂数据的AIDL服务端就完成了。接下来建立调用该服务的客户端,工程名为AidlClient3。
8.首先还是需要将服务端的文件复制到客户端工程中去。这里需要特别注意:与第一个例子不同的是,由于我们需要传递复杂的数据(Product),因此,除了IMyService.java文件,Product.java文件也必须一同复制过去。方法还是先建立它们的包,然后复制进该包内。如下图:
图6 复制服务端文件到客户端
9.然后,在AidlClient3Activity.java中键入如下代码:
package com.eyelike.mobile;
import com.eyelike.mobile.complex.type.aidl.IMyService;
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.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.TextView;
public class AidlClient3Activity extends Activity implements OnClickListener{
private IMyService mMyService = null;
private Button mBtnInvokeAIDLService;
private Button mBtnBindAIDLService;
private TextView mTextView;
private ServiceConnection mServiceConnection = new ServiceConnection() {
@Override
public void onServiceDisconnected(ComponentName name) {
}
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
// 获得AIDL服务对象
mMyService = IMyService.Stub.asInterface(service);
mBtnInvokeAIDLService.setEnabled(true);
}
};
/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
mBtnInvokeAIDLService = (Button) findViewById(R.id.btn_call_service);
mBtnBindAIDLService = (Button) findViewById(R.id.btn_bind_service);
mBtnInvokeAIDLService.setEnabled(false);
mTextView = (TextView) findViewById(R.id.tv_show);
mBtnInvokeAIDLService.setOnClickListener(this);
mBtnBindAIDLService.setOnClickListener(this);
}
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.btn_bind_service:
// 绑定AIDL服务
bindService(new Intent("com.eyelike.mobile.complex.type.aidl.IMyService"),
mServiceConnection, Context.BIND_AUTO_CREATE);
break;
case R.id.btn_call_service:
try {
String mStr = "";
// 调用AIDL服务中的方法
mStr = "Product.id = " + mMyService.getProduct().getId() + "\n";
mStr += "Product.name = " + mMyService.getProduct().getName() + "\n";
mStr += "Product.price = " + mMyService.getProduct().getPrice() + "\n";
mStr += mMyService.getMap("China", mMyService.getProduct()).toString();
mTextView.setText(mStr);
} catch (Exception e) {
}
break;
default:
break;
}
}
}
10.首先运行服务端程序,然后运行客户端程序,单击【绑定AIDL服务】按钮,待成功绑定后单击【调用AIDL服务】按钮,会输出如下图所示内容:
图7 实例运行效果
7.总结
经过上面的例子练习,我们能够发现AIDL的使用流程分为三步:aidl→Service→Client。浓缩为这三步的时候,就能恍然发现,我们做的就是在Service和Client间使用aidl的过程。
三步的阐述如下:
(1)建立aidl文件,在其中声明接口。gen目录自动生成.java文件,拥有内部类Stub.java。
(2)建立Service文件,在其中继承Stub即可实现aidl中声明的方法,在AndroidManifest.xml文件中声明aciton。
(3)建立Client文件,在其中建立ServiceConnection实例,实现onServiceConnected方法,在其中通过IMyService.Stub.asInterface(service)方法即可获得AIDL服务对象。有了该对象便可使用它的各种方法与Service进行通信。
(4)附加一条复杂数据阐述:自定义的数据类必须implements Parcelable,必须添加一个名为CREATOR的静态成员,对数据的读写定义在readFromParcel和writeToParcel方法中,并建立一个对应的aidl文件进行声明。
整个过程并没有特别难以理解的地方,但由于需要记忆性特别注意实现的地方较多,因此,独立实现AIDL服务,尤其是复杂数据的传递,对于一个新手来说还是颇具难度的。以上简单阐述就是为了在记忆有些模糊的时候便于快速回忆和确认的。凡事熟能生巧,只要真正理解了AIDL服务的实现思路,相信我们都可掌握它!
参考文献
- 《疯狂Android讲义》第10章10.2节 跨进程调用Service(AIDL服务)
- 《Android开发完全讲义》第8章8.4节 跨进程访问(AIDL服务)
- http://wayfarer.iteye.com/blog/562836
- http://dwtf55dwtf.iteye.com/blog/1362796
- [email protected]