简单梳理一下Binder通信的流程

本文简要梳理了Android进程中利用AIDL编程实现Binder通信的流程,包括对象的演变过程、底层传输机制以及如何在客户端通过IBinder对象调用服务端的方法。重点介绍了TimeBinder类作为服务端返回给客户端的对象,以及客户端如何通过ITime.Stub.asInterface方法将IBinder对象转换为ITime接口对象进行远程方法调用。文章还详细解释了查询本地接口对象和创建代理对象的过程,帮助开发者更好地理解Binder机制。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

关于利用AIDL编程实现进程间通信的例子可以看这篇文章Android进程间通信——AIDL Service

本文主要梳理一下Binder通信的流程,方便理解Binder机制。

还是根据上面这篇文章中的例子来说。

首先看一张图。
Binder通信过程中对象的演变

这张图描述的是Binder通信过程中对象的演变过程。

Remote Service通过IBinder onBind(Intent intent)方法返回一个IBinder接口对象给Client,Client在
void onServiceConnected(ComponentName name, IBinder service)方法中获得这个IBinder接口对象。

这是应用程序开发层面看到的Binder通信过程,画了虚线表示这一过程依赖底层实现。

右侧的流程图则更加详细地描述了这一通信过程:
1)在Remote Service端,定义一个TimeBinder实例。TimeBinder继承自ITime.Stub类,而ITime.Stub类继承自Binder同时实现了ITime接口(关于这些类之间的继承关系后面会说)。这个TimeBinder类就是服务端要返回给客户端的东西。
2)TimeBinder被包装成Parcel对象在底层传输(Binder通信在底层依赖Binder驱动);当客户端收到传过来的Parcel对象后将其解包,恢复成IBinder对象。为什么恢复成IBinder对象而不是TimeBinder对象呢?如果熟悉Java泛型的话,就会知道,泛型编程会丢失对象原来的类型信息。在Parcel中处理的都是IBinder类型的对象,所以从Parcel中读出来的也是IBinder类型的对象。
3)调用ITime.Stub的静态方法asInterface(android.os.IBinder obj),将IBinder对象转换成ITime接口对象。asInterface方法原型为

public static ITime asInterface(android.os.IBinder obj)

4)通过转换得到的ITime接口对象,就可以调用ITime接口中定义的方法了。这样就完成了一次远程调用。

以上是一个粗略的流程,下面结合代码来细看一下。

首先给出各个类和接口之间的关系。
这里写图片描述

结合Android进程间通信——AIDL Service一文中提到的AIDL编程的步骤,我们来看一下。
TimeBinder是在Service中定义的,它继承了ITime.Stub类

 public class TimeBinder extends Stub
    {
        @Override
        public String getCurrentDate() throws RemoteException {
            // 回调方法,返回当前日期
            SimpleDateFormat format=new SimpleDateFormat("yyyy/MM/dd HH:mm:ss");
            String date=format.format(new Date());
            return date;
        }
        @Override
        public long getCurrentTime() throws RemoteException {
            // 回调方法,返回当前毫秒
            return System.currentTimeMillis();
        }
    }

在这个类中具体实现了ITime接口定义的方法,因此它是服务端返回给客户端的对象。

客户端在onServiceConnected方法中获得服务端传过来的IBinder对象

            @Override
            public void onServiceConnected(ComponentName name, IBinder service) {
                // 绑定远程service成功后,回调此方法
                // 通过ITime.Stub类的asInterface方法可以将该代理转换为ITime接口的对象
                binder=ITime.Stub.asInterface(service);
            }

在这儿客户端调用了ITime.Stub.asInterface静态方法将IBinder对象转换成ITime接口对象。ITime.Stub.asInterface方法实现如下:

    public static com.example.customview.ITime asInterface(
            android.os.IBinder obj) {
        if ((obj == null)) {
            return null;
        }
        // 先查找本地有没有这个接口的对象
        android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
        // 如果本地有这个接口的对象
        // 并且这个对象实现了ITime接口
        // 则将其转换成ITime接口对象
        if (((iin != null) && (iin instanceof com.example.customview.ITime))) {
            return ((com.example.customview.ITime) iin);
        }
        // 本地没有这个接口对象,就创建一个代理对象
        return new com.example.customview.ITime.Stub.Proxy(obj);
    }

在这个方法里主要做了两件事:
1)调用queryLocalInterface在本地查找DESCRIPTOR描述的接口的对象,找到了就返回
2)本地没有找到,则创建一个代理对象并返回

queryLocalInterface方法是在IBinder接口中定义的,在Binder类中予以实现:

    public IInterface queryLocalInterface(String descriptor) {
        if (mDescriptor.equals(descriptor)) {
            return mOwner;
        }
        return null;
    }

ITime.Stub类和TimeBinder类也继承了这一方法。

从类图中可以看到,每个Binder类中都有一个IInterface接口对象mOwner,这个就是本地的接口对象。
因为服务端返回的是TimeBinder,它是Binder的子类,所以它的内部也保存了这么一个mOwner。

queryLocalInterface方法中先判断需要查找的接口的descriptor是否和本地接口对象的mDescriptor相等,如果相等,就返回本地接口对象,否则返回null。

Binder里的mOwner是在哪里赋值的呢?
是在attachInterface方法中。看看Binder中对这个方法的实现:

    public void attachInterface(IInterface owner, String descriptor) {
        mOwner = owner;
        mDescriptor = descriptor;
    }

而attachInterface方法是在ITime.Stub方法的构造方法中调用的

public Stub()
{
this.attachInterface(this, DESCRIPTOR);
}

TimeBinder是Stub的子类,那么构造TimeBinder实例的时候就会调用attachInterface方法,给TimeBinder内部的mOwner赋值。
所以这个mOwner在客户端连接之前就已经被赋值了。

继续看queryLocalInterface方法。
如果descriptor不等于mDescriptor,那么返回null。

asInterface方法中继续判断queryLocalInterface的返回值是否是null。
如果返回值不是null,而且返回的对象实现了ITime接口,则将其转换成ITime接口对象并返回;如果返回值是null,则创建一个Proxy对象返回。Proxy也是ITime接口的实现类,就是所谓的代理。

所以说asInterface方法的目的就是想方设法地从IBinder接口对象中得到ITime接口对象。因为得到了ITime接口对象之后就可以调用接口中定义的方法啦。

不过这儿要分两种情况来看:
第一种情况:

        // 如果本地有这个接口的对象
        // 并且这个对象实现了ITime接口
        // 则将其转换成ITime接口对象
        if (((iin != null) && (iin instanceof com.example.customview.ITime))) {
            return ((com.example.customview.ITime) iin);

即本地就有ITime接口的实现对象,那么直接调用其方法即可。

第二种情况:

// 本地没有这个接口对象,就创建一个代理对象
        return new com.example.customview.ITime.Stub.Proxy(obj);

即得到的是一个代理对象,那么调用其方法的时候实际上是调用了该代理对象内部IBinder对象mRemote的transact方法:

@Override public long getCurrentTime() throws android.os.RemoteException
{
android.os.Parcel _data = android.os.Parcel.obtain();
android.os.Parcel _reply = android.os.Parcel.obtain();
long _result;
try {
_data.writeInterfaceToken(DESCRIPTOR);
mRemote.transact(Stub.TRANSACTION_getCurrentTime, _data, _reply, 0);
_reply.readException();
_result = _reply.readLong();
}
finally {
_reply.recycle();
_data.recycle();
}
return _result;
}

总结:
本文只是简单梳理了一下Binder通信的流程和各个类之间的关系,并不涉及系统底层代码。
再画一张方法调用流程图作为结束:

方法调用流程图

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值