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