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);
    

我的博客

推荐阅读更多精彩内容

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