小猪的Android入门之路 Day 9 part 2
Android四大组件之——AIDL实现跨进程通信
——转账请注明出处:coder-pig
本节引言:
在上一part中我们对Service进行了简单的学习:
什么是Service,Service的生命周期,StartService和BindService的区别以及使用
IntentService来解决Service的异步问题;
而在今天的这一Part中将会研究另一个东西:IPC,安卓给我们提供了AIDL Service
来完成进程间的数据交换!好了,开始本节的内容吧!
本节学习路线图:
正文:
看这篇博文的人,不知道你能不能坚持地看完本篇内容,为了照顾你们,直接把总结用法放到本文的开头!
AIDL简介:
开头我们说的IPC,全名叫做跨进程通信(interprocess communication),因为在Android系统中,
个个应用程序都运行在自己的进程中,进程之间一般是无法直接进行数据交换的,而为了实现跨进程
间的通信,Android给我们提供了AIDL技术,AIDL(Android Interface Definition Language)是一种
接口语言,语法十分的简单,这种接口语言并不是真正的编程语言,只是定义两个进程间的通信接口而已!
而生成符合通信协议的Java代码则是由Android SDK的platform-tools下的aidl.exe工具自动地在gen
目录下生成一个对应的Xxx.java的接口,在该接口中包含一个Stub的内部类,在该类中实现了IBinder接口
与自定义的通信接口,这个类将会作为远程Service的回调类——实现了IBinder接口,所以可作为Service
的onBind( )方法的返回值!
使用AIDL完成两个进程之间的简单通信
一.建立作为服务端的工程:
step 1:创建IPerson.aidl文件,不要直接new file然后建立哦!这样的话是打不开文件,从而不能编写代码哦!
①直接新建一个txt文件,编写好后保存为.aidl格式,然后复制到对应路径下
②因为aidl和接口类似,所以直接new interface,编写好内容后,来到对应java文件所在目录下修改文件后缀名;
然后点击刷新即可,另外编写ADIL文件时有以下注意事项:
1.接口名词需要与aidl文件名相同
2.接口和方法前面不要加访问权限修饰符:public ,private,protected等,也不能用static final!
3.AIDL默认支持的类型包括Java基本类型,String,List,Map,CharSequence,除此之外的其他类型都需要import
声明,对于使用自定义类型作为参数或者返回值,自定义类型需要实现Parcelable接口,详情请看后面的传递复杂数据类型
4.自定义类型和AIDL生成的其它接口类型在aidl描述文件中,应该显式import,即便在该类和定义的包在同一个包中。
编写IPerson.aidl文件内容如下:
- package com.example.aidl;
- interface IPerson {
- String queryPerson(int num);
- }
我们可以打开IPerson.java看看里面的代码
- /*
- * This file is auto-generated. DO NOT MODIFY.
- * Original file: E:\\Eandroid_code\\AIDL_Service\\src\\com\\example\\aidl\\IPerson.aidl
- */
- package com.example.aidl;
- public interface IPerson extends android.os.IInterface
- {
- /** Local-side IPC implementation stub class. */
- public static abstract class Stub extends android.os.Binder implements com.example.aidl.IPerson
- {
- private static final java.lang.String DESCRIPTOR = "com.example.aidl.IPerson";
- /** Construct the stub at attach it to the interface. */
- public Stub()
- {
- this.attachInterface(this, DESCRIPTOR);
- }
- /**
- * Cast an IBinder object into an com.example.aidl.IPerson interface,
- * generating a proxy if needed.
- */
- public static com.example.aidl.IPerson asInterface(android.os.IBinder obj)
- {
- if ((obj==null)) {
- return null;
- }
- android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
- if (((iin!=null)&&(iin instanceof com.example.aidl.IPerson))) {
- return ((com.example.aidl.IPerson)iin);
- }
- return new com.example.aidl.IPerson.Stub.Proxy(obj);
- }
- @Override public android.os.IBinder asBinder()
- {
- return this;
- }
- @Override public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException
- {
- switch (code)
- {
- case INTERFACE_TRANSACTION:
- {
- reply.writeString(DESCRIPTOR);
- return true;
- }
- case TRANSACTION_queryPerson:
- {
- data.enforceInterface(DESCRIPTOR);
- int _arg0;
- _arg0 = data.readInt();
- java.lang.String _result = this.queryPerson(_arg0);
- reply.writeNoException();
- reply.writeString(_result);
- return true;
- }
- }
- return super.onTransact(code, data, reply, flags);
- }
- private static class Proxy implements com.example.aidl.IPerson
- {
- private android.os.IBinder mRemote;
- Proxy(android.os.IBinder remote)
- {
- mRemote = remote;
- }
- @Override public android.os.IBinder asBinder()
- {
- return mRemote;
- }
- public java.lang.String getInterfaceDescriptor()
- {
- return DESCRIPTOR;
- }
- @Override public java.lang.String queryPerson(int num) 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);
- _data.writeInt(num);
- mRemote.transact(Stub.TRANSACTION_queryPerson, _data, _reply, 0);
- _reply.readException();
- _result = _reply.readString();
- }
- finally {
- _reply.recycle();
- _data.recycle();
- }
- return _result;
- }
- }
- static final int TRANSACTION_queryPerson = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
- }
- public java.lang.String queryPerson(int num) throws android.os.RemoteException;
- }
我们关注的只是asInterface(IBinder)和我们定义的接口中的queryPerson()方法!
该方法会把IBinder类型的对象转换成IPerson类型的,必要时生成一个代理对象返回结果!
当然,你也可以选择不看,因为我们完全可以跳过这里去进行下一步,这里只是了解下而已!
step 2:自定义我们的Service类,完成下述操作:
1)继承Service类,同时也自定义了一个PersonQueryBinder类用来继承IPerson.Stub类
就是实现了IPerson接口和IBinder接口
2)实例化自定义的Stub类,并重写Service的onBind方法,返回一个binder对象!
AidlService.java
- package com.example.aidl_service;
- import com.example.aidl.IPerson.Stub;
- import android.app.Service;
- import android.content.Intent;
- import android.os.IBinder;
- import android.os.RemoteException;
- public class AidlService extends Service {
- private IBinder binder = new PersonQueryBinder();
- private String[] names = {"小涛","刘老师","蓝翔","挖掘机"};
- @Override
- public IBinder onBind(Intent intent) {
- return binder;
- }
- private String query(int num)
- {
- if(num > 0 && num < 5){
- return names[num - 1];
- }
- return null;
- }
- private final class PersonQueryBinder extends Stub{
- @Override
- public String queryPerson(int num) throws RemoteException {
- return query(num);
- }
- }
- }
- <service android:name=".AidlService">
- <intent-filter>
- <action android:name="android.intent.action.AIDLService" />
- <category android:name="android.intent.category.DEFAULT" />
- </intent-filter>
- </service>
这里并没有提供Activity界面,当着没关系,该应用提供的Service可以供其他app来调用!
二.建立作为客户端的工程:
首先需要把我们把服务端创建的IPerson.aidl文件复制过来,然后
我们直接在MainActvivity中完成,和绑定本地Service的操作类似:
1)自定义PersonConnection类实现ServiceConnection接口
2)以PersonConnection对象作为参数,调用bindService绑定远程Service
bindService(service, conn, BIND_AUTO_CREATE);
ps:第三个参数是设置如果服务没有启动的话,自动创建
3)和本地Service不同,绑定远程Service的ServiceConnection并不能直接获取Service的onBind( )方法
返回的IBinder对象,只能返回onBind( )方法所返回的代理对象,需要做如下处理:
iPerson = IPerson.Stub.asInterface(service);
再接着完成初始化,以及按钮事件等就可以了
MainActvitiy.java
- package com.jay.example.aidl_client;
- import com.example.aidl.IPerson;
- import android.app.Activity;
- import android.content.ComponentName;
- import android.content.Intent;
- import android.content.ServiceConnection;
- import android.os.Bundle;
- import android.os.IBinder;
- import android.os.RemoteException;
- import android.view.View;
- import android.view.View.OnClickListener;
- import android.widget.Button;
- import android.widget.EditText;
- import android.widget.TextView;
- public class MainActivity extends Activity {
- private EditText editnum;
- private Button btnquery;
- private TextView txtshow;
- private IPerson iPerson;
- private PersonConnection conn = new PersonConnection();
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.activity_main);
- editnum = (EditText) findViewById(R.id.editnum);
- btnquery = (Button) findViewById(R.id.btnquery);
- txtshow = (TextView) findViewById(R.id.txtshow);
- Intent service = new Intent("android.intent.action.AIDLService");
- bindService(service, conn, BIND_AUTO_CREATE);
- btnquery.setOnClickListener(new OnClickListener() {
- @Override
- public void onClick(View v) {
- String number = editnum.getText().toString();
- int num = Integer.valueOf(number);
- try {
- txtshow.setText(iPerson.queryPerson(num));
- } catch (RemoteException e) {
- e.printStackTrace();
- }
- }
- });
- }
- @Override
- protected void onDestroy() {
- unbindService(conn);
- super.onDestroy();
- }
- private final class PersonConnection implements ServiceConnection {
- public void onServiceConnected(ComponentName name, IBinder service) {
- iPerson = IPerson.Stub.asInterface(service);
- }
- public void onServiceDisconnected(ComponentName name) {
- iPerson = null;
- }
- }
- }
然后先启动我们的AIDL_Service,再接着启动AIDL_Cilent,输入要查询的姓名序号,即可知道对应姓名!
当然也可以直接启动AIDL_Client,也可以获得相同的结果!
效果图:
好了,对于AIDLService的简单实用示例就到这里,后续如果有更好的例子会再给出!
传递复杂数据的AIDL Service
上面的简单示例中,客户端向服务端发送了一个int类型的参数,然后服务端返回一个String类型的参数,看似已经
基本满足我们的需求了,但是实际情况下,我们还需要考虑向客户端传递复杂数据类型的情况!那么下面就来讲
解下如何向服务端传递复杂数据类型的问题!
1.先要了解的是Parcelable接口,对于自定义类型,Android规定调用远程Service的参数与返回值都必须实现
Parcelable接口,该接口告诉Android运行时在封送(marshalling)和解封送(unmarshalling)过程中如何序列化
与反序列化对象,这个时候你或许会有疑惑,为什么不直接用Java提供的Serializable接口呢?这是因为Android
团队觉得Java序列化太慢,难以满足Android的进程间的通信需求,所以他们构建了Parcelable方案!
---->也就是自定义的对象要实现Parcelable接口!实现接口要实现下述方法
※实现Paecelable接口意味着要实现writeToParcel和readFromPacel方法;
写入方法将对象写入到包裹(parcel)中,而读取方法则从包裹中读取对象,请注意,写入属性顺序需与读取顺序相同
※需要在该类中添加一个名为CREATOR的static final属性,该属性需要实现android.os.Parcelable.Creator<T>接口
里面的两个方法
createFromParcel(Parcel source)方法:实现从source创建出JavaBean实例的功能
newArray(int size):创建一个类型为T,长度为size的数组,只有一个简单的return new T[size]; (这里的T是Person类)
※describeContents():这个我也不知道是拿来干嘛的,直接返回0即可!不用理他
2.非原始类型中,除了String和CharSequence以外,其余均需要一个方向指示符。方向指示符包括
in、out、和inout。in表示由客户端设置,out表示由服务端设置,inout表示客户端和服务端都设置了该值。
代码实例:
这里我们自定义两种对象类型:Person与Salary,Person作为调用远程的Service的参数,Salary作为返回值!
那么首先要做的就是创建Person与Salary类,同时需要实现Parcelable接口
一.服务端的编写
step 1.先建立Person.aidl和Salary.aidl的文件,因为这两个需要实现Parcelable接口,就下面简单的一句!
Person.aidl: parcelable Person;
Salary.aidl: parcelable Salary;
step 2.分别建立Person类与Salary类,都需要实现Parcelable接口.重写对应的方法!
另外因为我们后面是根据Person对象来获取Map集合中的数据,所以Person.java中我们重写了hashcode和equals
的方法;而Salary类则不需要!
Person.java:
- package com.jay.example.aidl;
- import android.os.Parcel;
- import android.os.Parcelable;
- public class Person implements Parcelable{
- private Integer id;
- private String name;
- public Person(){}
- public Person(Integer id, String name) {
- super();
- this.id = id;
- this.name = name;
- }
- public Integer getId() {
- return id;
- }
- public void setId(Integer id) {
- this.id = id;
- }
- public String getName() {
- return name;
- }
- public void setName(String name) {
- this.name = name;
- }
- //实现Parcelable必须实现的方法,不知道拿来干嘛的,直接返回0就行了
- @Override
- public int describeContents() {
- return 0;
- }
- //写入数据到Parcel中的方法
- @Override
- public void writeToParcel(Parcel dest, int flags) {
- //把对象所包含的数据写入到parcel中
- dest.writeInt(id);
- dest.writeString(name);
- }
- //必须提供一个名为CREATOR的static final属性 该属性需要实现
- //android.os.Parcelable.Creator<T>接口
- public static final Parcelable.Creator<Person> CREATOR = new Parcelable.Creator<Person>() {
- //从Parcel中读取数据,返回Person对象
- @Override
- public Person createFromParcel(Parcel source) {
- return new Person(source.readInt(),source.readString());
- }
- @Override
- public Person[] newArray(int size) {
- return new Person[size];
- }
- };
- //因为我们集合取出元素的时候是根据Person对象来取得,所以比较麻烦,
- //需要我们重写hashCode()和equals()方法
- @Override
- public int hashCode()
- {
- final int prime = 31;
- int result = 1;
- result = prime * result + ((name == null) ? 0 : name.hashCode());
- return result;
- }
- @Override
- public boolean equals(Object obj)
- {
- if (this == obj)
- return true;
- if (obj == null)
- return false;
- if (getClass() != obj.getClass())
- return false;
- Person other = (Person) obj;
- if (name == null)
- {
- if (other.name != null)
- return false;
- }
- else if (!name.equals(other.name))
- return false;
- return true;
- }
- }
接着是Salary类了,照葫芦画瓢,都是类似的:
Salary.java:
- package com.jay.example.aidl;
- import android.os.Parcel;
- import android.os.Parcelable;
- public class Salary implements Parcelable{
- private String type;
- private Integer salary;
- public Salary(){}
- public Salary(String type, Integer salary) {
- super();
- this.type = type;
- this.salary = salary;
- }
- public String getType() {
- return type;
- }
- public void setType(String type) {
- this.type = type;
- }
- public Integer getSalary() {
- return salary;
- }
- public void setSalary(Integer salary) {
- this.salary = salary;
- }
- //实现Parcelable必须实现的方法,不知道拿来干嘛的,直接返回0就行了
- @Override
- public int describeContents() {
- return 0;
- }
- //写入数据到Parcel中的方法
- @Override
- public void writeToParcel(Parcel dest, int flags) {
- //把对象所包含的数据写入到parcel中
- dest.writeString(type);
- dest.writeInt(salary);
- }
- //必须提供一个名为CREATOR的static final属性 该属性需要实现
- //android.os.Parcelable.Creator<T>接口
- public static final Parcelable.Creator<Salary> CREATOR = new Parcelable.Creator<Salary>() {
- //从Parcel中读取数据,返回Person对象
- @Override
- public Salary createFromParcel(Parcel source) {
- return new Salary(source.readString(),source.readInt());
- }
- @Override
- public Salary[] newArray(int size) {
- return new Salary[size];
- }
- };
- public String toString()
- {
- return "工作:"+type+" 薪水"+salary;
- };
- }
嗯呢,完成上述四个文件后,接着就搞个ISalary.aidl了,我们在里面只写一个简单的获取工资信息的方法
ISalary.aidl:
- package com.jay.example.aidl;
- import com.jay.example.aidl.Salary;
- import com.jay.example.aidl.Person;
- interface ISalary
- {
- //定义一个Person对象作为传入参数
- //接口中定义方法时,需要制定新参的传递模式,这里是传入,所以前面有一个in
- Salary getMsg(in Person owner);
- }
ps:这里可以记得如果使用的是自定义的数据类型的话,需要import哦!!!切记!!!
接着就到服务端的核心Service的编写了!
定义一个SalaryBinder类继承Stub,从而实现ISalary和IBinder接口;定义一个存储信息的Map集合!
重新onBind方法,返回SalaryBinder类的对象实例!
AidlService.java
- package com.jay.example.aidl_complexservice;
- import java.util.HashMap;
- import java.util.Map;
- import com.jay.example.aidl.ISalary.Stub;
- import com.jay.example.aidl.Person;
- import com.jay.example.aidl.Salary;
- import android.app.Service;
- import android.content.Intent;
- import android.os.IBinder;
- import android.os.RemoteException;
- public class AidlService extends Service {
- private SalaryBinder salaryBinder;
- private static Map<Person,Salary> ss = new HashMap<Person, Salary>();
- //初始化Map集合,这里在静态代码块中进行初始化,当然你可也以在构造方法中完成初始化
- static
- {
- ss.put(new Person(1, "Jay"), new Salary("码农", 2000));
- ss.put(new Person(2, "GEM"), new Salary("歌手", 20000));
- ss.put(new Person(3, "XM"), new Salary("学生", 20));
- ss.put(new Person(4, "MrWang"), new Salary("老师", 2000));
- }
- @Override
- public void onCreate() {
- super.onCreate();
- salaryBinder = new SalaryBinder();
- }
- @Override
- public IBinder onBind(Intent intent) {
- return salaryBinder;
- }
- //同样是继承Stub,即同时实现ISalary接口和IBinder接口
- public class SalaryBinder extends Stub
- {
- @Override
- public Salary getMsg(Person owner) throws RemoteException {
- return ss.get(owner);
- }
- }
- @Override
- public void onDestroy() {
- System.out.println("服务结束!");
- super.onDestroy();
- }
- }
最后别忘了需要在AndroidManifest.xml文件中注册Service哦!
- <service android:name=".AidlService">
- <intent-filter>
- <action android:name="android.intent.action.AIDLService" />
- <category android:name="android.intent.category.DEFAULT" />
- </intent-filter>
- </service>
二.客户端的编写:
1.先把服务端的AIDL的文件直接拷贝过来,拷贝后的目录如下:
2.编写简单的布局,再接着就是核心MainActvitiy的实现了
定义一个ServciceConnection对象,重写对应方法,和前面的普通数据的类似
再接着在bindService,然后再Button的点击事件中获取Salary对象并显示出来!
MainActivity.java
- package com.jay.example.aidl_complexclient;
- import com.jay.example.aidl.ISalary;
- import com.jay.example.aidl.Person;
- import com.jay.example.aidl.Salary;
- import android.app.Activity;
- import android.app.Service;
- import android.content.ComponentName;
- import android.content.Intent;
- import android.content.ServiceConnection;
- import android.os.Bundle;
- import android.os.IBinder;
- import android.os.RemoteException;
- import android.view.View;
- import android.view.View.OnClickListener;
- import android.widget.Button;
- import android.widget.EditText;
- import android.widget.TextView;
- public class MainActivity extends Activity {
- private ISalary salaryService;
- private Button btnquery;
- private EditText editname;
- private TextView textshow;
- private ServiceConnection conn = new ServiceConnection() {
- @Override
- public void onServiceDisconnected(ComponentName name) {
- salaryService = null;
- }
- @Override
- public void onServiceConnected(ComponentName name, IBinder service) {
- //返回的是代理对象,要调用这个方法哦!
- salaryService = ISalary.Stub.asInterface(service);
- }
- };
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.activity_main);
- btnquery = (Button) findViewById(R.id.btnquery);
- editname = (EditText) findViewById(R.id.editname);
- textshow = (TextView) findViewById(R.id.textshow);
- Intent it = new Intent();
- it.setAction("com.jay.aidl.AIDL_SERVICE");
- bindService(it, conn, Service.BIND_AUTO_CREATE);
- btnquery.setOnClickListener(new OnClickListener() {
- @Override
- public void onClick(View v) {
- try
- {
- String name = editname.getText().toString();
- Salary salary = salaryService.getMsg(new Person(1,name));
- textshow.setText(name + salary.toString());
- }catch(RemoteException e){e.printStackTrace();}
- }
- });
- }
- @Override
- protected void onDestroy() {
- super.onDestroy();
- this.unbindService(conn);
- }
- }
好了,关于Android中的跨进程IPC的代表AIDL就讲解到这里,如果后续有什么新的知识点会
在进行补偿!谢谢支持!
本节相关代码下载:
1)使用AIDL完成进程间的简单通信:点击下载
2)传递复杂数据的AIDL Service的实现:点击下载