AIDL使用与踩坑部分总结

整理一下AIDL相关的部分信息,也算是总结一下重新回顾一下知识吧~

什么是AIDL?

AIDL(Android Interface Definition Language) Android接口定义语言 利用它定义客户端与服务均认可的编程接口,以便二者使用进程间通信 (IPC) 进行相互通信。实际上起作用的并不是AIDL文件,而是根据AIDL生成的实例代码,AIDL是安卓替我们设计好的一个模板,根据模板生成Interface的代码。

ADIL的存在就是为了实现进程间的通信,通过AIDL我们可以在不同进程中进行数据通信与逻辑交互。

一般使用都是有两个端:客户端与服务端

支持参数类型

  • 基本数据类型:boolean、byte、char、float、double、int、long
  • String 、CharSequence
  • List类型 (包含的数据必须是AIDL支持的类型或者其它声明的AIDL对象)
  • Map类型(包含的数据必须是AIDL支持的类型或者其它声明的AIDL对象)
  • 实现了Parcelable接口的数据类型 (例如:Bundle)
  • AIDL接口本身

(网上人家说 不支持short,说是因为Parcel没有办法对short进行序列化,但是我发现里面的writeValue()中有short的类型的处理。)
但是我一旦在AIDL参数添加了short类型的时候就一直报错再build的时候就一直报错不行
Process 'command 'D:\Android\sdk\build-tools\28.0.3\aidl.exe'' finished with non-zero exit value 1 详细的报的是arguments 报错 可能是参数为short的时候不支持
这个问题如果有懂的同学麻烦下面评论提及一下,大恩不言谢~~

AIDL的使用

  • 先定义两端统一的包名跟接口aidl文件

aidle文件预览.png

IoneAidlInterface.aidl: 暴露给客户端使用的接口类,
(记得要导包,把数据类型或者引用到的实体类所在的包名明确标明导入!!)

package testview.zhen.com.myapplication;
import testview.zhen.com.myapplication.PersonBean;
import testview.zhen.com.myapplication.IPersonBeanCallBack;

interface IOneAidlInterface {
    void basis(int anInt, long aLong, boolean aBoolean, float aFloat,
            double aDouble, String aString);
     PersonBean getPerson();
    void setPerson(in PersonBean a);
    void registerCallback(IPersonBeanCallBack callback);
    void unregisterCallback(IPersonBeanCallBack callback);

}

IPersonBeanCallBack.aidl : 在IoneAidlInterface.aidl里面使用的回调对象声明类

package testview.zhen.com.myapplication;

interface IPersonBeanCallBack {
   void getName(String name);
   void getAge(int age);
}

PersonBean.aidl : 在IoneAidlInterface.aidl里面使用的实体类的一个声明文件,要声明了才能在aidl中使用,该实体类也必须实现接口parcelable

package testview.zhen.com.myapplication;//类所在的包地址
parcelable PersonBean; 

上面的都是aidl格式的文件,下面是实现的PersonBean.kt实体类文件

package testview.zhen.com.myapplication

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

/**
 * Create by ldr
 * on 2019/11/5 16:14.
 */
class PersonBean() :Parcelable{
     var name: String=""
    var age: Int? = null

    constructor(parcel: Parcel) : this() {
        name = parcel.readString()
        age = parcel.readValue(Int::class.java.classLoader) as? Int
    }

    override fun writeToParcel(parcel: Parcel, flags: Int) {
        parcel.writeString(name)
        parcel.writeValue(age)
    }

    override fun describeContents(): Int {
        return 0
    }

    companion object CREATOR : Parcelable.Creator<PersonBean> {
        override fun createFromParcel(parcel: Parcel): PersonBean {
            return PersonBean(parcel)
        }

        override fun newArray(size: Int): Array<PersonBean?> {
            return arrayOfNulls(size)
        }
    }
}
PersonBean.kt所在路径.png

上面有定义的几个文件, 客户端服务端都要有并且包名必须要保持一致

  • 创建服务端

  1. AidlService : 实现 Service
class AidlService :Service(){
    companion object{
        val TAG = "AidlService"
    }
    override fun onBind(intent: Intent?): IBinder? {
        return binder
    }
    //这个适用于回调的使用的
    var remoteCallbackList =  RemoteCallbackList<IPersonBeanCallBack>()

    private val binder = object: IOneAidlInterface.Stub() {
        override fun registerCallback(callback: IPersonBeanCallBack?) {
            remoteCallbackList.register(callback)
         Log.i(TAG,"服务端注册回调")
        }
        override fun unregisterCallback(callback: IPersonBeanCallBack?) {
            remoteCallbackList.unregister(callback)
       Log.i(TAG,"服务端取消回调")
        }
        override fun basis(anInt: Int, aLong: Long, aBoolean: Boolean, aFloat: Float, aDouble: Double, aString: String?) {
                Log.i(TAG,"得到客户端传入的数据anInt=${anInt},aLong=${aLong}" +
                        "aBoolean=${aBoolean} , aFloat=${aFloat}, aDouble=${aDouble}, aString=${aString}")
        }
        override fun getPerson(): PersonBean {
            return  PersonBean().also {
                    it.name = "小明"
                    it.age = 18
            }
        }
        //设置人名的时候回调一下通知给客户端
        override fun setPerson(a: PersonBean?) {
           Log.i(TAG,"得到客户端传入的实体Bean信息名字=${a!!.name},岁数=${a!!.age}")
          //关键代码1
            //从remoteCallbackList获取回调的对象并调用回调对象中的方法
            var n =   remoteCallbackList.beginBroadcast()
             for (i in 0 until n){
                remoteCallbackList.getBroadcastItem(i).getName(a!!.name)
                remoteCallbackList.getBroadcastItem(i).getAge(a!!.age!!)
            }
            remoteCallbackList.finishBroadcast()
        }
//关键代码2 
  //这里用于捕获里面出现的异常,防止服务端有错误的异常直接抛向客户端
        override fun onTransact(code: Int, data: Parcel, reply: Parcel?, flags: Int): Boolean {
            try{
                return  super.onTransact(code, data, reply, flags)
            }catch (e:RuntimeException){
                Log.w(TAG, "Unexpected remote exception", e)
              throw e
            }
        }
    }
}

onbind()的方法中将IOneAidlInterface.Stub()的对象进行绑定。remoteCallbackList则是用于注册回调使用的,callback的注册跟取消注册在这里。
关键代码1:remoteCallbackList.getBroadcastItem(i)用于获取回调对象, beginBroadcastfinishBroadcast 必须配套使用。
关键代码2:这个是当服务端这边自己被调用的函数出现问题,然后让客户端抛出java.lang.NullPointerException的时候进行自己异常的捕获跟打印 不然客户端莫名其妙抛出,但是你服务端自己却没有捕获到异常定位不到异常的抛出真正地方(参考CSDN:https://blog.csdn.net/zxfrdas/article/details/51009691
具体可看一下这篇文章)

  1. AndroidManifest.xml
        <service android:name=".service.AidlService" android:exported="true">
            <intent-filter>
                <action android:name="testview.zhen.com.myapplication.service.aidservice"></action>
                <category android:name="android.intent.category.DEFAULT"></category>
            </intent-filter>
        </service>

android:exported="true"这个属性记得加上去,外部才能访问,并且添加<acition>节点,调用的时候可以使用acition来调用 ,当然你也可以直接指定Service类名方式来绑定Service

  • 创建客户端

class MainActivity : AppCompatActivity() {

    companion object{
        val TAG = "MainActivity"
    }

    var isConnection = false
    lateinit var iOne:IOneAidlInterface
  //关键代码1
    private var servirveConnection = object : ServiceConnection {
        override fun onServiceDisconnected(name: ComponentName?) {
            isConnection =false
        }
//关键代码2
        override fun onServiceConnected(name: ComponentName?, service: IBinder?) {
            iOne =  IOneAidlInterface.Stub.asInterface(service)
        }
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        button1.setOnClickListener {
            if (isConnection) return@setOnClickListener
            iOne.basis(1,1000*1000,true,1.0f,1.00,"hello world")
        }
  //关键代码3
        var intent = Intent().also {
            it.setPackage("testview.zhen.com.myapplication")
            it.action = "testview.zhen.com.myapplication.service.aidservice"
        }
        bindService(intent,servirveConnection,Context.BIND_AUTO_CREATE)
    }

    override fun onDestroy() {
        super.onDestroy()
        if (isConnection) unbindService(servirveConnection)
    }

}

关键代码1 中创建了 servirveConnection对象,其并在匿名内部类中实现的onServiceConnected(name: ComponentName?, service: IBinder?)iOne = IOneAidlInterface.Stub.asInterface(service)iOne实例化 ,最后关键代码3,将intentpackageaction 设置为服务端的service配置信息,传入 bindService() 实现绑定。

以下调用,具体详情代码请看上面的几个类的方法中的具体逻辑

  1. 点击客户端 MainActivity的按钮 button1
        button1.setOnClickListener {
            if (isConnection) return@setOnClickListener
            iOne.basis(1,1000*1000,true,1.0f,1.00,"hello world")
        }

服务端AidlService.kt打印的信息

2019-11-06 16:03:30.277 5195-5210/testview.zhen.com.myapplication I/AidlService: 得到客户端传入的数据anInt=1,aLong=1000000aBoolean=true , aFloat=1.0, aDouble=1.0, aString=hello world

ok~~ 大功告成 基础的调用调通了~

  1. 客户端获取getPerson()返回person对象
        button2.setOnClickListener {
            if (isConnection) return@setOnClickListener
            // 调用Person()方法 获取PersonBean对象
            var person =  iOne.person
            Log.i(TAG,"从服务端获取回来的Person对象信息name = ${person.name},age =${person.age}")
        }

客户端打印的信息:

2019-11-06 17:59:33.702 30666-30666/com.mx.testaidldemo I/MainActivity: 从服务端获取回来的Person对象信息name = 小明,age =18
  1. 注册回调 返回回调信息

3.1 客户端回调方法逻辑

private var  istb:IPersonBeanCallBack.Stub =  object : IPersonBeanCallBack.Stub() {
        override fun getName(name: String?) {
                Log.d(TAG,"获取到回调的名字信息name = ${name}")
        }
        override fun getAge(age: Int) {
            Log.d(TAG,"获取到回调的岁数信息age = ${age}")
        }
    }

3.2 客户端调用注册回调 调用setPerson

        button3.setOnClickListener {
            if (isConnection) return@setOnClickListener
            //注册回调
            iOne.registerCallback(istb)
            Log.i(TAG,"注册回调")
        }
        button4.setOnClickListener {
            if (isConnection) return@setOnClickListener
            iOne.person = PersonBean().apply {
                name = "同志"
                age = 40
            }
        }
        button5.setOnClickListener {
            if (isConnection) return@setOnClickListener
            //取消回调
            iOne.unregisterCallback(istb)
            Log.i(TAG,"取消回调")
        }

点击button3 注册回调
客户端Log:

2019-11-07 10:20:18.654 17960-17960/com.mx.testaidldemo I/MainActivity: 注册回调

服务端Log:

2019-11-07 10:20:18.652 17671-17694/testview.zhen.com.myapplication I/AidlService: 服务端注册回调

点击button4 iOne.person触发服务端回调,
客户端Log:

2019-11-07 10:22:28.017 17960-17960/com.mx.testaidldemo D/MainActivity: 获取到回调的名字信息name = 同志
2019-11-07 10:22:28.018 17960-17960/com.mx.testaidldemo D/MainActivity: 获取到回调的岁数信息age = 40

服务端Log:

2019-11-07 10:22:28.015 17671-17694/testview.zhen.com.myapplication I/AidlService: 得到客户端传入的实体Bean信息名字=同志,岁数=40

点击button5 注销回调
客户端Log:

2019-11-07 10:29:27.351 20514-20514/com.mx.testaidldemo I/MainActivity: 取消回调

服务端Log:

2019-11-07 10:29:27.350 20291-20306/testview.zhen.com.myapplication I/AidlService: 服务端取消回调

AIDL的回调

RemoteCallbackList 并不是一个List ,不能像 List 一样去操作它,遍历RemoteCallbackList必须要以上面AidlService类的使用方式进行,其中 beginBroadcastfinishBroadcast 必须配套使用,哪怕我们仅仅是想要获取 RemoteCallbackList 中的元素个数,

  • 如果你不使用beginBroadcast ,然后直接就去调remoteCallbackList.getBroadcastItem(0)那么它会返回空给你 而不是你所期待的对象,
  • 如果你不使用finishBroadcast 然后操作步骤 绑定->使用->注销->使用,就会抛异常说 java.lang.IllegalStateException: beginBroadcast() called while already in a broadcast!!!

定向TAG

android官网的文章上

All non-primitive parameters require a directional tag indicating which way the data goes . Either in , out , or inout . Primitives are in by default , and connot be otherwise .
所有的非基本参数都需要一个定向Tag来指出数据流通的方式,基本参数的定向Tag默认是并且只能是in

  • in:表示数据只能由客户端流向服务端
  • out:表示数据只能由服务端流向客户端
  • inout:表示数据可在服务端与客户端之间双向流通
    数据流向是针对在客户端中的那个传入方法的对象而言的。in 为定向 tag 的话表现为服务端将会接收到一个那个对象的完整数据,但是客户端的那个对象不会因为服务端对传参的修改而发生变动;out 的话表现为服务端将会接收到那个对象的的空对象,但是在服务端对接收到的空对象有任何修改之后客户端将会同步变动;inout 为定向 tag 的情况下,服务端将会接收到客户端传来对象的完整信息,并且客户端将会同步服务端对该对象的任何变动。

AIDL鉴权

关于鉴权相关的可以看一下简书其它作者的这篇文章//www.greatytc.com/p/69e5782dd3c3

注意事项(各种碰到的大坑小坑)

  1. 在导入以前可以运行的程序出现 AIDL 解析时已到达文件结尾,错误。
    解决: AIDL 中有中文的注释。IDE升级到android studio 3.5以后就会出现这个错误。把所有aidl 的注释删除 运行就可以。

  2. 客户端调用远程服务端的方法时客户端线程会被挂起,如果远程的方法有长时间的耗时操作是会引起我们的客户端ANR现象的,这种情况就要避免在客户端UI线程中访问远程方法;

  3. 当服务端回调客户端的方法时,也要注意耗时的问题。

  4. 由于服务端的方法在服务端的Binder线程池中运行的,方法本身可以进行耗时操作,所以切记不要在服务端方法中开线程进行异步任务;

  5. Binder是会意外死亡的(比如服务被杀了,或者突然中断),每次Aidl意外断开都会调起linkToDeath和onServiceDisconnected方法,只是linkToDeath方法调用在onServiceDisconnected之前,(相关信息跟处理方法,可看看相关的参考有:https://blog.csdn.net/Small_Lee/article/details/79181985
    //www.greatytc.com/p/9af02aa66da9

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

推荐阅读更多精彩内容

  • 一、概述 除了使用 AIDL 进行 IPC 外,我们还可以使用 Messenger 来替代 AIDL。通过在 Me...
    业志陈阅读 575评论 0 2
  • 在讲解AIDL之前,我们需要梳理一下Service的使用,Service是Android系统中的四大组件之一,主要...
    NoBugException阅读 1,886评论 0 9
  • 目录 AIDL用法服务端客户端 AIDL分析2.1 AIDL文件结构2.2 一些概念2.3 AIDL的UML图2....
    blingblingson阅读 879评论 0 1
  • 一、IPC简介 (1)IPC是Inter-Process Communication的缩写,含义为进程间通信或者跨...
    遥遥的远方阅读 7,217评论 0 3
  • 目录0x10 什么是 AIDL0x20 什么时候用 AIDL0x30 需要注意什么0x40 定义 AIDL 接口0...
    zhangweiheb阅读 243评论 0 0