Android通信方式篇(八)-Binder机制(Framework层)

Binder在应用层最典型的应用是AIDL,而学习AIDL的调用流程能帮助我们了解Binder在Framework层的工作。这篇文章基于AIDL调用过程进行简单梳理。

为了打印log方便,我这边给到的是自定义AIDL的例子,代码如下:

一、两端公共的Binder方法接口:

IOperation接口

public interface IOperation extends IInterface {
    //Binder描述符
    static final String DESCRIPTOR = "com.zht.aidltest.IOperation";
    //方法标记id,用于区分不同的方法调用
    static final int TRANSACTION_getSum = IBinder.FIRST_CALL_TRANSACTION + 0;
    // 定义了一个供跨进程调用的方法
    public double getSum(double first, double second) throws RemoteException;
}

IInterface是Binder接口基类。此处接口属于定于规范,它只实现了一个asBinder()的方法。那么想要进行AIDL调用的接口,必须要定义在接口中,且该接口继承IInterface。

二、server端:

IOperationStub Binder调用的服务端执行类

public class IOperationStub extends Binder implements IOperation{
    public static final String TAG = "ZHT server";
    public IOperationStub() {
        /**
         * 将一个IInterface类型的接口实例与Binder关联起来,DESCRIPTOR相当于一个标识。当该方法被调用后,
         * 可通过queryLocalInterface(DESCRIPTOR)获取与这个标识相应的接口实例
         */
        this.attachInterface(this, DESCRIPTOR);
    }

    @Override
    public IBinder asBinder() {
        return this;
    }
    /**
     * 该方法运行在服务端,当客户端发起请求,会进入到该方法进行处理
     * code:用于标识客户端请求调用的目标方法(即在IOperation中定义的方法标识符)
     * data:当请求的目标方法含有参数时,该参数封装了请求的参数
     * reply:当请求需要返回结果时,该参数封装了处理结果
     */

    @Override
    protected boolean onTransact(int code, Parcel data, Parcel reply, int flags)
            throws RemoteException {
        // 通过code区分不同的方法调用
        switch (code) {
            case INTERFACE_TRANSACTION:
                reply.writeString(DESCRIPTOR);
                return true;
            case TRANSACTION_getSum:
                Log.d(TAG,"onTransact: TRANSACTION_getSum");
                data.enforceInterface(DESCRIPTOR);
                // 通过data获取请求的参数
                double first = data.readDouble();
                double second = data.readDouble();
                Log.d(TAG,"onTransact: TRANSACTION_getSum first: "+first+"; second: "+second);
                double result = this.getSum(first, second);
                Log.d(TAG,"onTransact: TRANSACTION_getSum result: "+result);
                reply.writeNoException();
                // 将结果写入到reply中
                reply.writeDouble(result);
                return true;
            default:
                break;
        }
        return super.onTransact(code, data, reply, flags);
    }

    @Override
    public double getSum(double fisrt, double second) throws RemoteException {
        Log.d(TAG,"IOperationStub: getSum");
        return 0;
    }

    private static class Proxy implements IOperation {
        private IBinder mRemote;
        Proxy(IBinder mRemote) {
            this.mRemote = mRemote;
        }

        @Override
        public IBinder asBinder() {
            return mRemote;
        }

        public String getInterfaceDescriptor() {
            return DESCRIPTOR;
        }

        @Override
        public double getSum(double fisrt, double second)
                throws RemoteException {
            Log.d(TAG,"Proxy: getSum");
            Parcel data = Parcel.obtain();
            Parcel reply = Parcel.obtain();
            double result = 0;
            try {
                data.writeInterfaceToken(DESCRIPTOR);
                // 将请求的参数写入到data中
                data.writeDouble(fisrt);
                data.writeDouble(second);
                // 调用transact()方法,发起跨进程请求,当前进程挂起
                mRemote.transact(TRANSACTION_getSum, data, reply, 0);
                Log.d(TAG,"Proxy: mRemote.transact");
                // 从reply中获取返回的结果
                reply.readException();
                result = reply.readDouble();
            } catch (Exception e) {
            } finally {
                data.recycle();
                reply.recycle();
            }
            return result;
        }
    }
}

RemoteService Server端真正被客户端需要的服务

public class RemoteService extends Service {
    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        Log.d("ZHT server","calling uid: "+MyBinder.getCallingUid()+"calling pid: "+MyBinder.getCallingPid());
        return new MyBinder();
    }
    class MyBinder extends IOperationStub {

        @Override
        public double getSum(double fisrt, double second) throws RemoteException {
            Log.d("ZHT server","RemoteService: getSum");
            return fisrt + second;
        }
    }
}
三、client端

IOperationProxy Binder调用的客户端执行类

public class IOperationStub extends Binder implements IOperation {
    public static final String TAG = "ZHT client";
    public IOperationStub() {
        this.attachInterface(this, DESCRIPTOR);
    }
    /**
     * 用于将服务端的Binder对象转换成客户端需要的AIDL接口类型的对象(该过程区分进程):
     * client和server处于同一进程:直接返回客户端的Stub对象
     * client和server处于不同进程:返回封装好的Stub.proxy对象
     */
    public static IOperation asInterface(IBinder obj) {
        if (obj == null) {
            return null;
        }
        /**
         * 尝试在本地检索实现了该接口的Binder对象实例
         */
        android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
        if (iin != null && (iin instanceof IOperation)) {
            return ((IOperation) iin);
        }
        /**
         * 假如本地检索返回为null,需要手动生成一个proxy代理类,并在其中通过transact() 方法去处理请求
         */
        return new IOperationStub.Proxy(obj);
    }

    @Override
    public IBinder asBinder() {
        return this;
    }

    @Override
    protected boolean onTransact(int code, Parcel data, Parcel reply, int flags)
            throws RemoteException {
        // 通过code区分不同的方法调用
        switch (code) {
            case INTERFACE_TRANSACTION:
                reply.writeString(DESCRIPTOR);
                return true;
            case TRANSACTION_getSum:
                Log.d(TAG, "onTransact: TRANSACTION_getSum");
                data.enforceInterface(DESCRIPTOR);
                // 通过data获取请求的参数
                double first = data.readDouble();
                double second = data.readDouble();
                Log.d(TAG, "onTransact: TRANSACTION_getSum first: " + first + "; second: " + second);
                double result = this.getSum(first, second);
                Log.d(TAG, "onTransact: TRANSACTION_getSum result: " + result);
                reply.writeNoException();
                // 将结果写入到reply中
                reply.writeDouble(result);
                return true;
            default:
                break;
        }
        return super.onTransact(code, data, reply, flags);
    }

    @Override
    public double getSum(double fisrt, double second) throws RemoteException {
        Log.d(TAG, "IOperationStub: getSum");
        return 0;
    }

    private static class Proxy implements IOperation {
        private IBinder mRemote;
        Proxy(IBinder mRemote) {
            this.mRemote = mRemote;
        }

        @Override
        public IBinder asBinder() {
            return mRemote;
        }
        public java.lang.String getInterfaceDescriptor() {
            return DESCRIPTOR;
        }

        @Override
        public double getSum(double fisrt, double second)
                throws RemoteException {
            Log.d(TAG, "Proxy: getSum");
            Parcel data = Parcel.obtain();
            Parcel reply = Parcel.obtain();
            double result = 0;
            try {
                data.writeInterfaceToken(DESCRIPTOR);
                data.writeDouble(fisrt);
                data.writeDouble(second);
                mRemote.transact(TRANSACTION_getSum, data, reply, 0);
                Log.d(TAG, "Proxy: mRemote.transact");
                reply.readException();
                result = reply.readDouble();
            } catch (Exception e) {
            } finally {
                data.recycle();
                reply.recycle();
            }
            return result;
        }
    }
}

MainActivity

public class MainActivity extends AppCompatActivity {
    private IOperation mIOperation;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Button btn = (Button) findViewById(R.id.btn);
        btn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                try {
                    double result = mIOperation.getSum(1.00, 1.00);
                    Toast.makeText(MainActivity.this, String.valueOf(result), Toast.LENGTH_SHORT).show();
                } catch (RemoteException e) {
                    e.printStackTrace();
                }
            }
        });

        Intent intent = new Intent();
        intent.setAction("com.zht.aidltest.RemoteService");
        intent.setPackage("com.zht.aidltestserver");
        startService(intent);
        bindService(
                intent,
                new ServiceConnection() {
                    @Override
                    public void onServiceConnected(ComponentName name, IBinder service) {
                        mIOperation = IOperationProxy.asInterface(service);
                        Log.d("ZHT client: ", "" + mIOperation + ";name: " + name);
                    }
                    @Override
                    public void onServiceDisconnected(ComponentName name) {
                    }
                },
                Context.BIND_AUTO_CREATE
        );
    }
}

代码非常简单,就是在client项目的MainActivity中点击按钮,调用server项目的RemoteService执行1+1的操作。

先看看执行代码之后,打印是如何走的:

12-23 16:19:00.438 7995-7995/com.zht.aidltestserver D/ZHT server: calling uid: 10064calling pid: 7995

12-23 16:19:00.705 8091-8091/? D/ZHT client:: com.zht.aidltest.IOperationStub$Proxy@dde2115;name: > ComponentInfo{com.zht.aidltestserver/com.zht.aidltest.RemoteService}

12-23 16:20:06.631 8091-8091/com.zht.aidltest D/ZHT client: Proxy: getSum

12-23 16:20:06.632 7995-8006/com.zht.aidltestserver D/ZHT server: onTransact: TRANSACTION_getSum

12-23 16:20:06.632 7995-8006/com.zht.aidltestserver D/ZHT server: onTransact: TRANSACTION_getSum first: 1.0; second: 1.0

12-23 16:20:06.632 7995-8006/com.zht.aidltestserver D/ZHT server: RemoteService: getSum

12-23 16:20:06.632 7995-8006/com.zht.aidltestserver D/ZHT server: onTransact: TRANSACTION_getSum result: 2.0

12-23 16:20:06.635 8091-8091/com.zht.aidltest D/ZHT client: Proxy: mRemote.transact

那么接下来总结下整个调用流程:

1 bindService 绑定远程RemoteService,最终通过ServiceManager获取对应服务的IBinder (startService在此不讨论,默认让RemoteService先起来)

2 IOperationStub.asInterface(service);
asInterface中 obj.queryLocalInterface(DESCRIPTOR);
DESCRIPTOR是我们在IOperation定义的Binder标识符,标识一个IInterface类型的接口实例与Binder的关联。
尝试在本地检索实现了该接口的Binder对象实例,如果客户端和服务端属于同一进程,直接返回客户端的Stub对象,即直接return queryLocalInterface查询的对象,若处于不同进程返回封装好的Stub.proxy对象。
显然此处查询结果是null, return new IOperationStub.Proxy(obj);

3 asInterface结束后, IOperation mIOperation 就被赋值了,它此时代表的就是远程的RemoteService,可以直接调用其方法实现毫无跨进程感知的调用。但是具体怎么做的呢?还得接着往下走

4 点击按钮执行mIOperation.getSum(1.00, 1.00); 因为之前asInterface 返回的是new IOperationStub.Proxy(obj); 那么对应的就是执行Proxy对象中的getSum方法

5 Proxy对象中的getSum方法 有两个Parcel对象,Parcel本身就是可以用于进程间通信的对象,data 用于向server发送数据,reply用于获取server传回的数据。
data写入接口标识DESCRIPTOR 与对应的getSum的两个参数,最终通过ServiceManager获取到的IBinder,执行transact(TRANSACTION_getSum, data, reply, 0); 发起跨进程请求,此处,如果函数定义的是非oneway的,会挂起等待服务端返回结果,如果是oneway的就不会挂起等结果了,TRANSACTION_getSum是函数标识

6 Server端IOperationStub执行onTransact进行客户端请求的处理,通过data获取请求的参数,调用服务端对应的服务方法执行,并将结果写入到reply中。

7 客户端从reply中获取服务端返回的结果。

最后总结一张不成熟的小流程图:

从应用层角度理解,Binder的设计让客户端调用远程函数就像调用本地函数一样,它本身屏蔽了底层所有实现细节。如果应用层开发,了解到这,会用也就够了,但是如果想了解实现机制,那还需要继续往下看。

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

推荐阅读更多精彩内容