IPC之AIDL的简单使用

参考Android开发艺术探索

AIDL

实现流程

  1. 服务端

    服务端首先要创建一个Service 用来监听客户端的连接请求,然后创建一个AIDL文件,将暴露给客户端的接口在这个AIDL中声明,最后在Service 中实现这个 AIDL接口即可

  2. 客户端

    首先需要绑定服务端的Service,绑定成功之后,将服务端返回的 Binder 对象转成 AIDL 接口所属的类型,接着就可以调用AIDL 中的方法了。

  3. AIDL接口的创建

    具体的aidl 文件的创建方式参考2018-07-17-Binder

代码实现

aidl

Book.aidl

// IMyAidlInterface.aidl
package com.xianjin.loan.aidl;
                
// Declare any non-default types here with import statements
parcelable Book;

IOnNewBookArrivedListener.aidl

// IOnNewBookArrivedListener.aidl
package com.xianjin.loan.aidl;

// Declare any non-default types here with import statements
import com.xianjin.loan.aidl.Book;

interface IOnNewBookArrivedListener {
    /**
     * Demonstrates some basic types that you can use as parameters
     * and return values in AIDL.
     */
    void onNewBookArrived(in Book newBook);
}

IBookManager.aidl

// IBookManager.aidl
package com.xianjin.loan.aidl;

// Declare any non-default types here with import statements
import com.xianjin.loan.aidl.Book;
import com.xianjin.loan.aidl.IOnNewBookArrivedListener;

interface IBookManager {
    List<Book> getBookList();
    void addBook(in Book book);
    void registerListener(IOnNewBookArrivedListener listener);
    void unregisterListener(IOnNewBookArrivedListener listener);
}

服务端

 */
class BookManagerService : Service() {

    /**
     * 线程安全的,系统提供的原子类,我们不需要自己线程同步的问题,内部封装中已经为我们处理好了
     */
    private val mBookList = CopyOnWriteArrayList<Book>()
    private val mIsServiceDestroyed = AtomicBoolean(false)

    /**
     * 专门用来进程通信中处理回调的类,以当前 binder 对象作为 key。
     *
     * 因为在底层实现中,服务端与客户端只有 binder 对象是共有的,其余的对象都是序列化的传输,无法作为key
     */
    private val mListenerList = RemoteCallbackList<IOnNewBookArrivedListener>()


    private val mBinder = object : IBookManager.Stub() {

        /*这里的方法 都是工作在服务端的 Binder 线程池中的,直接同步操作即可*/

        override fun registerListener(listener: IOnNewBookArrivedListener?) {
            mListenerList.register(listener)
        }

        override fun unregisterListener(listener: IOnNewBookArrivedListener?) {
            mListenerList.unregister(listener)
        }

        override fun getBookList(): MutableList<Book> {
            return mBookList
        }

        override fun addBook(book: Book?) {
            mBookList.add(book)
        }
    }

    /**
     * 创建一个线程用来不停的添加新的书籍,并通知所有注册提醒服务的客户端:有新书到达
     */
    private val mWorkerThread = Thread(Runnable {
        while (!mIsServiceDestroyed.get()) {
            Thread.sleep(5000)
            val bookId = mBookList.size + 1
            val book = Book(bookId, "new book #$bookId")
            mBookList.add(book)
            onNewBookArrived(book)
        }
    })

    override fun onCreate() {
        super.onCreate()
        mBookList.add(Book(1, "Android"))
        mBookList.add(Book(2, "iOS"))
        mWorkerThread.start()
    }

    override fun onDestroy() {
        //用来停止 mWorkerThread 线程的标记位
        mIsServiceDestroyed.set(true)
        super.onDestroy()
    }

    override fun onBind(intent: Intent?): IBinder {
        return mBinder
    }

    /**
     * 有新书到达时,通知所有注册提醒服务的客户端
     */
    private fun onNewBookArrived(book: Book) {
        val num = mListenerList.beginBroadcast()
        for (i in 0 until num) {
            val item = mListenerList.getBroadcastItem(i)
            item.onNewBookArrived(book)
        }
        mListenerList.finishBroadcast()
    }
}

客户端

class BookManagerActivity : AppCompatActivity() {


    private var mRemoteBookManager: IBookManager? = null

    /**
     * 服务端通知客户端新书到达的监听回调
     *
     * 此方法最终的执行是在客户端的Binder线程池中执行的
     */
    private val mOnNewBookArrivedListener = object : IOnNewBookArrivedListener.Stub() {
        override fun onNewBookArrived(newBook: Book?) {
            mHandler.obtainMessage(MSG_NEW_BOOK_ARRIVED, newBook).sendToTarget()
        }
    }

    /**
     * 由于mOnNewBookArrivedListener回调的执行是在客户端的Binder线程池中执行的,为了便于UI操作,利用Handler将其切换到客户端的主线程中去执行。
     */
    private val mHandler = Handler(Handler.Callback {
        when (it.what) {
            MSG_NEW_BOOK_ARRIVED -> Log.d(TAG, "receive new book : " + it.obj)
        }
        true
    })


    private val mConnection = object : ServiceConnection {

        /*由于这个两个方法的都是在客户端的UI线程中运行的,所以不可以在这里直接调用服务端的耗时操作*/

        override fun onServiceDisconnected(name: ComponentName?) {
            //todo: 可以在这里处理Binder死亡后重新连接的逻辑
            mRemoteBookManager = null
            Log.e(TAG, "binder died")
        }

        override fun onServiceConnected(name: ComponentName?, service: IBinder?) {
            val bookManager = IBookManager.Stub.asInterface(service)
            mRemoteBookManager = bookManager
            val list = bookManager.bookList
            Log.i(TAG, "query book list , list type: " + list.javaClass.canonicalName)
            Log.i(TAG, "query book list: $list")
            bookManager.addBook(Book(3, "Android 开发艺术探索"))
            val newList = bookManager.bookList
            Log.i(TAG, "query book list: $newList")
            //注册mOnNewBookArrivedListener监听到远程服务端
            bookManager.registerListener(mOnNewBookArrivedListener)
        }

    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_book_manager)
        //绑定服务
        bindService(Intent(this, BookManagerService::class.java), mConnection, Context.BIND_AUTO_CREATE)
    }

    override fun onDestroy() {
        if (mRemoteBookManager != null && mRemoteBookManager!!.asBinder().isBinderAlive) {
            mRemoteBookManager?.unregisterListener(mOnNewBookArrivedListener)
            mRemoteBookManager = null
        }
        unbindService(mConnection)
        super.onDestroy()
    }

    companion object {
        const val TAG = "BookManagerActivity"
        const val MSG_NEW_BOOK_ARRIVED = 1
    }
}

RemoteCallbackList

  • beginBroadcast
  • finishBroadcast

这两个方法必须要配对使用,哪怕是仅仅是想要获取 RemoteCallbackList 中的元素个数

耗时操作

        override fun getBookList(): MutableList<Book> {
            //模拟耗时操作
            SystemClock.sleep(5000)
            return mBookList
        }

那么客户端在调用 getBookList 的时候需要在子线程中处理,以防ANR的出现。

权限验证

  • onBind中做处理
  • onTransact中做处理

处理逻辑基本差不多。

自定义权限:

<permission
    android:name="com.xianjin.loan.permission.ACCESS_BOOK_SERVICE"
    android:protectionLevel="normal"/>

验证代码:

    override fun onBind(intent: Intent?): IBinder? {
        val check = checkCallingOrSelfPermission("com.xianjin.loan.ACCESS_BOOK_SERVICE")
        if (check == PackageManager.PERMISSION_DENIED) {
            return null
        }
        return mBinder
    }

Binder 死亡处理

  • onServiceDisconnected 中重连
  • 设置 DeathRecipient 监听
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 217,657评论 6 505
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,889评论 3 394
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,057评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,509评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,562评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,443评论 1 302
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,251评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,129评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,561评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,779评论 3 335
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,902评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,621评论 5 345
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,220评论 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,838评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,971评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,025评论 2 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,843评论 2 354