前言
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实现中有这个功能,具体请自行下载查看。