简单解析AIDL的使用

学习IPC机制离不开对IBinder的了解,好久没有用过AIDL了,本篇是记录一下,免得自己忘记。

场景

1.aidl_service 提供简单计算服务
2.aidl_client 请求计算服务,并显示结果

AIDL文档

Google文档如下:

Paste_Image.png

1.新建aidl文件
2.实现aidl定义的接口
3.将定义好的接口文件给客户端

新建AIDL文件

aidl文件格式最基本的定义接口,写法和java基本一样,如下:

// Query.aidl
package my.service.calc;

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

interface Query {
    /**
     * 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语言支持的数据类型,Google文档如下:

Paste_Image.png

支持:
1.基本java的数据类型
2.String, Charsequence, List, Map
3.支持实现了Parcelable接口的对象,必须添加对象的aidl定义, 如下:

// Expression.aidl
package my.service.calc;

parcelable Expression;

在相应的包下定义正确的Java类,如下:

package my.service.calc;
/**
 * 代表要计算的表达式
 */
public class Expression implements Parcelable {
    ...
}

使用android studio2.2开发的,新建工程MyAidl_server
然后新建AIDL文件,如下:

Paste_Image.png

写上ICalcService,然后就会新建出AIDL文件,修改接口定义,提供一个设置表达式Expression的方法,一个计算并且返回结果的方法,这里有个知识点, 如下:

AIDL所有的非基本参数都需要一个定向tag来指出数据流通的方式,否则编译不过
in 代表这个参数的数据是由客户端流向给服务端
out 代表这个参数的数据是由服务端流向给客户端
inout 代表双向

后面会结合AIDL生成的Java代表分析,in,out,inout到底具体怎样体现的

我们生成基本的文件,ICalcService.aidl,如下:

// ICalcService.aidl
package my.service.calc;

// Declare any non-default types here with import statements
import my.service.calc.Expression;
import my.service.calc.Result;

interface ICalcService {

    void set(in Expression exp);//输入表达式

    void calc(out Result result);//输出计算结果

    void test(inout Result result);//测试inout的空方法

}
// Expression.aidl
package my.service.calc;
// 代表表达式,包含计算符号,左右2边int数据
parcelable Expression;
// Result.aidl
package my.service.calc;

// 代表最近一次表达式的计算结果
parcelable Result;

然后实现对应的Java类:
表达式类

public class Expression implements Parcelable {
    public static final int TYPE_ADD = 1;
    public static final int TYPE_SUB = 2;

    public int left;
    public int right;
    public int opt;

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

    @Override
    public void writeToParcel(Parcel dest, int flags) {
        dest.writeInt(this.left);
        dest.writeInt(this.right);
        dest.writeInt(this.opt);
    }

    public Expression() {
    }

    protected Expression(Parcel in) {
        this.left = in.readInt();
        this.right = in.readInt();
        this.opt = in.readInt();
    }

    public static final Parcelable.Creator<Expression> CREATOR = new Parcelable.Creator<Expression>() {
        @Override
        public Expression createFromParcel(Parcel source) {
            return new Expression(source);
        }

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

计算结果类

public class Result implements Parcelable {

    private boolean vaild;
    private int result;

    public void set(int result) {
        this.result = result;
        this.vaild = true;
    }

    @Override
    public String toString() {
        return String.format("%d [%s]", result, vaild ? "有效" : "无效");
    }

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

    @Override
    public void writeToParcel(Parcel dest, int flags) {
        dest.writeByte(this.vaild ? (byte) 1 : (byte) 0);
        dest.writeInt(this.result);
    }

    public void readFromParcel(Parcel dest) {
        this.vaild = dest.readByte() != 0;
        this.result = dest.readInt();
    }

    public Result() {
    }

    protected Result(Parcel in) {
        this.vaild = in.readByte() != 0;
        this.result = in.readInt();
    }

    public static final Parcelable.Creator<Result> CREATOR = new Parcelable.Creator<Result>() {
        @Override
        public Result createFromParcel(Parcel source) {
            return new Result(source);
        }

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

IDE帮我们通过AIDL文件,生成了 ICalcService.java文件,格式化后如下:

package my.service.calc;

public interface ICalcService extends android.os.IInterface {
    /**
     * Local-side IPC implementation stub class.
       我们自己的服务必须实现这个Stub class类以提供服务,它是继承的Binder
     */
    public static abstract class Stub extends android.os.Binder implements ICalcService {
        private static final String DESCRIPTOR = "my.service.calc.ICalcService";

        /**
         * Construct the stub at attach it to the interface.
         */
        public Stub() {
            this.attachInterface(this, DESCRIPTOR);
        }

        /**
         * 这里转换为正真的ICalcService接口
         */
        public static ICalcService asInterface(android.os.IBinder obj) {
            if ((obj == null)) {
                return null;
            }
            android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
            //如果是进程内通信的话,我们应该是直接拥有这个服务对象的,可以直接返回使用
            if (((iin != null) && (iin instanceof ICalcService))) {
                return ((ICalcService) iin);
            }
            //如果是2个进程之间相互通信,我们只能拥有remote对象的IBinder句柄,然后通过IBinder机制去交互
            return new Proxy(obj);
        }

        @Override
        public android.os.IBinder asBinder() {
            return this;
        }
        
        /*
        * 这里是真正交互的地方,code代表需要调用那个方法,在文件最后定义了代表相关方法的int值,data是输入参数,reply是输出参数,flags代表的是0代表一次普通的RPC,1代表one_way RPC        */
        @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;
                }
                //第1个方法是in
                case TRANSACTION_set: {
                    data.enforceInterface(DESCRIPTOR);
                    Expression _arg0;
                    //检查参数,如果有将会从客户端来的数据中读取一个输入参数对象
                    if ((0 != data.readInt())) {
                        _arg0 = Expression.CREATOR.createFromParcel(data);
                    } else {
                        _arg0 = null;
                    }
                    //服务调用处理
                    this.set(_arg0);
                    reply.writeNoException();
                    //服务端不会有任何回写操作
                    return true;
                }
                //第2个方法是out
                case TRANSACTION_calc: {
                    data.enforceInterface(DESCRIPTOR);
                    //这里明显区别,不从客户端读取参数,直接new一个新的参数对象
                    Result _arg0;
                    _arg0 = new Result();
                    //服务调用处理
                    this.calc(_arg0);
                    reply.writeNoException();
                    //将结果回写给客户端,数据流向了客户端
                    if ((_arg0 != null)) {
                        reply.writeInt(1);
                        _arg0.writeToParcel(reply, android.os.Parcelable.PARCELABLE_WRITE_RETURN_VALUE);
                    } else {
                        reply.writeInt(0);
                    }
                    return true;
                }
                //第3个方法inout,按照上面分析应该是 1,2情况的合并
                case TRANSACTION_test: {
                    data.enforceInterface(DESCRIPTOR);
                    ///检查参数,如果有将会从客户端来的数据中读取一个输入参数对象
                    Result _arg0;
                    if ((0 != data.readInt())) {
                        _arg0 = Result.CREATOR.createFromParcel(data);
                    } else {
                        _arg0 = null;
                    }
                    //服务调用处理
                    this.test(_arg0);
                    reply.writeNoException();
                    //将结果回写给客户端,数据流向了客户端
                    if ((_arg0 != null)) {
                        reply.writeInt(1);
                        _arg0.writeToParcel(reply, android.os.Parcelable.PARCELABLE_WRITE_RETURN_VALUE);
                    } else {
                        reply.writeInt(0);
                    }
                    return true;
                }
            }
            return super.onTransact(code, data, reply, flags);
        }

        //代理实现,主要还是调用远程的remote对象,代码自己查看本地的
        private static class Proxy implements ICalcService {
           ...
        }

        static final int TRANSACTION_set = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
        static final int TRANSACTION_calc = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1);
        static final int TRANSACTION_test = (android.os.IBinder.FIRST_CALL_TRANSACTION + 2);
    }

    public void set(Expression exp) throws android.os.RemoteException;

    public void calc(Result result) throws android.os.RemoteException;

    public void test(Result result) throws android.os.RemoteException;
}

我在代码注释了一些,包括in out inout不同,体现在onTransact函数中对参数的不同处理方式,以及结果的处理。
我们只要服务端继承ICalcService.Stub进行抽血方法实现,然后通过service的onBind接口返回,就完成了服务端的编写,如下:

@Nullable
    @Override
    public IBinder onBind(Intent intent) {
        return new CalcBinder();
    }

    private class CalcBinder extends ICalcService.Stub {

        private Expression mExp;


        @Override
        public void set(Expression exp) throws RemoteException {
            mExp = exp;
        }

        @Override
        public void calc(Result result) throws RemoteException {
            //计算结果设置给result对象
            if (mExp != null) {
                result.set(ExpressionFactory.create(mExp).getValue());
            }
        }

        @Override
        public void test(Result result) throws RemoteException {
            //empty
        }
    }

这里有个坑,如果有out标记的参数对象,编译时候会报错,必须给参数对象类添加readFromParcel(Parcel dest)以读取来自服务端的结果,如下:

    public void readFromParcel(Parcel dest) {
        this.vaild = dest.readByte() != 0;
        this.result = dest.readInt();
    }

以上服务端就完成了。
客户端工程只要将服务端的AIDL文件,Expression.java, Result.java拷贝过去,就可以了,生成同样的文件,然后bindService即可链接计算服务,如下:

Paste_Image.png

绑定服务->请求求和->请求计算结果,完成上面set calc方法的调用。

小记:  onServiceDisconnected只会在服务进程被意外杀死才会调用,unbindservice并不会回调这个方法

对于进程间通信来说,具体的流程就分为如下几步:

1.Client 发起远程调用请求 也就是RPC 到Binder。同时将自己挂起,挂起的原因是要等待RPC调用结束以后返回的结果

2.Binder 收到RPC请求以后 把参数收集一下,调用transact方法,把RPC请求转发给service端。

3.service端 收到rpc请求以后 就去线程池里 找一个空闲的线程去走service端的 onTransact方法 ,实际上也就是真正在运行service端的 方法了,等方法运行结束 就把结果 写回到binder中。

4.Binder 收到返回数据以后 就唤醒原来的Client 线程,返回结果。至此,一次进程间通信 的过程就结束了

关于IBinder线程问题:

1.客户端请求的时候,会被挂起,等待直到结果返回,所以耗时的请求通信过程不能放在主线程里面
2.经测试服务端方法是binder开启的线程调用的,每次调用线程不一样,所以服务端多个客户端的情况下,要考虑并发

参考大神们博客:
Android 手写Binder 教你理解android中的进程间通信
你真的理解Android AIDL中的in,out,inout么
Android aidl Binder框架浅析

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,185评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,445评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,684评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,564评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,681评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,874评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,025评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,761评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,217评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,545评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,694评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,351评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,988评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,778评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,007评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,427评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,580评论 2 349

推荐阅读更多精彩内容