Service详解(六):进程间通信-AIDL

《 Service详解(一):什么是Service》
《 Service详解(二):Service生命周期》
《Service详解(三):Service的使用》
《Service详解(四):绑定服务 与 通信》
《Service详解(五):使用Messager进行通信》
《Service详解(六):进程间通信-AIDL》

什么是AIDL

AIDL是Android Interface Definition Lanauage的缩写,即Android接口定义语言。在Android中,由于每个进程会独享一个单独的虚拟机,所以两个进程之间没有办法直接通信,所以Android提供了AIDL进行IPC,即使用AIDL实现两个进程间的间接通信。

Android Studio创建ADIL

Service详解(六):进程间通信-AIDL_第1张图片

如图所示在main目录上右键一次选择New - Folder - AIDL Folder即可创建文件。

文件夹创建之后,我们接下来创建AIDL文件,步骤如下:

Service详解(六):进程间通信-AIDL_第2张图片

在aidl文件夹上右键依次选择New - AIDL - AIDL File

Service详解(六):进程间通信-AIDL_第3张图片

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就建立好了。点击工具栏上的

这里写图片描述

图标重新编译工程,编译通过之后在

Service详解(六):进程间通信-AIDL_第4张图片

app - build - generated - source - aidl - debug - 包名 下即可找到一个和我们AIDL文件名相同的文件,这个就是android studio为我自动生成的文件,这里我们不许要修改此文件。

AIDL 支持的数据类型

aidl这个文件中还是有很多需要注意的,首先这个文件中的语法和java非常相似,但又有所区别,Android Studio中编辑这个文件是没有提示的。

首先我们要知道AIDL支持的数据类型有哪些:

  • 基本数据类型:byte、short、char、int、long、float、long、boolean
    • 这里注意官方给出的是所有基本数据类型,但是short类型是不能编译通过的,大家可以尝试一下。
  • String、CharSequence
  • List、Map
  • Parcelable

自定义数据类型

如果需要使用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这个名字了,所以直接创建会出错误。

Service详解(六):进程间通信-AIDL_第5张图片

所以这里我们只能复制其他的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复制到客户端对应的目录中,如下所示:

Service详解(六):进程间通信-AIDL_第6张图片

这里需要注意的是,这三个文件在客户端的位置,即包名要与服务器一致。编译工程,确保没有错误。

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也就成功了。

你可能感兴趣的:(进程通信,实战,详解,aidl)