美文网首页
IPC通信Kotlin实现之AIDL

IPC通信Kotlin实现之AIDL

作者: TodoCoder | 来源:发表于2017-06-02 15:42 被阅读0次

前言

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下载地址

相关文章

网友评论

      本文标题:IPC通信Kotlin实现之AIDL

      本文链接:https://www.haomeiwen.com/subject/bkoyfxtx.html