通过AIDL和IInterface了解Android的Binder通信机制

关于Binder的IPC的通信方式前前后后看了不少次都没有整理处来思路,所以总是记忆不深刻。正好这次在阅读Android framework中的源码时system_server进程启动了Android系统中重要的服务AMS、WMS、PMS…等都是基于IInterface接口直接实现的,然后在此激发了我再次了解Binder机制的欲望。也只有透彻了解Binder机制才能更容易的分析系统的服务的调用过程。

由于已经对IPC有了一定的了解,本次主要想自己动手通过实现IInterface接口实现一个系统服务供供本进程或者其他进程调用。在Android中我们更常用的实现跨进程访问的方式是AIDL(Android Interface Definition Language 即 Android特定领域语言),这次我们先分析AIDL的实现方式,这有利于我们直接实现IInterface接口实现同样的功能。

1.AIDL的介绍

关于AIDL的介绍Google dev上已经有比较详细的介绍AIDL介绍。我们直接介绍一下AIDL的使用。使用AIDL首先需要使用java语法定义在*.aidl文件中定义接口,在接口定义完成后Build一下会在gen/目录下生成定义接口的实现类,这里类对象可以像普通对象一样直接调用。

1.1创建.aidl文件

通过java语言构建.aidl文件,且每个文件中只能定义一个接口。默认情况下AIDL支持以下数据类型:

  • Java中所有的原始类型(如:int、long、char等)
  • String
  • CharSequence
  • List List中的所有元素必须是以上所支持的类型,其他AIDL生成的接口和自己声明的可序列化类型(实现Parcelable接口的引用类型)
  • Map Map的数据类型要求同List
  • 其他AIDL中定义的接口也可以在其他AIDL文件中使用。

在AIDL文件中定义接口使用的类型必须import导入。接口定义和java中的interface的定义基本一致,但在非原始类型的使用时都需要指示数据的走向的方向标记(in、out、inout)。

  • in方向标示:表示输入型参数(client传递给server端的数据,不能从server端传递给client端)
  • out方向标示:表示输出型参数(server端不能传递到client端,不能从client端传递到sever端)
  • inout方向标示:标示数据可以在Sever和client端双向传递。

不建议在做数据方向标示时都标示为inout,这样会带来更多的性能消耗,还需需要根据实际情况设定方向标示。

在创建AIDL时需要在工程的main/目录下建立子目录aidl(也可以命名为其他名字,如果定位其他名称需要在build.gradle中sourceSets–> main --> aidl.srcDirs= xxxx中指定目录名称)其中存放我们定义的AIDL文件。

在aidl文件中使用非基本数据类型时需要给具体的自定义类在aidl目录中定义相同名字的aidl文件。

AIDL文件的目录如下:

在这里插入图片描述

在定义好AIDL文件后build一下工程会在build/generated/目录下生成和AIDL文件名称一致的java文件(IAnimalManager.aidl生成的文件为IAnimalManager.java)。且生成的java文件不能够编辑,可以在工程中直接像普通类一样调用生成类的方法。

在这里插入图片描述

1.1.1AIDL接口定义示例:

示例很简单定义一个IAnimalManager接口用于实现动物管理服务。其中定义了两个方法addAnimal() 和 getAnimals()并且这里使用了非基本数据类型,非基本数据类型在AIDL的RPC调用过程中需要实现Parcelable接口。下边会介绍Parcel和Parcelable的使用。

IAnimalManager.aidl内容如下:

package com.cike.firstkotlin;
//自定义的类需要自己手动导入,不然build的时会失败
import com.cike.firstkotlin.model.Animal;
// Declare any non-default types here with import statements

interface IAnimalManager {
   //in 标示了数据数据方向,输入型参数
   //在接口中使用了Animal类
   void addAnimal(in Animal animal);

   List<Animal> getAnimals();
}

Animal.kt(这里我用了kotlin语言实现的) :

package com.cike.firstkotlin.model

import android.os.Parcel
import android.os.Parcelable
//Animal类实现了Parcelable接口 用于在RPC过程中传递
open class Animal(val name : String) : Parcelable {
   constructor(parcel: Parcel) : this(parcel.readString())

   override fun writeToParcel(p0: Parcel?, p1: Int) {
       if (p0 != null) {
           p0.writeString(name);
       }
   }

   override fun describeContents(): Int {
       return 0;
   }

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

       override fun newArray(size: Int): Array<Animal?> {
           return arrayOfNulls(size)
       }
   }
}

要在IAnimalManager.aidl文件中定义的接口中使用的Animal类对应的AIDL文件如下:

   // Animal.aidl
   //注意为Animal类定义的对应的AIDL文件的目录要和Animal类的定义的目录一致
package com.cike.firstkotlin.model;

// Declare any non-default types here with import statements
//使用parcelable 关键字进行标示
parcelable Animal;

至此我们要通过AIDL定义的服务的接口定义工作已经完成。现在需要build一下当前工程就可以在Build/generated/目录下找到自动生成的接口实现文件。生成的java文件不可以编辑;

生成的IAnimalManager.java内容如下:

   package com.cike.firstkotlin;
// Declare any non-default types here with import statements
//要是使用Binder机制实现进程间通信就必须实现IInterface接口
public interface IAnimalManager extends android.os.IInterface {
   /**
    * Local-side IPC implementation stub class.
    * Stub抽象类中实现了我们在IAnimalManager中描述的方法
    */
   public static abstract class Stub extends android.os.Binder implements com.cike.firstkotlin.IAnimalManager {
       //服务的Binder的标示符,在RPC调用服务方式时就是通过这个标示符来找到我们实现的服务
       private static final java.lang.String DESCRIPTOR = "com.cike.firstkotlin.IAnimalManager";

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

       /**
        * Cast an IBinder object into an com.cike.firstkotlin.IAnimalManager interface,
        * generating a proxy if needed.
        *此静态方法是供Client端使用的,在客户端在bindService()时会获取到一个服务的IBinder对象,然后通过此方法就可以获取到一个代表服务的引用(有可能是服务的直接引用,也可能是服务的代理对象)。
        由于asInterfacke(、、、)很重要下面会介绍binder的通信机制。
        */
       public static com.cike.firstkotlin.IAnimalManager asInterface(android.os.IBinder obj) {
           if ((obj == null)) {
               return null;
           }
           //通过IBinder的queryLocalInterface()查询现在的调用是不是本地调用,如果是iin将不为null
           android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
           if (((iin != null) && (iin instanceof com.cike.firstkotlin.IAnimalManager))) {
           //如果iin接口不为null且iin是我们定义的服务接口类型则强制类型转换后直接返回。
               return ((com.cike.firstkotlin.IAnimalManager) iin);
           }
           //如果iin为null时创建一个Proxy代理对象并返回
           return new com.cike.firstkotlin.IAnimalManager.Stub.Proxy(obj);
       }

       @Override
       public android.os.IBinder asBinder() {
           return this;
       }

       @Override
       public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException {
           java.lang.String descriptor = DESCRIPTOR;
           switch (code) {
               case INTERFACE_TRANSACTION: {
                   reply.writeString(descriptor);
                   return true;
               }
               case TRANSACTION_addAnimal: {
                   data.enforceInterface(descriptor);
                   com.cike.firstkotlin.model.Animal _arg0;
                   if ((0 != data.readInt())) {
                       _arg0 = com.cike.firstkotlin.model.Animal.CREATOR.createFromParcel(data);
                   } else {
                       _arg0 = null;
                   }
                   this.addAnimal(_arg0);
                   reply.writeNoException();
                   return true;
               }
               case TRANSACTION_getAnimals: {
                   data.enforceInterface(descriptor);
                   java.util.List<com.cike.firstkotlin.model.Animal> _result = this.getAnimals();
                   reply.writeNoException();
                   reply.writeTypedList(_result);
                   return true;
               }
               default: {
                   return super.onTransact(code, data, reply, flags);
               }
           }
       }

       private static class Proxy implements com.cike.firstkotlin.IAnimalManager {
           private android.os.IBinder mRemote;

           Proxy(android.os.IBinder remote) {
               mRemote = remote;
           }

           @Override
           public android.os.IBinder asBinder() {
               return mRemote;
           }

           public java.lang.String getInterfaceDescriptor() {
               return DESCRIPTOR;
           }

           @Override
           public void addAnimal(com.cike.firstkotlin.model.Animal animal) throws android.os.RemoteException {
               android.os.Parcel _data = android.os.Parcel.obtain();
               android.os.Parcel _reply = android.os.Parcel.obtain();
               try {
                   _data.writeInterfaceToken(DESCRIPTOR);
                   if ((animal != null)) {
                       _data.writeInt(1);
                       animal.writeToParcel(_data, 0);
                   } else {
                       _data.writeInt(0);
                   }
                   mRemote.transact(Stub.TRANSACTION_addAnimal, _data, _reply, 0);
                   _reply.readException();
               } finally {
                   _reply.recycle();
                   _data.recycle();
               }
           }

           @Override
           public java.util.List<com.cike.firstkotlin.model.Animal> getAnimals() throws android.os.RemoteException {
               android.os.Parcel _data = android.os.Parcel.obtain();
               android.os.Parcel _reply = android.os.Parcel.obtain();
               java.util.List<com.cike.firstkotlin.model.Animal> _result;
               try {
                   _data.writeInterfaceToken(DESCRIPTOR);
                   mRemote.transact(Stub.TRANSACTION_getAnimals, _data, _reply, 0);
                   _reply.readException();
                   _result = _reply.createTypedArrayList(com.cike.firstkotlin.model.Animal.CREATOR);
               } finally {
                   _reply.recycle();
                   _data.recycle();
               }
               return _result;
           }
       }

       static final int TRANSACTION_addAnimal = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
       static final int TRANSACTION_getAnimals = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1);
   }

   public void addAnimal(com.cike.firstkotlin.model.Animal animal) throws android.os.RemoteException;

   public java.util.List<com.cike.firstkotlin.model.Animal> getAnimals() throws android.os.RemoteException;
}

1.2AIDL接口实现

AIDL的接口文件都已经生成,现在我们需要实现我们的具体服务同时将我们的服务暴露给client端调用。在实现AIDL接口时需要注意一下几个规则:

  • 由于不能保证在主线程上执行传入调用,因此您一开始就需要做好多线程处理准备,并将您的服务正确地编译为线程安全服务。
  • 默认情况下,RPC 调用是同步调用。如果您明知服务完成请求的时间不止几毫秒,就不应该从 Activity 的主线程调用服务,因为这样做可能会使应用挂起(Android 可能会显示“Application is Not Responding”对话框)— 您通常应该从客户端内的单独线程调用服务。
  • 您引发的任何异常都不会回传给调用方。

AIDL定义的接口的具体实现和接口暴露给Client的代码如下:

package com.cike.firstkotlin.activity

import android.app.Service
import android.content.Intent
import android.os.IBinder
import com.cike.firstkotlin.IAnimalManager
import com.cike.firstkotlin.model.Animal

class AnimalManagerService : Service() {
    val store : MutableList<Animal> = ArrayList<Animal>()
        
    //通过onBind()方法将我们的服务暴露给Client以方便Client端调用
    override fun onBind(p0: Intent?): IBinder? {
        println(this.toString() + "onBind()")
        //这里是我们服务的具体实现。
        return  object : IAnimalManager.Stub() {
            //我们定义的方法的具体实现
            override fun getAnimals(): MutableList<Animal> {
                //这就是我们服务具体做的工作
                return store
            }
            
            //我们定义的方法的具体实现
            override fun addAnimal(animal: Animal?) {
                if(animal != null)  {
                    store.add(animal)
                }
                println( "有多少动物:${store?.size} 新添加的动物是${animal?.name}")
            }
        }
    }
}

现在Client端可以通过bindService()方法连接我们定义的服务,客户端可以在onServiceConnected()回调中接受到我们servcie–>onBind()方法返回的Ibinder实例,并通过这个IBinder实例调用服务。

在android系统中每个应用代表一个进程,不同进程具有独立的用户空间。因此其他应用要调用我们定义的服务需要听过Binder机制来完成,所以需要在调用我们服务的应用的src/目录中复制一份我们上边定义的AIDL文件作为客户端访问服务的副本。

下面我们看一下客户端绑定服务的具体实现,MainActivity.kt:

    class MainActivity : AppCompatActivity() {
    
            --------
    
    @SuppressLint("LongLogTag")
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        
        val bindIntent = Intent()
        bindIntent.setClass(this, AnimalManagerService::class.java)
        //绑定服务
        bindService(bindIntent, mServiceConnection, Context.BIND_AUTO_CREATE)
        
        btnAdd?.setOnClickListener() {
            Toast.makeText(this, "addsdfsdfsdfds", Toast.LENGTH_SHORT).show()
            var dog = Animal("小花狗")
            if (mServiceConnection.animalMS != null) {
                //调用服务中定义的方法
                mServiceConnection.animalMS!!.addAnimal(var1)
                mServiceConnection.animalMS?.addAnimal(dog)
            } else {
                Log.e("mServiceConnection-->animalMS", " animalMS is null")
            }
        }
    }
    
    
    object mServiceConnection : ServiceConnection {
        var animalMS: IAnimalManager? = null

        override fun onServiceDisconnected(p0: ComponentName?) {
            animalMS = null
        }

        override fun onServiceConnected(p0: ComponentName?, p1: IBinder?) {
            animalMS = IAnimalManager.Stub.asInterface(p1)
        }
    }
}

下面介绍一下Binder的机制和Parcel的使用。

2.Parcel简介

Parcel提供了一套将对象(Object)序列化的机制,在这套机制设置中有java层和Native层两层组成,可以将序列化之后的数据写入到内核态的的共享内存中,其他进程可以通过Parcel可以从这块共享内存中读取处字节流并反序列化成原有对象。
Parcel可以序列化的数据类型如下:

  • 原始类型
    writeByte(byte)   readByte()
    writeDouble(double)   readDouble()
    writeFloat(float)     readFloat()
    writeInt(int)     readInt()
    writeLong(long)    readLong()
    writeString(String)   readString()
  • 原始数组类
    这类方法用于序列化和反序列化原始数据类型组成的数组。
writeBooleanArray(boolean[])       readBooleanArray(boolean[])    createBooleanArray()
writeByteArray(byte[])   writeByteArray(byte[], int, int), readByteArray(byte[])   createByteArray()
writeCharArray(char[])     readCharArray(char[])   createCharArray() 
writeDoubleArray(double[])     readDoubleArray(double[])   createDoubleArray() 
writeFloatArray(float[])    readFloatArray(float[])     createFloatArray()
writeIntArray(int[])    readIntArray(int[])    createIntArray()
writeLongArray(long[])    readLongArray(long[])    createLongArray()
writeStringArray(String[])    readStringArray(String[])    createStringArray()
writeSparseBooleanArray(SparseBooleanArray)    readSparseBooleanArray()
  • Parcelable类
    遵守Parcelable协议的对象可以通过Parcel来序列化存取(如上边的Animal对象),自定义的类对象要在Binder机制中在两个进程中传递就需要实现Parcelable接口。
writeParcelable(Parcelable,int)readParcelable(ClassLoader)
writeParcelableArray(T[]int)
readParcelable(ClassLoader)

Parcelable和Serializable都是实现序列化并且都可以用于Intent间传递数据,Serializable是Java的实现方式,可能会频繁的IO操作,所以消耗比较大,但是实现方式简单 Parcelable是Android提供的方式,效率比较高,但是实现起来复杂一些 , 二者的选取规则是:内存序列化上选择Parcelable, 存储到设备或者网络传输上选择Serializable(当然Parcelable也可以但是稍显复杂)

3.直接实现IInterface接口和Binder类实现Binder机制的进程间通信

不实用AIDL来实现而是通过直接实现接口的方式来实现进程间通信是为了了解背后的方法调用逻辑。这样有利于再去看AMS、WMS的实现源码时更好理解它的实现,那么下面我贴出自己仿照AIDL生成类的文件手写的文件(一个Car对象的管理服务):

    // 此文件的逻辑和AIDL的生成类的逻辑相同,在此不再做注解
    interface ICarManager : IInterface {
    fun add(car: Car?)
    fun getCars(): MutableList<Car>

    abstract class Stub : Binder, ICarManager {

        constructor() : super() {
            attachInterface(this, DESCRIPTION)
        }

        override fun asBinder(): IBinder {
            return this
        }

        override fun onTransact(code: Int, data: Parcel, reply: Parcel?, flags: Int): Boolean {
            val descriptor: String = DESCRIPTION
            when (code) {
                IBinder.INTERFACE_TRANSACTION -> {
                    reply?.writeString(descriptor)
                    return true
                }
                TRANSACTION_add -> {
                    data.enforceInterface(descriptor)
                    var car: Car?
                    if (data.readInt() != 0) {
                        car = Car.CREATOR.createFromParcel(data)
                    } else {
                        car = null
                    }
                    this.add(car)
                    reply?.writeNoException()
                    return true
                }
                TRANSACTION_getCars -> {
                    data.enforceInterface(descriptor)
                    var list: MutableList<Car> = getCars()
                    reply?.writeNoException()
                    reply?.writeTypedList(list)
                    return true
                }

            }
            return super.onTransact(code, data, reply, flags)
        }

        companion object {
            public val DESCRIPTION: String = "com.cike.firstkotlin.ICarManager"
            private val TRANSACTION_add = IBinder.FIRST_CALL_TRANSACTION + 0
            private val TRANSACTION_getCars = IBinder.FIRST_CALL_TRANSACTION + 1
        }
    }
}

实现Parclalbe接口的被管理类Car(kotlin):

package com.cike.firstkotlin.model

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

class Car(val name : String, val age : Int) : Parcelable {

    override fun writeToParcel(p0: Parcel?, p1: Int) {
        if (p0 != null) {
            p0.writeString(name)
            p0.writeInt(age)
        }
    }

    override fun describeContents(): Int {
        return 0
    }

    companion object CREATOR : Parcelable.Creator<Car> {
        override fun createFromParcel(parcel: Parcel): Car {
            return Car(parcel.readString(), parcel.readInt())
        }

        override fun newArray(size: Int): Array<Car?> {
            return arrayOfNulls(size)
        }
    }
}

ICarManager接口的具体实现继承自Service类的CarManagerService,其中在service的onBind()方法中返回一个ICarManager.Stub抽象类的实现:

package com.cike.firstkotlin.activity

import android.app.Service
import android.content.Intent
import android.os.IBinder
import com.cike.firstkotlin.ibinder.ICarManagerKt
import com.cike.firstkotlin.model.Car

class CarManagerService : Service() {
    var store : MutableList<Car> = ArrayList<Car>()

    override fun onBind(p0: Intent?): IBinder? {
    //返回一个Stub的具体实现,Stub是一个实现了ICarManager接口的抽象类,Stub抽象类的抽象方法在理这里进行了实现
        return object : ICarManager.Stub() {
            override fun add(car: Car?) {
                println("添加了一个汽车")
                if (car != null) {
                    store?.add(car)
                    println("现在有多少汽车${store?.size}")
                }
            }

            override fun getCars(): MutableList<Car> {
                return store
            }
        }
    }
}

服务绑定的逻辑和AIDL的服务绑定方式一样在此不再介绍。下面我们分析一下Binder机制。
Binder机制的结构图
图片来自网络搜索

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章