Android IPC机制(二):AIDL的基本使用方法
JimDcf
9年前
<h2><strong>一、前言</strong></h2> <p> <a href="http://www.open-open.com/lib/view/open1460533690542.html">上一篇文章</a>,讲述了实现序列化和反序列化的基本方式,是实现进程间通讯的必要条件,而这篇文章主要来讲一讲IPC的主要方式之一——AIDL方式。除了AIDL方式,IPC还有其他进程间通讯方式,比如Messager、ContentProvider、Socket等,这些以后会讲到。现在先说说AIDL的基本使用方法。</p> <h2><strong>二、什么是AIDL?</strong></h2> <p> AIDL全称:Android Interface Definition Language,即Android接口定义语言。由于不同的进程不能共享内存,所以为了解决进程间通讯的问题,Android使用一种接口定义语言来公开服务的<strong>接口</strong>,本质上,AIDL非常像一个接口,通过公开接口,让别的进程调用该接口,从而实现进程间的通讯。</p> <h2><strong>三、使用AIDL</strong></h2> <p>以下结合一个具体实例来示范AIDL的使用方法。<br> <strong>1、建立.aidl文件</strong><br> 为了方便AIDL的开发,建议把所有和AIDL相关的类和文件放入同一个包中,这样方便把整个包复制,以便其他模块或者应用需要用到同一个AIDL。在Android Studio下,专门为AIDL文件创建了一个文件夹,方便我们的管理:</p> <p><img src="https://simg.open-open.com/show/503b653defe49311f00f024d7dab109f.png"></p> <p>项目结构</p> <p><br> 可以看到,笔者新建了一个service模块,该模块在manifests的声明如下:</p> <pre> <service android:name=".MyAidlService" android:enabled="true" android:exported="true"></service></pre> <p> 这个模块为app模块提供服务,与app模块处于不同进程,所以模拟了进程间通讯的场景。在service模块,新建一个AIDL文件夹,然后新建一个包,这里包名为com.chenyu.service,然后新建AIDL文件:IMyAidl.aidl:</p> <pre> // IMyAidl.aidl package com.chenyu.service; // Declare any non-default types here with import statements import com.chenyu.service.Person; interface IMyAidl { void addPerson(in Person person); List<Person> getPersonList(); }</pre> <p> 这与定义一个接口的语法基本相同,都是以Interface为关键字定义。里面声明了两个方法,分别是addPerson(in Person person)与getPersonList()。AIDL中除了基本数据类型,其他类型的参数必须标上方向,in、out、或者inout,in表示输入型参数,out表示输出型参数,inout表示输入输出型参数,我们要根据需要实际指定参数类型,因为底层的数据处理开销非常大,如果不指定类型,编译将会无法通过。</p> <p> <strong>AIDL支持的数据类型</strong>:<br> ①基本数据类型(int,long,char,boolean,double)<br> ②string和CharSequence<br> ③List:只支持ArrayList,以及里面所有的元素必须能够被AIDL支持 ④Map:只支持HashMap,以及里面所有的元素必须能够被AIDL支持 ⑤Parcelable:所有实现了Parcelable接口的对象<br> ⑥AIDL:所有AIDL接口本身也可以在AIDL文件中使用。</p> <p> <strong>注意一下</strong>:这里使用了自定义的Parcelable对象:Person类,但是AIDL不认识这个类,所以我们要创建一个与Person类同名的AIDL文件:Person.aidl</p> <pre> // IMyAidl.aidl package com.chenyu.service; //声明Person parcelable Person;</pre> <p> 只有这样,IMyAidl.aidl才能知道其中的Person是使用了Parcelable接口的类,注意,Person类的包名与Person.aidl的包名一定要相同,即无论其他应用或者其他模块,只要有AIDL,都应该保证AIDL的所有包结构一致,才能保证顺利进行IPC通讯,减少不必要的麻烦。</p> <p><strong>2、Person类,实现Parcelable接口</strong><br> 在java文件夹中,创建com.chenyu.service包,这样就与上面的是相同包名了。</p> <pre> package com.chenyu.service; import android.os.Parcel; import android.os.Parcelable; public class Person implements Parcelable { private String name; private int age; private int number; public Person(Parcel source) { this.name=source.readString(); this.age=source.readInt(); this.number=source.readInt(); } //getter、setter method //... public Person(int age, String name, int number) { this.age = age; this.name = name; this.number = number; } @Override public int describeContents() { return 0; } @Override public void writeToParcel(Parcel dest, int flags) { dest.writeString(name); dest.writeInt(age); dest.writeInt(number); } public static final Parcelable.Creator<Person> CREATOR=new Creator<Person>() { @Override public Person createFromParcel(Parcel source) { return new Person(source); } @Override public Person[] newArray(int size) { return new Person[size]; } }; @Override public String toString() { return "Person{" + "name='" + name + '\\\\'' + ", age=" + age + ", number=" + number + '}'; } }</pre> <p>对于Parcelable接口的详细解析,可参考上一篇文章,这里不再赘述。</p> <p><strong>3、实现服务端</strong><br> 上面我们定义了一个AIDL接口,接下来要做的是实现这个AIDL接口,在java/com.chenyu.service中,创建MyAidlService.java文件:</p> <pre> package com.chenyu.service; import android.app.Service; import android.content.Intent; import android.os.IBinder; import android.os.RemoteException; import android.util.Log; import java.util.ArrayList; import java.util.List; public class MyAidlService extends Service { private ArrayList<Person> persons; @Override public IBinder onBind(Intent intent) { persons=new ArrayList<Person>(); Log.d("cy", "success bind"); return iBinder; } private IBinder iBinder= new IMyAidl.Stub() { @Override public void addPerson(Person person) throws RemoteException { persons.add(person); } @Override public List<Person> getPersonList() throws RemoteException { return persons; } }; @Override public void onCreate() { super.onCreate(); Log.d("cy", "onCreate "); } }</pre> <p> 我们来看一下服务端是如何实现接口的:<br> (1)为了实现来自.aidl文件生成的接口,需要继承Binder接口(例如Ibinder接口),并且实现从.aidl文件中继承的方法,在上面代码中,使用匿名实例实现一个叫IMyAidl(定义在IMyAidl.aidl中)的接口,实现了两个方法,addPerson和getPersonList.<br> (2)onBind方法:该方法在客户端与服务端连接的时候回调,实现客户端和服务端的绑定,并返回一个Binder实例,这里返回的是iBinder,而IBinder是(1)中实现了接口的匿名实例,即客户端拿到的实际上实现了接口的一个实例,这样,客户端通过Binder就与服务端建立了连接,客户端通过Binder远程调用服务端的实例方法,这样也即实现了进程间通讯。</p> <h2><strong>4、实现客户端</strong></h2> <p>在实现客户端之前,先确保把aidl的包复制过来,就相上面笔者所给出的结构图一样,包括Person类也应该复制过来。显示界面比较简单,就不贴出来了,主要看Activity的代码:</p> <pre> package com.chenyu.myaidl; 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.widget.Button; import com.chenyu.service.IMyAidl; import com.chenyu.service.Person; import java.util.List; public class MainActivity extends Activity implements View.OnClickListener { private Button btn; IMyAidl iMyAidl; private ServiceConnection conn=new ServiceConnection() { @Override public void onServiceConnected(ComponentName name, IBinder service) { Log.d("cylog", "onServiceConnected success"); iMyAidl=IMyAidl.Stub.asInterface(service); // 1 } @Override public void onServiceDisconnected(ComponentName name) { Log.d("cylog", "onServicedisConnected "); iMyAidl=null; } }; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); initView(); bindService(); } private void initView() { btn= (Button) findViewById(R.id.cal); btn.setOnClickListener(this); } @Override public void onClick(View v) { try { iMyAidl.addPerson(new Person(21, "陈育", 22255)); List<Person> persons = iMyAidl.getPersonList(); // 2 Log.d("cylog",persons.toString()); } catch (RemoteException e) { e.printStackTrace(); } } private void bindService() { Intent intent=new Intent(); intent.setComponent(new ComponentName("com.chenyu.service","com.chenyu.service.MyAidlService")); bindService(intent,conn, Context.BIND_AUTO_CREATE); } @Override protected void onDestroy() { super.onDestroy(); unbindService(conn); } }</pre> <p> 与绑定一般Service的语法差不多,但是在安卓5.0之后,必须显式指定Service的包名和类名即:</p> <pre> private void bindService() { Intent intent=new Intent(); intent.setComponent(new ComponentName("com.chenyu.service","com.chenyu.service.MyAidlService")); bindService(intent,conn, Context.BIND_AUTO_CREATE); }</pre> <p> 在bindService(intent,conn,Context.BIND_AUTO_CREATE)方法中有几个参数需要说明一下,<br> ①conn:该参数代表了与服务端的连接,即ServiceConnection.<br> ②Context.BIND_AUTO_CREATE:该参数表示绑定的同时创建一个Service。<br> 在发出请求绑定成功之后,会回调①处的代码,此时,可在回调方法onServiceConnected()方法中,获取服务端返回的IMyAidl实例,在客户端拿到该实例之后,就可以通过调用相应的方法进行远程通讯了,比如上述的②处代码。<br> 最后,看一下运行结果,先运行service,然后运行app:</p> <p><img src="https://simg.open-open.com/show/7c805bf5384c6385c495aa7c9aedc50b.png"></p> <p>运行结果</p> <p><img src="https://simg.open-open.com/show/51a2ad87a1284736397bfc6cd59eff99.png"></p> <p>进程显示</p> <p><br> 可以看出,app端和service端的确是构成了进程间通讯,并且完成了进程间通讯。<br> 以上是利用AIDL实现进程通讯的基本方法,希望对大家有所帮助。关于AIDL的核心原理以及Binder,AIDL优化,会在下一篇文章详细讲述。</p> <p>来自: <a href="/misc/goto?guid=4959670552585595579" rel="nofollow">http://www.jianshu.com/p/b9b15252b3d6</a></p>