Android Review - Binder机制(二)

前一篇我们从理论上来了解Android的Binder机制,本篇从实战来深入了解Android的Binder机制。

Binder的使用

Binder依赖于Service,在组件(Activity)中通过bindService(),就可以获取Service中的Binder对象,实现Service与Activity的通信。

服务分为本地服务和远程服务,都可以使用Binder。

一:本地服务
在本地服务中使用Binder,只需要两步:

  1. 声明一个Service,重写onBind(),返回一个继承Binder的自定义对象;
  2. 声明一个ServiceConnection,重写onServiceConnected(),获取IBinder对象,然后调用Activity的bindService();

声明Service:

class ServiceWithBinder : Service() {

    private val TAG = "ServiceWithBinder"

    class InnerBinder : Binder() {

        @Throws(RemoteException::class)
        fun multiply(x: Int, y: Int): Int {
            return x * y
        }

        @Throws(RemoteException::class)
        fun divide(x: Int, y: Int): Int {
            return x / y
        }
    }

    override fun onBind(t: Intent): IBinder? {
        Log.e(TAG, "onBind")
        return InnerBinder()
    }
}

在Activity中绑定服务:

fun myBindService() {
   val mServiceConnBinder = object : ServiceConnection {
        override fun onServiceDisconnected(name: ComponentName) {
            Log.e(TAG, "onServiceDisconnected Binder")
            binderInterface = null
        }

        override fun onServiceConnected(name: ComponentName, service: IBinder) {
            Log.e(TAG, "onServiceConnected Binder")
            binderInterface = service as ServiceWithBinder.InnerBinder
            //调用服务的方法
            binderInterface?.multiply(3,2)
        }
    }
    val intentBinder = Intent(this, ServiceWithBinder::class.java)
    //此时使用bindService开启服务
   bindService(intentBinder, mServiceConnBinder, Context.BIND_AUTO_CREATE)
    //销毁服务
   unbindService(mServiceConnBinder)
}

二:远程服务
在远程服务中使用Binder,需要三步:创建aidl、声明Service、调用bindService。

1.创建aidl
aidl是进程间通信接口,需要在客户端(声明Service的应用)和服务端(调用Service的应用)同时声明,并且要完全一致。

首先,在服务端端创建aidl:选中项目目录->右键选中new->然后选中AIDL->填写文件名(比如:ICalcAIDL)->修改aidl的代码。

// ICalcAIDL.aidl
package com.pmm.demo.advanced;

// Declare any non-default types here with import statements
interface ICalcAIDL {
    int add(int x , int y);
    int min(int x , int y );
}

然后,在客户端创建aidl,步骤同上。客户端的aidl要与服务端的一模一样,包括包名、类名、方法。

然后,选中Build->Make Project,编译整个工程,就会在build/generated/source/aidl/debug目录下生产一个名为ICalcAIDL的接口。

2.声明Service
在服务端,声明一个类(比如:MyStub)继承ICalcAIDL.Stub(ICalcAIDL的代理类,系统帮我们生成的),然后声明一个继承Service的类,在onBind()中返回MyStub对象。

class ServiceWithAIDL : Service() {

    private val TAG = "ServiceWithAIDL"

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

        @Throws(RemoteException::class)
        override fun add(x: Int, y: Int): Int {
            return x + y
        }

        @Throws(RemoteException::class)
        override fun min(x: Int, y: Int): Int {
            return x - y
        }
    }

    override fun onBind(t: Intent): IBinder? {
        Log.e(TAG, "onBind")
        return mBinder
    }
}

3.调用bindService
在客户端,声明一个匿名内部类ServiceConnection,重写onServiceConnected(),通过ICalcAIDL.Stub.asInterface(iBinder)获取服务端的ICalcAIDL对象。

fun myBindService() {
   val mServiceConnAIDL = object : ServiceConnection {
        override fun onServiceDisconnected(name: ComponentName) {
            Log.e(TAG, "onServiceDisconnected AIDL")
            mCalcAidl = null
        }

        override fun onServiceConnected(name: ComponentName, service: IBinder) {
            Log.e(TAG, "onServiceConnected AIDL")
            mCalcAidl = ICalcAIDL.Stub.asInterface(service)
           //调用服务的方法
            mCalcAidl?.add(3, 2)
        }
    }
    val intentBinder = Intent(this, ServiceWithAIDL::class.java)
    //此时使用bindService开启服务
   bindService(intentBinder, mServiceConnAIDL, Context.BIND_AUTO_CREATE)
    //销毁服务
    unbindService(mServiceConnAIDL)
}

AIDL机制

aidl是进程间通信接口,它不是Java的类,更像一种协议。它的作用是让AS自动生成Binder类,供客户端调用。

aidl文件编译后,会生成一个Java接口,分为3层:ICalcAIDL、Stub、Proxy。

package com.pmm.demo.advanced;

public interface ICalcAIDL extends android.os.IInterface {
   //具体的Binder类
    public static abstract class Stub extends android.os.Binder implements com.pmm.demo.advanced.ICalcAIDL {
        ·······
           //代理类
        private static class Proxy implements com.pmm.demo.advanced.ICalcAIDL {
            ·······
        }
    }

    //加
    public int add(int x, int y) throws android.os.RemoteException;
    //减
    public int min(int x, int y) throws android.os.RemoteException;
}

下面是生成类ICalcAIDL的结构图

ICalcAIDL的结构图

1.ICalcAIDL
ICalcAIDL就是aidl文件中声明的接口,也就是我们要给客户端调用的功能。

2.Sub
Stub是一个使用ICalcAIDL装饰的Binder类,是我们实际传递给客户端的对象。通过Stub,客户端就可以调用ICalcAIDL的方法。

首先是DESCRIPTOR,默认值是包名+类名,作用是在IBinder中查找ICalcAIDL接口。

private static final java.lang.String DESCRIPTOR = "com.pmm.demo.advanced.ICalcAIDL";

然后是asInterface(),作用是返回Proxy对象,也就是把ICalcAIDL对象返回给客户端。我们注意到一般obj都是对应服务的指引,然后传递给Proxy对象。

public static com.pmm.demo.advanced.ICalcAIDL asInterface(android.os.IBinder obj) {
    if ((obj == null)) {
        return null;
    }
    android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
    if (((iin != null) && (iin instanceof com.pmm.demo.advanced.ICalcAIDL))) {
        return ((com.pmm.demo.advanced.ICalcAIDL) iin);
    }
    //返回Proxy对象
    return new com.pmm.demo.advanced.ICalcAIDL.Stub.Proxy(obj);
}

然后是onTransact(),作用是处理客户端的请求,并将处理结果返回给客户端。

@Override
public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException {
    java.lang.String descriptor = DESCRIPTOR;
    switch (code) {
        //用来指定DESCRIPTOR,从而确定需要通讯的接口
        case INTERFACE_TRANSACTION: {
            reply.writeString(descriptor);
            return true;
        }
        //用来处理ICalcAIDL的add()
        case TRANSACTION_add: {
            //说明data正准备给指定DESCRIPTOR的接口使用
            data.enforceInterface(descriptor);
            int _arg0;
            _arg0 = data.readInt();
            int _arg1;
            _arg1 = data.readInt();
            int _result = this.add(_arg0, _arg1);
            //说明当前操作没有出现异常
            reply.writeNoException();
            reply.writeInt(_result);
            return true;
        }
         //用来处理ICalcAIDL的min()
        case TRANSACTION_min: {
            data.enforceInterface(descriptor);
            int _arg0;
            _arg0 = data.readInt();
            int _arg1;
            _arg1 = data.readInt();
            int _result = this.min(_arg0, _arg1);
            reply.writeNoException();
            reply.writeInt(_result);
            return true;
        }
        default: {
            return super.onTransact(code, data, reply, flags);
        }
    }
}

3.Proxy
Proxy,顾名思义,就是ICalcAIDL的代理类,用来实现ICalcAIDL的方法。Proxy的作用是将需要传递的参数转化为Parcel,从而跨进程传输。

我们来看下核心代码,只显示add(),不显示min方法。

private static class Proxy implements com.pmm.demo.advanced.ICalcAIDL {
    private android.os.IBinder mRemote;

    Proxy(android.os.IBinder remote) {
        mRemote = remote;
    }

    @Override
    public int add(int x, int y) throws android.os.RemoteException {
        android.os.Parcel _data = android.os.Parcel.obtain();
        android.os.Parcel _reply = android.os.Parcel.obtain();
        int _result;
        try {
            //用来标识_data是给含有DESCRIPTOR标志的Binder接口的参数
            _data.writeInterfaceToken(DESCRIPTOR);
            _data.writeInt(x);
            _data.writeInt(y);
            //调用Stub的transact,处理add请求
            mRemote.transact(Stub.TRANSACTION_add, _data, _reply, 0);
            _reply.readException();
            //获取Stub的处理结果
            _result = _reply.readInt();
        } finally {
            _reply.recycle();
            _data.recycle();
        }
        return _result;
    }
}

我们可以看到,前面asInterface(obj)传递进来的对象,是我们代理里真正的执行对象。比如:mRemote.transact(Stub.TRANSACTION_add, _data, _reply, 0);

AIDL其实通过我们写的aidl文件,帮助我们生成了一个接口,一个Stub类用于服务端,一个Proxy类用于客户端调用。

客户端使用AIDL接口的asInterface()获取对应的代理,代理调用对应的方法,方法里都有mRemote.transact()进行具体的调用发消息给服务器,最后服务端对应Binder对象中的onTransact()来处理客户端的请求,并将处理结果返回给客户端。

PS:本文整理自以下博客
安卓移动架构07-Binder核心机
Android aidl Binder框架浅析

若有发现问题请致邮 caoyanglee92@gmail.com

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