AIDL使用与踩坑部分总结

2019-11-07  本文已影响0人  未扬帆的小船

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

什么是AIDL?

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

ADIL的存在就是为了实现进程间的通信,通过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的使用

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 中的元素个数,

定向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

AIDL鉴权

关于鉴权相关的可以看一下简书其它作者的这篇文章https://www.jianshu.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
    https://www.jianshu.com/p/9af02aa66da9

上一篇下一篇

猜你喜欢

热点阅读