IPC通信Kotlin實現之AIDL

前言

Kotlin一個基于 JVM 的新的編程語言,由JetBrains開發。它和java完全兼容,可做android開發用,之前一直不溫不火,在google宣布做為android官方開發語言之前一直是做為小三的存在而不受android開發人員的待見,而如今已轉為正室,其名氣也由之前的鮮為人知到現在的婦孺皆知,曾經的星星之火,如今已有燎原之勢,所過之處,并沒有哀鴻遍野,反倒是破土重生,當然這對于kotlin來說也算是實至名歸吧,做為一名android開發,也要有一顆時刻在追逐新世界的心,也許最終不一定能得到one piece,但這偉大的航路中一路的風光不是對生命最好的詮釋么。扯淡的話就不多說了,咱們言歸正傳,最近也在了解kotlin中,kotlin的好處本文不做探討,本文主要是運用kotlin來實現AIDL機制。

正文

1,概述

PC是Inter-Process Communication的縮寫,即進程間通信或夸進程通信。那什么是進程呢?進程和線程是什么關系呢?操作系統中講,線程是CPU調度的最小單元,同時線程是一種有限的系統資源。而進程一般指一個執行單元。一個進程中至少有一個線程(即主線程),也可以有多個,即進程包含線程。在android中,一個app如沒有特殊需求開啟一個進程,此時的一個app啟動就是開啟一個進程。那什么時候需要進程間通信?比如,在app中獲取系統通訊錄中的信息,在app中開啟了桌面小部件功能時更部件就是跨進程的,再比如,有些app需要盡可能保證不被系統殺死,在app啟動時候開啟一個經量級的進程來監控主進程的運行情況,等等。當然IPC通信的使用場景不止這些,這里不做詳細討論。

AIDL是一個縮寫,全稱是Android Interface Definition Language,也就是Android接口定義語言。但其實上,AIDL只是一個方便系統為我們生成代碼的工具,通過它系統回按照固定的格式生成Binder接口的實現java類,我們不用AIDL語言也完全可做到,AIDL只是簡化了這個過程(關于這一點稍后的文章中會有分析)。

2,使用AIDL

  • 編寫AIDL接口文件和相應的數據類
  • 創建一個Service端用來監聽客戶端的連接請求
  • 創建客戶端并綁服務端

3,實現過程

1. AIDL接口和數據類的創建

AIDL如下代碼

// Book.aidl
//第一類AIDL文件
//這個文件的作用是引入了一個序列化對象 Book 供其他的AIDL文件使用
//注意:Book.aidl與Book.java的包名應當是一樣的
package com.ipcdemo.entity;

parcelable Book;
// IBookManager.aidl
//第二類AIDL文件
//作用是定義方法接口
package com.ipcdemo.entity;

import com.ipcdemo.entity.Book;
interface IBookManager {
    List<Book> getBookList();
    void addBook(in Book book);
}

注意:Book.aidl與Book.java的包名應當是一樣的,當然也有其它方式的處理方法,具體請參考Android:學習AIDL,這一篇文章就夠了(上)

數據類需要實現 Parcelable 接口,實現這個接口,一個類的對象就可以實現序列化并可以通過Intent和Binder傳遞。Book對象的代碼如下:

class Book(var bookId: Int,var bookName: String) : Parcelable {
    constructor(source: Parcel): this(source.readInt(),source.readString())
    /**
     * @return Int 當前對象的描述,幾乎所有情況都是0
     * */
    override fun describeContents(): Int {
        return 0
    }

    /**
     * @param dest 將當前對象寫入序列化結構中(即寫入Parcel類中)
     * @param flags 1:表示當前對象需要作為返回值返回,不能立即釋放,幾乎所有情況都是0
     * */
    override fun writeToParcel(dest: Parcel?, flags: Int) {
        dest?.writeInt(bookId)
        dest?.writeString(bookName)
    }
    /**
     * @param dest Parcel類內部包裝了可序列化數據,可以在Binder中存儲與傳輸數據
     * */
    fun readFromParcel(dest: Parcel): Unit{
        //此處的讀值順序應當是和writeToParcel()方法中一致的
        bookId = dest.readInt()
        bookName = dest.readString()
    }
    /**
     * 用伴生對象和注解@JvmField來標識一個屬性來表示一個java中的靜態對象
     * */
    companion object {
        @JvmField final var CREATOR: Parcelable.Creator<Book> = object : Parcelable.Creator<Book> {
            override fun newArray(size: Int): Array<Book?> {
                return arrayOfNulls(size)
            }

            override fun createFromParcel(source: Parcel): Book {
                return Book(source)
            }

        }
    }
}

注意,只有在JVM 平臺,如果使? @JvmStatic 注解,你可以將伴?對象的成員?成為真正的 靜態?法和字段。

2. 創建一個Service端

  • 創建一個Service并注冊
  • 實現AIDL中的Binder的接口(即Stub)
  • 通過onBind返回這個Binder的實現給客戶端

具體代碼如下:

class KTAIDLService: Service() {
    val TAG = this.javaClass.simpleName
    var mBooks : List<Book> = ArrayList()
    /**
     * 創建?個繼承?IBookManager.Stub類型的匿名類的對象,即實現AIDL中的Binder的接口
     * */
    var mIBookManager = object : IBookManager.Stub() {
        override fun getBookList(): MutableList<Book> {
            return rlock {
                if (mBooks==null) ArrayList<Book>() else mBooks as MutableList<Book>
            }
        }

        override fun addBook(book: Book?) {
            lock {
                if (book != null) {
                    //TODO 此處這個判斷無效
                    if (!(mBooks as MutableList<Book>).contains(book)) {
                        (mBooks as MutableList<Book>).add(book)
                    }
                } else {
                    var booknew = Book(111,"百年孤獨")
                    (mBooks as MutableList<Book>).add(booknew)
                }
            }
        }

    }
    override fun onCreate() {
        Log.i(TAG, "onCreate()")
        var book = Book(222,"毒木圣經")
        (mBooks as MutableList<Book>).add(book)
        super.onCreate()
    }
    override fun onBind(intent: Intent?): IBinder {
        Log.i(TAG, String.format("on bind,intent = %s", intent.toString()))
        return mIBookManager
    }

}

上面Service實現中,在onCreate()方法中初始化添加一本圖書,然后創建一個Binder對象并在onBind中返回它,這個Binder對象繼承了IBookManager.Stub并實現AIDL中的方法,getBookList,addBook(),這里對mBooks對象的操作加鎖是因為,AIDL方法是在服務端的Binder線程池中執行的,當多個客戶端連接時會有多個線程操作同一個方法的情況,所以對mBooks對象的讀寫需要加鎖還有個問題是 (mBooks as MutableList<Book>).contains(book)這個判斷是沒有用的,這里先賣個關子,下文會有說明。

這里的rlock{} ,lock{} 方法用了kotlin中的擴展方法相關的知識,具體實現如下:

/**
 * 給Any添加鎖同步的擴展函數
 * */
fun Any.lock(body: ()->Unit) {
    synchronized(this) {
        body()
    }
}
fun <T>Any.rlock(body: ()-> T): T{
    synchronized(this) {
        return body()
    }
}

上述的lock方法,接收一個無參無返回值Lambda表達式的函數體,對這個函數體進行加鎖。rlock方法接收一個有參無返回值的函數體。

這個Service需要在AndroidMainfest.xml中注冊,如下

<service
       android:name="com.ipcdemo.service.KTAIDLService"
       android:enabled="true"
       android:process=":remotejv">
       <intent-filter>
            <action android:name="com.ipcdemo.service.KTAIDLService" />
       </intent-filter>
 </service>

上述中 android:process = “true” 表示在一個新的進程里面啟動一個服務。

3. 客戶端的實現

  • 綁定一個遠程的服務
  • 綁定成功后將服務返回的Binder對象換成AIDL接口
  • 通這這個AIDL接口去調用遠程的方法
    代碼如下:
class KTClientActivity : AppCompatActivity() {
    val TAG = this.javaClass.simpleName
    //由AIDL生成的類
    private lateinit var mIBookManager: IBookManager
    //標志當前與服務端連接狀況的布爾值,false 為未連接,true
    private var mBound: Boolean = false
    //包含Book對象的List
    private lateinit var mBooks: List<Book>

    val book = Book(333,"天黑以后")

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_ktclient)
        initEvent()
    }

    private fun initEvent() {
        kotlin_addbook.setOnClickListener {
            if (!mBound) {
                attemptToBindService()
                toast("當前與服務端處于未連接狀態,正在嘗試重連,請稍后再試")
                return@setOnClickListener
            }
            
            mIBookManager?.addBook(book)
        }
        kotlin_getbooklist.setOnClickListener {
            if (!mBound) {
                attemptToBindService()
                toast("當前與服務端處于未連接狀態,正在嘗試重連,請稍后再試")
                return@setOnClickListener
            }
            mBooks = mIBookManager?.bookList ?: mBooks
            var sb = StringBuffer()
            for (book in mBooks) {
                sb.append("{"+book.bookId+"},"+"{"+book.bookName+"}\n")
            }
            content.text = sb.toString()
        }
    }
    fun attemptToBindService() {
        val intent = Intent()
        intent.action = "com.ipcdemo.service.KTAIDLService"
        intent.`package`="com.ipcdemo"
        //1,綁定一個遠程的服務
        bindService(intent,mServiceConnection, Context.BIND_AUTO_CREATE)
    }
    var mServiceConnection: ServiceConnection = object: ServiceConnection{
        override fun onServiceDisconnected(name: ComponentName?) {
            Log.i(TAG, "service disconnected")
            mBound = false
        }

        override fun onServiceConnected(name: ComponentName?, service: IBinder?) {
            Log.i(TAG, "service connected")
            //注意:這里的service對象就是KTAIDLService中onBind()方法中返回的AIDL的實現
            //2,將服務返回的Binder對象換成AIDL接口
            mIBookManager = IBookManager.Stub.asInterface(service)
            mBound = true
            mBooks = mIBookManager?.bookList
        }
    }

    override fun onStart() {
        super.onStart()
        if (!mBound) {
            attemptToBindService();
        }
    }

    override fun onStop() {
        super.onStop()
        if (mBound) {
            //解綁遠程服務
            unbindService(mServiceConnection);
            mBound = false;
        }
    }
}

綁定成功后,會通過mIBookManager去調用AIDL中的方法,然后通過AIDL中的方法調用KTAIDLService中實現IBookManager.Stub接口的方法。

效果圖如下

ezgif.com-video-to-gif.gif

對于圖中的問題,KotlinAIDL實現中,每次調用addBook添加一個Book對象,每次在客戶端添加相同的Book對象,在服務端用(mBooks as MutableList<Book>).contains(book),contains這個方法是List中是否有相同的對象,而我們客戶端確確實實是添加同一個Book對象,其中的原因是,對象是不能跨進程直接傳輸的,對象跨進程傳輸的本質都是序列化反序列化的過程,這里的客戶端添加一Book對象,AIDL中Binder會把客戶端傳過來的對象重新轉化并生成一個新的對象。所以這里的contains判斷是無用的,當然具體怎么排重,我想你應該有辦法的,在項目中的java實現中有這個功能,具體請自行下載查看。

本文Demo下載地址

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容