AIDL实现进程间通信

本文将以一个简单的 AS 工程实例,使用 AIDL 实现进程间通信(Inter-Process Communication,IPC),中间穿插介绍必要的技术理论和 AS 操作步骤。

 

第一部分  Parcelable 的理解与实现

(1) IPC 中对数据进行序列化的原因:

Java 中,除了基本数据类型,包括 byte,char,short,int,long,float,double,boolean,其他类型的对象,无法通过 IPC 直接传输。“ 序列化 ” 的出现就是为了解决这个问题。在两个进程中包含同一个类的相同定义的前提下,只有进行了“序列化”,相应的数据对象在传递到目标进程后,才能依据 “ 序列化 ” 的准则将数据流正确 “ 反序列化 ” 为最终的被传对象。注意,IPC 过程中,不同进程中的两个对象,对象内容虽然相同,但是,不是同一个对象。

(2)Java 中的 “ 序列化 ”:实现 Serializable 接口

class Test implements Serializable {

    private static final long serialVersionUID = 0;

    ...

}

使用 Serializable 接口实现序列化,只需要注意两点:

第一点:implements Serializable

第二点:定义 private static final long serialVersionUID = 0;

第二点serialVersionUID 如果不定义,有可能导致反序列化失败,或者引起其它异常。

(3)Android中提供的 “序列化”方案:实现Parcelable接口

package com.xxx.sarcontrol;

import android.os.Parcel;
import android.os.Parcelable;

public class SarStatus implements Parcelable {

    public boolean mChargerConnected;
    public boolean mEarPiece;
    public boolean mVoiceCall;
    public boolean mDataCall;
    public boolean mConnection;
    public boolean mWifi;
    public boolean mMifi;
    public boolean mWifiDirect;
    public boolean mAirPlaneMode;
    public boolean mRfCable;
    public boolean mOB5State;
    public boolean mTX0State;

    public int mSarState;
    public int mWifiCutbackUsed;
    public int[] mSensorState = null;

    public SarStatus() {
    }


    protected SarStatus(Parcel in) {

        mChargerConnected = in.readInt() == 1;
        mEarPiece = in.readInt() == 1;
        mVoiceCall = in.readInt() == 1;
        mDataCall = in.readInt() == 1;
        mConnection = in.readInt() == 1;
        mWifi = in.readInt() == 1;
        mMifi = in.readInt() == 1;
        mWifiDirect = in.readInt() == 1;
        mAirPlaneMode = in.readInt() == 1;
        mRfCable = in.readInt() == 1;
        mOB5State = in.readInt() == 1;
        mTX0State = in.readInt() == 1;

        mSarState = in.readInt();
        mWifiCutbackUsed = in.readInt();

        int length = in.readInt();
        if(length > 0){
            mSensorState = new int[length];
            in.readIntArray(mSensorState);
        }
    }

    public static final Creator CREATOR = new Creator() {
        @Override
        public SarStatus createFromParcel(Parcel in) {
            return new SarStatus(in);
        }

        @Override
        public SarStatus[] newArray(int size) {
            return new SarStatus[size];
        }
    };

    @Override
    public int describeContents() {
        return 0;
    }

    @Override
    public void writeToParcel(Parcel parcel, int i) {

        parcel.writeInt(mChargerConnected ? 1:0);
        parcel.writeInt(mEarPiece ? 1:0);
        parcel.writeInt(mVoiceCall ? 1:0);
        parcel.writeInt(mDataCall ? 1:0);
        parcel.writeInt(mConnection ? 1:0);
        parcel.writeInt(mWifi ? 1:0);
        parcel.writeInt(mMifi ? 1:0);
        parcel.writeInt(mWifiDirect ? 1:0);
        parcel.writeInt(mAirPlaneMode ? 1:0);
        parcel.writeInt(mRfCable ? 1:0);
        parcel.writeInt(mOB5State ? 1:0);
        parcel.writeInt(mTX0State ? 1:0);

        parcel.writeInt(mSarState);
        parcel.writeInt(mWifiCutbackUsed);
        if(mSensorState == null){
            parcel.writeInt(0);
        } else {
            parcel.writeInt(mSensorState.length);
            parcel.writeIntArray(mSensorState);
        }

    }



    @Override
    public String toString() {
        StringBuilder string_mSensorState = new StringBuilder();

        if(mSensorState != null){
            for(int i =0; i < mSensorState.length; i++ ){
                string_mSensorState.append(mSensorState[i] +" ");
            }
        }
        String out =
                "[ mChargerConnected = " + mChargerConnected + ", mEarPiece = " + mEarPiece +
                        ", mVoiceCall = " + mVoiceCall + ", mDataCall = " + mDataCall +
                        ", mConnection = " + mConnection + ", mWifi = " + mWifi +
                        ", mMifi = " + mMifi + ", mWifiDirect = " + mWifiDirect +
                        ", mAirPlaneMode = " + mAirPlaneMode + ", mRfCable = " + mRfCable +
                        ", mOB5State = " + mOB5State + ", mTX0State = " + mTX0State +
                        ", mSarState = " + mSarState + ", mWifiCutbackUsed = " + mWifiCutbackUsed +
                        ", mSensorState[] = [" + string_mSensorState +"], ]";
        return out;
    }

}

上述代码,重点关注“数组成员变量”在其中的处理方式。

实现Parcelable步骤

1)implements Parcelable

2)重写writeToParcel方法,将你的对象序列化为一个Parcel对象,即:将类的数据写入外部提供的Parcel中,打包需要传递的数据到Parcel容器保存,以便从 Parcel容器获取数据

3)重写describeContents方法,内容接口描述,默认返回0就可以

4)实例化静态内部对象CREATOR实现接口Parcelable.Creator

public static final Parcelable.Creator CREATOR

注:其中public static final一个都不能少,内部对象CREATOR的名称也不能改变,必须全部大写。需重写本接口中的两个方法:createFromParcel(Parcel in) 实现从 Parcel 容器中读取传递数据值,封装成 Parcelable 对象返回逻辑层,newArray(int size) 创建一个类型为 T,长度为 size 的数组,仅一句话即可(return new T[size]),供外部类反序列化本类数组使用。

简而言之:通过 writeToParcel 将你的对象映射成 Parcel 对象,再通过 createFromParcel 将 Parcel 对象映射成你的对象。也可以将 Parcel 看成是一个流,通过 writeToParcel 把对象写到流里面,在通过 createFromParcel 从流里读取对象,只不过这个过程需要你来实现,因此写的顺序和读的顺序必须一致。

(4)Serializable 和 Parcelable 的共同点和区别

共同点:二者均实现了序列化,并可用于intent 间的数据传递。

区别:

Serializable 是 Java 中的序列化接口,使用起来简单但开销很大,序列化和反序列化过程需要大量 I/O 操作。

Parcelable 是 Android 中的序列化方式,因此更适合用在 Android 平台上,缺点是使用起来麻烦,但是效率很高。

Parcelable 主要用在内存序列化上;对于 “ 对象序列化到存储设备中 ” 和 “ 将对象序列化后通过网络传输 ” 两种情况,推荐使用Serializable,因为此时 Parcelable 显得过程复杂。

第二部分  Binder,AIDL,Parcelable 之间的关系

(1)Binder 可以简单认为是 Android 特有的一种进程间通信方式,这种进程间通信方式核心在于内核中的 binder 驱动,该驱动负责数据的 IPC 双向传递,向 Client 端提供访问接口,并对 Server 端进行调用。

在 Android 中,Binder 通信也是可以支持传递 “ 序列化 ” 过的 Java 字节流的,但是它也只是将这串字节流当做普通数据类型来处理,它只负责传递,具体如何解释这些数据流,需要有双方约定,这个约定就是 “ 序列化 ” 和 “ 反序列化 ” 。

可以通过Binder进程传递的数据类型有3种:

一,基本数据类型,

二,binder数据类型(Parcelable数据被声明成AIDL文件中的接口,可以理解为:就是为了将其变成binder数据类型,进行Binder传递)

三,文件描述符

 

(2)Parcelable 很好理解,本文第一部分已介绍,它是一种 “序列化” 方式,序列化之后的数据可以通过 IPC 进行传递。

(3)AIDL,为了使其他的应用程序也可以访问本应用程序提供的 Service(这里的 Service 并不是特指 Android 四大组件中的Service,而是 Service 端可以向 Client 端提供的调用),Android 系统采用了远程过程调用(Remote Procedure Call,RPC)方式来实现。Android使用一种接口定义语言(Interface Definition Language,IDL)来公开服务的接口,称为 AIDL(Android Interface Definition Language)服务。

上面这段定义是我从网上摘来的,我自己个人理解如下:AIDL其实就一种接口的声明规范,这里的接口指的是Server端提供的访问接口,按照该规范写出来的带有aidl后缀的接口文件就是AIDL文件,通过编译工具(aidl.exe)对AIDL文件进行编译后,生成了可通过Binder IPC进行传递的binder数据类型(即Stub,该数据类型extends了Binder,并implements了上述接口),用户再通过extends 该Stub类,来定义Server端具体的、可供调用的功能。这部分内容的理解需要熟悉 AIDL 转换过程中各种类和接口的关系。

AIDL中支持的数据类型:

支持类型 是否需要import 备注
Java基本数据类型 no  

String

 

CharSequence

 
List

List,Map内的元素必须是AIDL支持的类型,包括key和value;

List接收方必须是ArrayList;

Map接收方必须是HashMap

Map
实现Parcelable的类 Yes 值传递
其他AIDL定义的AIDL接口 Yes 传递的是引用

综上,Binder是Android中特有的一种进程间通信方式,该进程方式中除了支持基本数据类型的直接传播,还支持 Binder 子类类型的数据传递,当然还有数据描述符;Parcelable 的存在是为了 “ 序列化 ” 和 “ 反序列化 ”,这样才能通过IPC传递,但是经过了序列化和反序列化,并不能保证一定能在某种特定的IPC机制下进行进程间传递;而AIDL的出现,就是为了将接口类IInterface转换为Binder子类,有了Binder子类,才能在Android的Binder通信机制中传递数据和实现跨进程调用,包括对Parcelable进行aidl声明,也是为了满足Binder IPC机制支持直接传递Binder子类对象这一点。

这部分总结是本人个人理解,不保证官方正确性。

第三部分  Parcelable 实现类在 AndroidStudio 中的 aidl 声明

AIDL实现进程间通信_第1张图片

 

步骤一:右击工程main/java目录下的包名,New -> AIDL -> AIDL File,根据弹出的窗口,命名 Parcelable 数据封装类的类名,以 “SarStatus” 为例,生成 SarStatus.aidl 文件。生成的aidl文件默认保存在main/java的同级aidl目录(新生成)下

默认生成的SarStatus.aidl中SarStatus是Interface类型,修改为parcelable关键字,注意首字母是小写。内容如下

// SarStatus.aidl
package com.motorola.aidlservertest;

// Declare any non-default types here with import statements

parcelable SarStatus;

步骤二:再次右击工程 main/java 目录下的包名(此包要和aidl中的包名保持一致),New -> Java Class,在弹出的窗口中输入和步骤一中同名的类名,并实现Parcelable 接口。

具体类的定义内容见第一部分对应的Parcelable实现类。

注意:

步骤一和步骤二的顺序不能交换,如果步骤二先执行,再执行步骤一,AS会禁止生成同名的aidl文件。

 

第四部分  Aidl 文件在 AndroidStudio 中的添加步骤

步骤一:每次生成aidl文件,都要右击java目录下的相应包名,,New -> AIDL -> AIDL File,命名完毕后,新的aidl文件会生成在java的同级aidl目录下

步骤二:完善aidl文件的内容。

完善aidl文件时,需要注意两点

1,import相关的aidl类或者Parcelable类

2,对于Parcelable作为函数参数的情况,需要添加 in, out或者inout参数修饰

关于in, out和inout三者之间的区别,请参考

https://blog.csdn.net/luoyanglizi/article/details/51958091

3,Aidl接口中只支持方法,不支持声明静态变量。这一点不同于传统的接口

第五部分  使用AIDL实现两个进程间的双向调用

这部分,除了讨论传统AIDL+Service的组合可以实现Client端和Server端之间的通信,还使用RemoteCallbackList<>,可动态的向Server添加注册,从而实现Server端向Client端的主动调用。

5.1  打通Client端调用Server端的通道

1)创建 Parcelable AIDL文件 

2)创建要操作的实体类,实现 Parcelable 接口,实现序列化/反序列化

3)创建接口 aidl 文件

4)Make project ,生成Aidl对应的 Binder Java 文件

服务端 

5)创建 Service,并在其中创建上面生成的 Binder 对象实例(Stub子类),实现接口定义的方法

6)在 onBind() 中返回上一步中Binder对象实例

客户端 

7)实现 ServiceConnection 接口,在其中拿到 AIDL 类,通过asInterface()转换为 “访问接口A”

8)bindService()

9)通过 “访问接口A” 调用 AIDL 类中定义好的操作请求

 

5.2  连接建立以后,打通Server端向Client端主动发起调用:

Client端

1)实现待注册的Stub子类,通过“访问接口A”的注册接口,向Server端发起注册

Server端

2)在接受注册的函数中,使用RemoteCallbackList保存上一步中注册过来的Binder对象

3)  在 Server 端特定的位置调用 2)中的 RemoteCallbackList 获取Binder对象,通过Binder对象回调Client端的相关逻辑。

此处贴一下实现Server端、Client端双向主动调用功能的Aidl文件定义:

 

 

其中,SarStatus.aidl 对应的是 Parcelable 实现类;

// SarStatus.aidl
package com.motorola.sarcontrol;

// Declare any non-default types here with import statements

parcelable SarStatus;

ISarStatusManager.aidl 对应的 binder 类是在 Server 端实现,通过 Service 的 onBinder() 回传至 Client 端,供 Client 调用;

// ISarStatusManager.aidl
package com.motorola.sarcontrol;

import com.motorola.sarcontrol.SarStatus;
import com.motorola.sarcontrol.IOnSarStatusChangedListener;
// Declare any non-default types here with import statements

interface ISarStatusManager {
    SarStatus getSarStatus();

    void registerListener(IOnSarStatusChangedListener listener);
    void unregisterListener(IOnSarStatusChangedListener listener);

}

IOnSarStatusChangedListener.aidl 对应的binder类是在Client端实现,通过ISarStatusManager中的接口传递至Server端,Server端拿到之后,保存在RemoteCallbackList中,在需要的时候回调IOnSarStatusChangedListener中的接口。

// IOnSarStatusChangedListener.aidl
package com.motorola.sarcontrol;

import com.motorola.sarcontrol.SarStatus;

// Declare any non-default types here with import statements

interface IOnSarStatusChangedListener {

    void onSarStatusChanged(in SarStatus sarStatus);

}

 

 

第六部分:在Android系统源码中新建一个app,使用Aidl,并配置Android.mk

关于在Android系统源码中新建一个app,需要重点做好两点:

1)选定合适的目录,并把app所有源代码和相关文件放至该目录,我个人比较偏好 /package/apps/ 目录,同时配置好Android.mk

2)  在待编译的产品配置目录下(一般在 /device/手机厂商名/产品名,例如/device/lenovo/snoopy),选择一个参与系统编译的mk文件,一般选择device.mk文件,做类似以下配置:

PRODUCT_PACKAGES += ControlService

其中 ControlService 为 Android.mk 中 LOCAL_PACKAGE_NAME 一项所指定的 app 模块名

如果app中使用了aidl,关于aidl文件的编译需要在Android.mk中添加相应的配置。

我个人在参考AndroidStudio工程的目录结构后,把Aidl文件和相关java文件在系统源码对应路径下放好后,在Android.mk中添加了以下配置,保证了aidl正常生效:

LOCAL_PATH:= $(call my-dir)
include $(CLEAR_VARS)

LOCAL_MODULE_TAGS := optional

LOCAL_SRC_FILES := $(call all-subdir-java-files) $(call all-Iaidl-files-under,aidl)

LOCAL_AIDL_INCLUDES := $(LOCAL_PATH)/aidl


LOCAL_PACKAGE_NAME := ControlService

include $(BUILD_PACKAGE)

其中:

LOCAL_SRC_FILES := $(call all-subdir-java-files) $(call all-Iaidl-files-under,aidl)

LOCAL_AIDL_INCLUDES := $(LOCAL_PATH)/aidl

两项直接和Aidl相关。

 

 

 

 

 

 

 

 

 

 

 

 

 

你可能感兴趣的:(Android基础,Android工具)