转载请注明出处:http://blog.youkuaiyun.com/yegongheng/article/details/18964225
在上一篇《Android核心组件之Service》博文中我们详细讲解了Bound Service的两种方法(继承Binder和使用Messenger),当只需在本应用程序绑定 Service而不需要执行进程间通信时,继承Binder的方法为较好的选择,而若需要在不同的应用程序执行进程间的通信,但不需要在Service中使用多线程访问时,则可以使用Messenger来完成我们的需求。不过,若即需要在不同的应用程序中执行进程间的通信,也要在Service中使用多线程访问,那么我们该怎么办呢?别急,Android已经为我们提供了解决方法,就是使用AIDL来实现进程间的通信,接下来,我们将会详细讲解AIDL的原理和使用方法。
AIDL是什么
AIDL(Android Interface Definition Language),它是一种类似于IDLs的Android接口定义语言,它可以生成Android设备上两个进程之间进行进程间通信的代码,然后来完成客户端和服务器端之间的交流。在Android系统中,一般情况下一个进程是无法访问另外一个进程的内存的,而使用代码编组来实现是又比较的冗长乏味,那么我们就需要把对象分解成操作系统能够理解的基本单元,安全的通过进程边界。那么,我们可以使用AIDL来完成这些工作。
创建.aidl文件
AIDL IPC机制是面向接口的,像COM或Corba一样,但是更加轻量级,它是使用代理类在客户端和服务器端传递数据。实现AIDL的第一步就是了解并创建.aidl文件的规则和原理:首先在客户端和服务器端的项目中创建后缀名为.aildl的 同名文件(例如MyIPerson.aidl),并使用类java语言的编程方式在文件中编辑定义一个接口(interface MyIPerson),保存后Android SDK 会自动在各自的项目/gen文件中创建一个MyIPerson.java文件,且该类包含了一个继承了Binder类的内部类Stub,而我们知道Android进程间通信是基于IBinder实现的,由此我们可知客户端便是借助Stub来执完成进程间访问的。
在MyIPerson.aidl文件中定义接口后,我们可以在接口中定义远程客户端需使用要的方法,定义的方法不需要实现,并且方法即可以有返回值、有参数(甚至其它AIDL定义的接口),也可以无返回值、无参数,但有一点需要注意的是接口中不能定义具有static fields(全局变量)的值。
默认情况下,AIDL支持诸多的数据类型:除了八种基本数据类型(byte,short,int,long,char,float,double,boolean)和String、CharSequence、List和Map在.aidl中使用时无需import该类型具体路径外,其它的数据类型都要使用import说明数据对象的包路径,甚至在同一包中定义的interface。还有要注意的是所有非基本数据类型在接口定义时都需要说明数据的走向,使用in、on或者是inout,而基本数据类型默认的是in。
使用IPC传递一个对象
如果需要使用IPC(进程间通信)将一个类对象从一个进程传递到另一个进程中,那么必须要保证该类能通过进程通道,在Java Web开发中我们一般可以让类实现java.io.Serializable接口来实现序列化和反序列化操作进而进行跨进程的传输。而在Android开发中,Google为开发者提供了一个序列化速度更快、更完善的Parcelable接口,它能更加方便地完成类的序列化和反序列化操作。接下来,我们用一个简单的实例来说明Parcelable接口的用法,而一般使用Parcelable分为以下几个步骤:
<1>让类实现Parcelable接口;
<2>重写writeToParcel()和定义readFromParcel()方法,目的是得到当前对象的状态并将其写进Parcel中。
<3>实现Parcelable.creator接口并实例化,保证该对象为static final类型且对象名命名为CREATOR。
<4>最后,创建一个实现了Parcelable 接口的类的.aidl声明文件。
下面有一个实例,具体代码如下:
- package com.fendou.domain;
- import android.os.Parcel;
- import android.os.Parcelable;
- //<1>实现 Parceable接口
- public class Person implements Parcelable {
- private int id ;
- private String userName;
- private double height ;
- public Person(){
- }
- //创建私有化的构造函数,调用readFromParcel()方法
- private Person(Parcel in)
- {
- readFromParcel(in);
- }
- //(3)必须提供一个名为CREATOR的static final属性 该属性需要实现android.os.Parcelable.Creator<T>接口
- private static final Parcelable.Creator<Person> CREATOR = new Parcelable.Creator<Person>() {
- public Person createFromParcel(Parcel source) {
- // TODO Auto-generated method stub
- return new Person(source);
- }
- public Person[] newArray(int size) {
- // TODO Auto-generated method stub
- return new Person[size];
- }
- };
- public int describeContents() {
- // TODO Auto-generated method stub
- return 0;
- }
- /**
- * 注意:writeToParcel()和readFromParcel()对变量的读写顺序需一致
- */
- //(2)将Person对象的状态写入Parcel中
- public void writeToParcel(Parcel out, int arg1) {
- // TODO Auto-generated method stub
- out.writeInt( id);
- out.writeString( userName);
- out.writeDouble( height);
- }
- //(2)从Parcel中读取Person对象的状态
- public void readFromParcel(Parcel in){
- id = in.readInt();
- userName = in.readString();
- height = in.readDouble();
- }
- public int getId() {
- return id ;
- }
- public void setId(int id) {
- this.id = id;
- }
- public String getUserName() {
- return userName ;
- }
- public void setUserName(String userName) {
- this.userName = userName;
- }
- public double getHeight() {
- return height ;
- }
- public void setHeight(double height) {
- this.height = height;
- }
- }
定义的Person声明文件Person.aidl文件代码如下:
- package com.fendou.aidl;
- parcelable Person;
AIDL实例讲解
接下来,我们将通过一个实例来进一步了解AIDL的原理和使用方法。再此之前,我们先来参考一下Google官方提供给我们使用AIDL实现进程间通信的方法步骤,如下:
<1>创建后缀名为.aidl文件;
<2>创建一个实现IBinder接口的类(Eclipse会自动完成);
<3>实例化一个ServiceConnection对象;
<4>调用Context.bindService()方法,并将IBinder对象通过onBind()方法返回给客户端。
<5>在客户端ServiceConnection中的onServiceConnection()方法中获接收IBinder对象,并调用Stub对象中的asInterface()方法得到实例;
<6>在客户端调用接口中定义的方法,一般在不能正常连接时会抛DeadObjectException异常。
<7>调用Context.unbindService()方法解除Service绑定。
根据以上提供的步骤,我们首先创建两个项目,一个作为客户端,另一个座位服务器端,客户端通过AIDL来操作服务器端的方法,实现进程间的通信。下面是两个项目的目录结构:
客户端目录结构:
客户端MainActivity.java的源码如下:
- package com.fendou.activity;
- import com.fendou.aidl.MyIPerson;
- import com.fendou.aidl.Person;
- 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.TextView;
- public class MainActivity extends Activity implements OnClickListener{
- /** Called when the activity is first created. */
- private static final String TAG = "ygh";
- //声明控件对象
- private Button mButton_boundservice;
- private Button mButton_unboundservice;
- private Button mButton_getmethod_button;
- private Button mButton_cleartext;
- private TextView mTextView_showresult;
- private MyIPerson myIPerson;
- private boolean isBound = false;
- @Override
- public void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout. main);
- //根据ID得到代表该控件的对象
- mButton_boundservice = (Button)findViewById(R.id.bound_service_button );
- mButton_unboundservice = (Button)findViewById(R.id.unbound_service_button );
- mButton_getmethod_button = (Button)findViewById(R.id.getmethod_from_service_button );
- mButton_cleartext = (Button)findViewById(R.id.clear_text_textview );
- mTextView_showresult = (TextView)findViewById(R.id.showresult_textview );
- //为控件设置事件监听
- mButton_boundservice.setOnClickListener(this);
- mButton_unboundservice.setOnClickListener(this);
- mButton_getmethod_button .setOnClickListener(this);
- mButton_cleartext.setOnClickListener(this);
- mTextView_showresult.setOnClickListener(this);
- }
- //实例化ServiceConnection对象,主要用于接收从Service端传递过来的值
- ServiceConnection mServiceConnection = new ServiceConnection() {
- @Override
- public void onServiceDisconnected(ComponentName name) {
- // TODO Auto-generated method stub
- Log. i(TAG, "onServiceDisconnected");
- isBound = false ;
- }
- @Override
- public void onServiceConnected(ComponentName name, IBinder service) {
- // TODO Auto-generated method stub
- Log. i(TAG, "onServiceConnected");
- //得到MyIPerson实例,可以使用用该实例去范文远程的Service中的方法
- myIPerson = MyIPerson.Stub.asInterface(service);
- isBound = true ;
- }
- };
- @Override
- public void onClick(View v) {
- // TODO Auto-generated method stub
- switch (v.getId()) {
- case R.id.bound_service_button :
- boundService();
- break;
- case R.id.getmethod_from_service_button :
- getMethodFromService();
- break;
- case R.id.clear_text_textview :
- mTextView_showresult.setText("" );
- break;
- case R.id.unbound_service_button :
- unBoundService();
- break;
- default:
- break;
- }
- }
- //绑定Service
- public void boundService()
- {
- //创建Intent实例对象
- Intent mIntent = new Intent("com.fendou.MainService" );
- //绑定Service
- this.bindService(mIntent, mServiceConnection, Context.BIND_AUTO_CREATE);
- }
- //调用Service中的方法
- public void getMethodFromService()
- {
- Person mPerson = new Person();
- mPerson.setId(1);
- mPerson.setUserName( "Jack");
- mPerson.setHeight(1.71);
- try {
- //远程调用Service端的方法
- String personInfo = myIPerson.getPerson(mPerson);
- mTextView_showresult.setText(personInfo);
- } catch (RemoteException e) {
- // TODO Auto-generated catch block
- e.printStackTrace();
- }
- }
- //解除Service的绑定
- public void unBoundService()
- {
- if(isBound )
- {
- this.unbindService(mServiceConnection );
- }
- }
- }
客户端项目目录包中的Person.java文件中的代码上面已经列出,要实现远程传递Person类对象,Person类需实现Parcelable接口。而MyIPerson.aidl是客户端需要从服务端端调用的方法的接口设计文件,客户端和服务端都需要定义该文件,以便建立起两者之间的通信。而Person.aidl则是对Person.java的声明,这个有点像C语言中的头文件,客户端和服务端同样也都需要定义该文件。
下面再看服务端的目录结构和代码:
服务器端目录结构:
服务端目录包中MainService.java文件中的代码如下:
- package com.fendou.service;
- import com.fendou.aidl.MyIPerson;
- import com.fendou.aidl.Person;
- import android.app.Service;
- import android.content.ComponentName;
- import android.content.Intent;
- import android.content.ServiceConnection;
- import android.os.IBinder;
- import android.os.RemoteException;
- import android.util.Log;
- public class MainService extends Service{
- private static final String TAG = "ygh";
- /** Called when the activity is first created. */
- MyIPerson.Stub binder = new MyIPerson.Stub() {
- @Override
- public String getPerson(com.fendou.aidl.Person mPerson)
- throws RemoteException {
- // TODO Auto-generated method stub
- String userInfo = "Hello," + mPerson.getUserName() +" !Welcome to study Android.\nYou ID is "+
- mPerson.getId() + ",height is "+ mPerson.getHeight() + "." ;
- return userInfo;
- }
- };
- @Override
- public void onCreate() {
- // TODO Auto-generated method stub
- super.onCreate();
- Log. i(TAG, "onCreate");
- }
- @Override
- public int onStartCommand(Intent intent, int flags, int startId) {
- // TODO Auto-generated method stub
- Log. i(TAG, "onStartCommand");
- return super .onStartCommand(intent, flags, startId);
- }
- @Override
- public void onStart(Intent intent, int startId) {
- // TODO Auto-generated method stub
- Log. i(TAG, "onStart");
- super.onStart(intent, startId);
- }
- @Override
- public IBinder onBind(Intent intent) {
- // TODO Auto-generated method stub
- Log. i(TAG, "onBind");
- return binder ;
- }
- @Override
- public boolean onUnbind(Intent intent) {
- // TODO Auto-generated method stub
- Log. i(TAG, "onUnbind");
- return super .onUnbind(intent);
- }
- @Override
- public void onDestroy() {
- // TODO Auto-generated method stub
- Log. i(TAG, "onDestroy");
- super.onDestroy();
- }
- }
项目中com.fendou.aidl包中的文件与客户端项目中的同名文件是一样的,其中Person.java和Person.aidl的代码已列出,MyIPerson.aidl的内容如下:
- package com.fendou.aidl;
- import com.fendou.aidl.Person;
- interface MyIPerson{
- String getPerson(in Person mPerson);
- }
aidl文件中的接口和方法与java的很相似,只不过声明方法时不需要定义修饰符(private、protected和public)。本实例是在接口中定义一个getPerson()方法,客户端远程调用Service中的该方法时,传递一个Person对象,该方法接收到对象后经过相关处理再返回一个String类型的值给客户端。
下面我将稍微分析一下代码的执行过程,首先运行服务端项目,由于服务端没有定义Activity,因此不具备服务端没有界面显示,然后再运行客户端项目,运行后的界面显示如下图:
首先点击"Bound Service"按钮,调用客户端MainActivity中的boundService()方法,开启远程绑定服务,控制台打印的Log信息如下:
说明开启远程绑定服务成功,并且在ServiceConnection中使用MyIPerson.Stub.asInterface(service)方法得到MyIPerson对象,然后再点击“Call The method from service”按钮,实例化一个Person对象并远程调用Service中的getPerson()方法,将Person对象传递作为实参传递给该方法,然后经过方法处理返回一个String类型的值并使用TextView展现在界面上,界面效果如下图:
由此我们可知客户端远程调用Service中的方法成功,使用AIDL完成了进程间的通信。最后,点击“UnBound Service”解除Service绑定,控制台打印的Log信息如下:
总结:以上实例简单的使用了AIDL实现了进程间的通信,不过在AIDL实际应用中,遇到的情况可能会复杂得多,比如在Service中的方法操作会是一个较为耗时的工作(比如文网络下载、文件操作等),那么我们就应避免在主线程中调用该Service中的方法,否则容易导致界面出现ANR,所以最好要考虑线程的安全性,我们可以将耗时操作放在一个新建的线程中执行。