AIDL的使用、传递复杂对象以及回调客户端

前言

  • 此教程的目的是教会大家如何使用AIDL,包括定义AIDL服务、调用AIDL服务、传递复杂对象、AIDL回调客户端等。

  • Github地址:https://github.com/daking1991/AIDLDemo

概述

  • 全称Android Interface Definition Language。

  • 像其他IDLs一样,允许你定义编程接口,以便客户端和服务能通过内部进程通信(interprocess communication,IPC)。

定义AIDL服务

  1. 创建.aidl文件
  2. SDK生成对应.java文件和Stub内部类
  3. 通过Service子类将接口暴露给外界

1. 创建.aidl文件

  • 用Java编程语言来构造.aidl文件。每个.aidl文件必须定义一个带方法声明的接口。

  • AIDL支持以下数据类型:

    1. Java基本类型,即int、long、char等;
    2. String;
    3. CharSequence;
    4. List
      • List中的所有元素都必须是AIDL支持的数据类型、其他AIDL接口或你之前声明的Parcelable实现类。
    5. Map
      • Map中的所有元素都必须是AIDL支持的数据类型、其他AIDL接口或你之前声明的Parcelable实现类。
    6. 其他类型,必须要有import语句,即使它跟.aidl是同一个包下。
  • AIDL中的方法和变量

    • 方法可有零、一或多个参数,可有返回值或void。
    • 所有非基本类型的参数都需要标签来表明这个数据的去向:
      1. in,表示此变量由客户端设置;
      2. out,表示此变量由服务端设置;
      3. inout,表示此变量可由客户端和服务端设置;
      4. 基本类型只能是in
    • 只expose方法,不会expose静态变量
  • .aidl文件保存在项目/src/<SourceSet>/aidl目录下。

    // IRemoteService.aidl
    package com.daking.aidl;
    
    // Declare any non-default types here with import statements
    
    interface IRemoteService {
     /**
        * 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);
    }
    

2. SDK生成对应.java文件和Stub内部类

  • 当编译APP时,SDK工具会将项目/src/<SourceSet>/aidl目录下的.aidl文件一个个在项目/build/generated/source/aidl目录下生成IBinder接口.java文件。两个文件名一样,只是后缀不同。如IRemoteService.aidl生成IRemoteService.java

  • Stub内部类

    • .aidl文件编译后生成的.java文件中自动生成的内部类。
    • public static abstract声明。
    • extends android.os.Binder。
    • 实现.aidl文件中定义的接口,且声明其所有方法。
  • 实现Stub内部类要注意

    • 对于传过来的调用,无法保证是在主线程中执行的。Service必须要考虑多线程和线程安全。
    • 默认情况下,RPC都是异步的。避免在主线程中调用AIDL,不然可能会导致ANR。
    • 不能给调用方回抛异常

3. 通过Service子类将接口暴露给外界

  • 需要创建Service子类,并在onBind()中返回Stub内部类。

    package com.daking.aidl;
    
    import android.app.Service;
    import android.content.Intent;
    import android.os.IBinder;
    import android.os.RemoteException;
    
    public class RemoteService extends Service {
        public RemoteService() {}
    
        @Override 
        public IBinder onBind(Intent intent) {
            return mBinder;
        }
    
        private final IRemoteService.Stub mBinder = new IRemoteService.Stub() {
            @Override
            public void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat, double aDouble, String aString) throws RemoteException {
            
            }
        };
    }
    
  • <service>配置,设置exported为true、自定义action名等。

    <service
        android:name=".RemoteService"
        android:enabled="true"
        android:exported="true">
        <intent-filter>
            <action android:name="com.daking.aidl.RemoteService" />
            <category android:name="android.intent.category.DEFAULT" />
        </intent-filter>
    </service>
    

调用AIDL服务

  • 若客户端组件和服务分开在不同APP,那么客户端所在APP的/src/<SourceSet>/aidl目录下必须要有一份.aidl副本。

  • 绑定AIDL服务

    Intent intent = new Intent();
    intent.setPackage("com.daking.aidl");
    intent.setAction("com.daking.aidl.RemoteService");
    bindService(intent, serviceConnection, Context.BIND_AUTO_CREATE);
    
  • 当客户端onServiceConnected()接收到这个AIDL服务返回的IBinder时,必须要将其强制类型转换为YourServiceInterface类型。如

    public void onServiceConnected(ComponentName className, IBinder service) {
        mIRemoteService = IRemoteService.Stub.asInterface(service);
    } 
    
  • 可像普通对象一样调用mIRemoteService。注意,AIDL服务默认是运行在主线程中,若里面有耗时操作,应该在子线程中调用AIDL。

传递复杂对象

  • 在AIDL中传递的复杂对象必须要实现Parcelable接口,这是因为Parcelable允许Android系统将复杂对象分解成基本类型以便在进程间传输。

  • Parcelable实现类

    1. implements Parcelable;
    2. 实现writeToParcel(),它会读取这个对象的当前状态并写入一个包中;
    3. 实现describeContents();
    4. 添加一个实现Parcelable.Creator接口的静态变量CREATOR。
    package com.daking.aidl;
    
    import android.os.Parcel;
    import android.os.Parcelable;
    
    public class RequestVO implements Parcelable {
    
        private String name;
        private String type;
        
        public RequestVO() {}
        
        public RequestVO(Parcel source) {
            super();
            this.setName(source.readString());
            this.setType(source.readString());
        }
        
        public String getName() {
            return name;
        }
        
        public void setName(String name) {
            this.name = name;
        }
        
        public String getType() {
            return type;
        }
        
        public void setType(String type) {
            this.type = type;
        }
        
        public int describeContents() {
            return 0;
        }
        
        public void writeToParcel(Parcel dest, int flags) {
            dest.writeString(name);
            dest.writeString(type);
        }
        
        public static final Parcelable.Creator<RequestVO> CREATOR = new Parcelable.Creator<RequestVO>() {
            public RequestVO createFromParcel(Parcel source) {
                return new RequestVO(source);
            }
            
            public RequestVO[] newArray(int size) {
                return new RequestVO[size];
            }
        };
    }
    
  • 若客户端组件和服务分开在不同APP,必须要把该Parcelable实现类.java文件拷贝到客户端所在的APP,包路径要一致

  • 另外,需要为这个Parcelable实现类定义一个相应的.aidl文件。与AIDL服务接口.aidl同理,客户端所在APP的/src/<SourceSet>/aidl目录下也要有这份副本。

    package com.daking.aidl;
    
    parcelable RequestVO;
    
  • 将复杂对象作为AIDL接口的形参时,记得加上in

    import com.daking.aidl.RequestVO;
    
    interface IRemoteService {
        void request(in RequestVO vo);
    }
    

AIDL服务回调客户端

  1. 自定义回调接口.aidl。

    package com.daking.aidl;
    
    import com.daking.aidl.ResponseVO; // 自定义结构类,具体实现可参考上一节。
    
    interface ICallback {
        void onResult(in ResponseVO vo);
    }
    
  2. AIDL服务.aidl提供接口给客户端注册和注销此回调。

    package com.daking.aidl;
    
    import com.daking.aidl.RequestVO;
    import com.daking.aidl.ICallback;
    
    interface IRemoteService {
        void request(in RequestVO vo);
        void registerCallback(in ICallback cb);
        void unregisterCallback(in ICallback cb);
    }
    
  3. AIDL服务.java的具体实现。

    public class RemoteService extends Service {
        // ICallback列表
        private RemoteCallbackList<ICallback> icallbacks;
    
        @Override
        public IBinder onBind(Intent intent) {
            icallbacks = new RemoteCallback<ICallback>();
            return mBinder;
        }
        
        private final IRemoteService.Stub mBinder = new IRemoteService.Stub() {
            @Override
            public void request(in RequestVO vo) {
                sendResponse();
            }
            @Override
            public void registerCallback(in ICallback cb) {
                if(cb != null) {
                    icallbacks.register(cb);
                }
            }
            @Override
            public void unregisterCallback(in ICallback cb) {
                if(cb != null) {
                    icallbacks.unregister(cb);
                }
            }
        };
        
        private void sendResponse() {
            ResponseVO vo = new ResponseVO();
            
            // 以广播的方式进行客户端回调
            int len = icallbacks.beginBroadcast();
            for (int i = 0; i < len; i++) {
                try {
                    icallbacks.getBroadcastItem(i).onResult(vo);
                } catch (RemoteException e) {
                    e.printStackTrace();
                }
            }
            // 记得要关闭广播
            icallbacks.finishBroadcast();
        }
    }
    
  4. 客户端创建回调接口的实现对象,并注册到AIDL。

    protected ICallback callback = new ICallback.Stub() {
        @Override
        public void onResult(ResponseVO vo) {
            // AIDL回调客户端后的业务处理
        }
    };
    
    // mService为AIDL服务
    mService.registerCallback(callback);
    

我的博客

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

推荐阅读更多精彩内容

  • Jianwei's blog 首页 分类 关于 归档 标签 巧用Android多进程,微信,微博等主流App都在用...
    justCode_阅读 5,905评论 1 23
  • Android中AIDL的基本用法Android 中AIDL的使用与理解Android AIDL使用详解彻底明白A...
    TTTqiu阅读 1,138评论 0 1
  • 上篇文章介绍了IPC机制的基本概念以及简单使用,文章链接:Android 关于IPC机制的理解(一) 这篇文章主要...
    老实任阅读 709评论 0 2
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,637评论 18 139
  • 你说 我睡觉是因为下午要听课 我答 我睡觉是因为昨晚睡得少 其实 睡觉是因为 我不开心 心理老师说 不开心你就睡觉...
    可爱蛋黄派阅读 520评论 0 1