《 Service详解(一):什么是Service》
《 Service详解(二):Service生命周期》
《Service详解(三):Service的使用》
《Service详解(四):绑定服务 与 通信》
《Service详解(五):使用Messager进行通信》
《Service详解(六):进程间通信-AIDL》
AIDL是Android Interface Definition Lanauage的缩写,即Android接口定义语言。在Android中,由于每个进程会独享一个单独的虚拟机,所以两个进程之间没有办法直接通信,所以Android提供了AIDL进行IPC,即使用AIDL实现两个进程间的间接通信。
如图所示在main目录上右键一次选择New - Folder - AIDL Folder即可创建文件。
文件夹创建之后,我们接下来创建AIDL文件,步骤如下:
在aidl文件夹上右键依次选择New - AIDL - AIDL File
Interface Name为AIDL File 文件名和 定义的接口名,点击finish即可建立完成。
Android Studio会给出默认的模板:
// IMyAidlInterface.aidl
package com.mark.aidl;
// Declare any non-default types here with import statements
interface IMyAidlInterface {
/** * Demonstrates some basic types that you can use as parameters * and return values in AIDL. */
void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat,
double aDouble, String aString);
}
这样我们的AIDL就建立好了。点击工具栏上的
图标重新编译工程,编译通过之后在
app - build - generated - source - aidl - debug - 包名 下即可找到一个和我们AIDL文件名相同的文件,这个就是android studio为我自动生成的文件,这里我们不许要修改此文件。
aidl这个文件中还是有很多需要注意的,首先这个文件中的语法和java非常相似,但又有所区别,Android Studio中编辑这个文件是没有提示的。
首先我们要知道AIDL支持的数据类型有哪些:
如果需要使用AIDL来传输自定义数据类型,如Person对象、Book对象等,这些对象必须要支持序列化、这里使用Parcelable来序列化,关于Parcelable与Serializable的使用和区别大家何以阅读相关文章进行了解,这里就不进行讲解了。
我们接下来通过一个简单的案例来操作一下,在实战的过程中讲解AIDL的一些注意事项。
我们在aidlServer工程中可以计算人的平均年龄,aidlClient客户端需要计算人的平均年龄,为了节省开发成本,这里使用AIDL复用aidlServer中的功能,代码如下:
首先我们创建了两个module,我们在aidlServer中创建Person类,代码如下:
package com.mark.aidl;
import android.os.Parcel;
import android.os.Parcelable;
/** * Created by Mark on 4/8/16. */
public class Person implements Parcelable {
private String name;
private int age;
protected Person(Parcel in) {
name = in.readString();
age = in.readInt();
}
public static final Creator<Person> CREATOR = new Creator<Person>() {
@Override
public Person createFromParcel(Parcel in) {
return new Person(in);
}
@Override
public Person[] newArray(int size) {
return new Person[size];
}
};
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(name);
dest.writeInt(age);
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
因为Person类为自定义数据类型,所以这里需要实现Parcelable接口,以支持序列化操作。然后重写了的toString方法,打印Person的信息。这里要提示大家的是:
protected Person(Parcel in);
public void writeToParcel(Parcel dest, int flags)
这两个方法中都对Person的成员变量name、age进行了操作,这里需要注意的是其操作顺序必须是一样的,否则会序列化失败。也就是说,如果在writeToParcel方法中先dest.writeString(name),那么在Person构造函数中也必须第一个name = in.readString(),一次类推,否则将序列化失败。
接下来我们在aidlServer中创建AIDL文件,名为AidlRemote.aidl:
// AidlRemote.aidl
package com.mark.aidl;
interface AidlRemote {
// 获取每个人的平均年龄
float getAverageAge(List<Person> persons);
}
在AidlRemote中我们定义一个借口方法,传入一个List<Person>
集合,然后通过计算之后会返回集合中Person的平均年龄。
编译工程,可以发现编译不能通过,会报如下错误,
/Users/Lyong/GitHub/AIDL/aidlServer/src/main/aidl/com/mark/aidl/AidlRemote.aidl:7 parameter persons (1) unknown type List<Person>
不能识别List<Person>
,这是为什么,原因是aidl不能识别这个Person,我们需要使用aidl语法来声明这个Person,具体操作如下:
创建Person.aidl文件:
在android studio创建Person.aidl文件会有一个bug,因为我们java代码中已经用过Person这个名字了,所以直接创建会出错误。
所以这里我们只能复制其他的aidl文件,然后黏贴到aidl文件夹中,然后改名字了。
创建好之后我们修改Person.aidl文件的内容如下:
// AidlRemote.aidl
package com.mark.aidl;
parcelable Person;
指定包名,使用parcelable关键字来声明Person类。声明之后我们需要在AidlRemote中导入Person类。
// AidlRemote.aidl
package com.mark.aidl;
// 导入Person
import com.mark.aidl.Person;
interface AidlRemote {
// 获取每个人的平均年龄
float getAverageAge(List<Person> persons);
}
编译程序….,又错了!不要急,可见错误原因已经变了!
/Users/Lyong/GitHub/AIDL/aidlServer/src/main/aidl/com/mark/aidl/AidlRemote.aidl:9 parameter 1: 'List<Person> persons' can be an out parameter, so you must declare it as in, out or inout.
这里描述的是我们需要说明参数List<Person> persons
是输入参数、还是输出参数,因为aidl底层工作机制会有一个打包拆包的过程,非常消耗资源,这里我们指明输入器用途即可是这个处理过程更加简单。所以我们使用in/out来指明是输入还是输出即可。
// AidlRemote.aidl
package com.mark.aidl;
// 导入Person
import com.mark.aidl.Person;
interface AidlRemote {
// 获取每个人的平均年龄
float getAverageAge(in List<Person> persons);
}
重新编译,终于编译通过了。
自动生成的AidlRemote文件内容如下:
/* * This file is auto-generated. DO NOT MODIFY. * Original file: /Users/Lyong/GitHub/AIDL/aidlServer/src/main/aidl/com/mark/aidl/AidlRemote.aidl */
package com.mark.aidl;
public interface AidlRemote extends android.os.IInterface {
/** Local-side IPC implementation stub class. */
public static abstract class Stub extends android.os.Binder implements com.mark.aidl.AidlRemote {
private static final java.lang.String DESCRIPTOR = "com.mark.aidl.AidlRemote";
/** Construct the stub at attach it to the interface. */
public Stub()
{
this.attachInterface(this, DESCRIPTOR);
}
/** * Cast an IBinder object into an com.mark.aidl.AidlRemote interface, * generating a proxy if needed. */
public static com.mark.aidl.AidlRemote asInterface(android.os.IBinder obj)
{
if ((obj==null)) {
return null;
}
android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
if (((iin!=null)&&(iin instanceof com.mark.aidl.AidlRemote))) {
return ((com.mark.aidl.AidlRemote)iin);
}
return new com.mark.aidl.AidlRemote.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_getAverageAge:
{
data.enforceInterface(DESCRIPTOR);
java.util.List<com.mark.aidl.Person> _arg0;
_arg0 = data.createTypedArrayList(com.mark.aidl.Person.CREATOR);
float _result = this.getAverageAge(_arg0);
reply.writeNoException();
reply.writeFloat(_result);
return true;
}
}
return super.onTransact(code, data, reply, flags);
}
private static class Proxy implements com.mark.aidl.AidlRemote {
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 float getAverageAge(java.util.List<com.mark.aidl.Person> persons) throws android.os.RemoteException
{
android.os.Parcel _data = android.os.Parcel.obtain();
android.os.Parcel _reply = android.os.Parcel.obtain();
float _result;
try {
_data.writeInterfaceToken(DESCRIPTOR);
_data.writeTypedList(persons);
mRemote.transact(Stub.TRANSACTION_getAverageAge, _data, _reply, 0);
_reply.readException();
_result = _reply.readFloat();
}
finally {
_reply.recycle();
_data.recycle();
}
return _result;
}
}
static final int TRANSACTION_getAverageAge = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
}
// 获取每个人的平均年龄
public float getAverageAge(java.util.List<com.mark.aidl.Person> persons) throws android.os.RemoteException;
}
这里不多做解释,我们继续完成我们的任务。
AIDL文件我们已经完成了,接下来就需要一个服务了(Service),是的,AIDL离不开Service。接下来我们创建RemoteService,代码如下:
package com.mark.aidl;
import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
import android.os.RemoteException;
import java.util.List;
public class RemoteService extends Service {
public RemoteService() {
}
/** * 当服务绑定的时候调用此方法,返回IBinder对象,即AidlRemote.Stub实例 * @param intent * @return */
@Override
public IBinder onBind(Intent intent) {
return iBinder;
}
/** * Stub为AidlRemote中的一个静态抽象内部类,并且此类继承了android.os.Binder * 并实现了com.mark.aidl.AidlRemote接口,也就是我们创建的AIDL接口。 */
AidlRemote.Stub iBinder = new AidlRemote.Stub() {
/** * 实现计算年龄平均值方法 * @param persons 多人集合 * @return 集合中人的平均年龄 * @throws RemoteException */
@Override
public float getAverageAge(List<Person> persons) throws RemoteException {
int sum = 0;
// 计算集合中人的总年龄
for(Person person : persons) {
sum += person.getAge();
}
// 返回总年龄除以人数-平均年龄
return sum / persons.size();
}
};
}
Service的代码如上,注释非常详细,这里不多做解释,至此服务端的工作就完成了。我们接下来看看客户端如何与服务端通过AIDL进行通信。
首先我们需要将服务端的AidlRemote.aidl、Person.aidl、Person.java复制到客户端对应的目录中,如下所示:
这里需要注意的是,这三个文件在客户端的位置,即包名要与服务器一致。编译工程,确保没有错误。
package com.mark.aidlclient;
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.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import com.mark.aidl.AidlRemote;
import com.mark.aidl.Person;
import java.util.ArrayList;
import java.util.List;
public class MainActivity extends AppCompatActivity {
private final static String TAG = MainActivity.class.getSimpleName();
Button btnAdd;
List<Person> mPersons;
// 声明AidlRemote接口
AidlRemote mAidlRemote;
private ServiceConnection conn = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
// 当服务绑定成功之后会调用此方法,
// 这里调用AidlRemote.Stub.asInterface静态方法将service参数转成AidlRemote实例
mAidlRemote = AidlRemote.Stub.asInterface(service);
}
@Override
public void onServiceDisconnected(ComponentName name) {
// 当断开服务时,回收资源
mAidlRemote = null;
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// 绑定服务
bindService();
// 创建Person数据
mPersons = getPersonList(10);
initView();
}
private void initView() {
btnAdd = (Button) findViewById(R.id.btnAdd);
btnAdd.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
try {
// 计算mPersons中Person的平均年龄
float average = mAidlRemote.getAverageAge(mPersons);
Log.d(TAG, "平均年龄为:" + average);
} catch (RemoteException e) {
e.printStackTrace();
}
}
});
}
/** * 创建Person数据 * @param num 创建多少个Person对象 * @return */
private List<Person> getPersonList(int num) {
List<Person> persons = new ArrayList<>();
for(int i = 0;i<num;i++) {
Person person = new Person("Person"+i, 20 + i);
persons.add(person);
}
return persons;
}
/** * 绑定到服务端的求平均值服务 */
private void bindService() {
// 获取到服务端
Intent intent = new Intent();
intent.setComponent(new ComponentName("com.mark.aidl", "com.mark.aidl.RemoteService"));
bindService(intent, conn, Context.BIND_AUTO_CREATE);
}
@Override
protected void onDestroy() {
super.onDestroy();
// 解除绑定
unbindService(conn);
}
}
MainActivity的代码如上,这里简单介绍一下思路,大家也可以根据注释进行理解,首先我们根据服务端的包名字符串创建一个Intent,然后使用bindService进行绑定服务,然后在onServiceConnected中使用AidlRemote.Stub.asInterface静态方法将Ibinder对象转成mAidlRemote对象。这里可以回去看一下RemoteService中的onBind方法的返回值,你就豁然开朗了。然后在断开连接的时候回收资源,避免内存泄露。
在onCreate时候绑定到服务端的求平均值的RemoteService,然后创建需要计算平均值的Person数据,然后在点击Button按钮的时候通过AIDL调用服务端的服务来求平均值,大致过程就是这样的,我们现在先运行服务端,在运行客户端,然后点击button,观察log打印情况。
04-08 15:52:41.174 14986-14986/com.mark.aidlclient D/MainActivity: 平均年龄为:24.0
可见,平均值已经求出来,我们的aidl也就成功了。