關於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機制。
圖片來自網絡搜索