美文网首页BinderandroidAndroid-源码
Android:从源码角度来赏析Binder机制的优美

Android:从源码角度来赏析Binder机制的优美

作者: SupKing_a520 | 来源:发表于2018-09-20 14:45 被阅读411次

    谈到android进程间通信,就不得不想到Binder,那么他到底是何方圣神呢?话不多说,咱们下面一起来解密一番!

    IBinder

    首先我们来看一下Binder的声明:

    public class Binder implements IBinder {...}
    

    哟,那么IBinder又是什么呢?

    public interface IBinder {
        
        int FIRST_CALL_TRANSACTION  = 0x00000001;
        int LAST_CALL_TRANSACTION   = 0x00ffffff;
        int PING_TRANSACTION        = ('_'<<24)|('P'<<16)|('N'<<8)|'G';
        int DUMP_TRANSACTION        = ('_'<<24)|('D'<<16)|('M'<<8)|'P';
        int SHELL_COMMAND_TRANSACTION = ('_'<<24)|('C'<<16)|('M'<<8)|'D';
        int INTERFACE_TRANSACTION   = ('_'<<24)|('N'<<16)|('T'<<8)|'F';
        int TWEET_TRANSACTION       = ('_'<<24)|('T'<<16)|('W'<<8)|'T';
        int LIKE_TRANSACTION        = ('_'<<24)|('L'<<16)|('I'<<8)|'K';
        int SYSPROPS_TRANSACTION    = ('_'<<24)|('S'<<16)|('P'<<8)|'R';
        int FLAG_ONEWAY             = 0x00000001;
    
        public static final int MAX_IPC_SIZE = 64 * 1024;
        public @Nullable String getInterfaceDescriptor() throws RemoteException;
        public boolean pingBinder();
        public boolean isBinderAlive();
        public @Nullable IInterface queryLocalInterface(@NonNull String descriptor);
        public void dump(@NonNull FileDescriptor fd, @Nullable String[] args) throws RemoteException;
        public void dumpAsync(@NonNull FileDescriptor fd, @Nullable String[] args) throws RemoteException;
        public void shellCommand(@Nullable FileDescriptor in, 
                @Nullable FileDescriptor out, @Nullable FileDescriptor err,
                @NonNull String[] args, @Nullable ShellCallback shellCallback,
                @NonNull ResultReceiver resultReceiver) throws RemoteException;
        public boolean transact(int code, @NonNull Parcel data, @Nullable Parcel reply, int flags) throws RemoteException;
        public interface DeathRecipient {
            public void binderDied();
        }
        public void linkToDeath(@NonNull DeathRecipient recipient, int flags) throws RemoteException;
        public boolean unlinkToDeath(@NonNull DeathRecipient recipient, int flags);
    }
    

    哦,原来IBinder 是一个定义了跨进程传输能力的接口。只要实现了这个接口,就能将这个对象进行跨进程传递。
    这些方法中重点关注 transact(),Binder里面与它对应的是 Binder.onTransact()

    Binder

    Binder是官方提供的实现了IBinder接口的操作,它是 Android IPC 的基础,平常接触到的各种 Manager(ActivityManager, ServiceManager 等),以及绑定 Service 时都在使用它进行跨进程操作。
    下面介绍几个关键方法:

    • transact()
    public final boolean transact(int code, @NonNull Parcel data, @Nullable Parcel reply, int flags) throws RemoteException {
        if (false) Log.v("Binder", "Transact: " + code + " to " + this);
    
        if (data != null) {
            data.setDataPosition(0);
        }
        boolean r = onTransact(code, data, reply, flags);
        if (reply != null) {
            reply.setDataPosition(0);
        }
        return r;
    }
    

    连接Binder驱动,发起IPC请求。

    • onTransact()
    protected boolean onTransact(int code, @NonNull Parcel data, @Nullable Parcel reply, int flags) throws RemoteException {
        if (code == INTERFACE_TRANSACTION) {
            ......
        } else if (code == DUMP_TRANSACTION) {
            ......
        } else if (code == SHELL_COMMAND_TRANSACTION) {
            ......
        }
        return false;
    }
    

    根据 code 对传入的参数 data 做相应的处理,然后写入 reply,这样就能返回操作后的数据。

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

    这个方法的作用是将一个描述符、特定的 IInterface 与当前 Binder 绑定起来,这样后续调用 queryLocalInterface 就可以拿到这个 IInterface,那 IInterface 又是什么呢?

    public interface IInterface{
        public IBinder asBinder();
    }
    

    IInterface 里只定义了一个 asBinder() 方法,这个方法可以返回当前接口关联的 Binder 对象。

    Binder通信机制

    上面只是简单介绍了Binder类相关的信息,具体的详细信息可以去查看源码,但这只是Binder通信机制的基础。
    要理解Binder通信机制,首先我们要先来了解一下Android的内存空间:


    Linux内核模型.png

    用户空间:用户空间的进程是相互独立的,彼此之间不能共享
    内核空间:内核空间的内存是可以共享的,其大小可以通过参数来配置
    Android进程间通信其实质就是利用内核空间共享来完成的。

    下面我们来看Binder进程间通信的四个重要角色:


    Binder通信的四个角色.png
    • Client:用户端进程
    • Server:服务端进程
    • Service Manager:运行在用户空间,它负责管理 Service 注册与查询等操作
    • Binder驱动:负责进程之间Binder通信的建立,进程间通信的核心

    下面我们来总结一下Binder机制的整个运行过程

    1. ServiceManager 初始化
    2. Server 向 ServiceManager 注册自己
    3. Client 获取远程服务
      • Client 向 Server 发起 IPC 请求
      • Binder 驱动将该请求转发给 ServiceManager 进程,
      • ServiceManager 查找到 对应的 Server 的 Binder 引用后通过 Binder 驱动反馈给 Client(并不是实际真实的远程Binder对象,而是Binder驱动里的一个映射)
      • Client 收到 Server 对应的 Binder 引用后,会创建一个 Server 对应的远程服务(即 Server 在当前进程的代理)
    4. Client 通过代理调用 Server
      • Client 会先将请求数据从用户空间拷贝到内核空间,然后调用transact去连接Binder驱动
      • 接着Binder驱动会去连接远程进程,并通知远程进程执行onTransact()函数
    5. Server 返回结果
      • 执行完成后将结果写入内核空间
      • Binder驱动再唤醒客户端线程获取数据

    注意:这个过程中客户端当前线程会被挂起!因此,如果远程进程是执行长时间的运算,请不要使用主线程去调用远程函数,以防止ANR。

    示例分析

    上面讲了一些理论上的Binder进程间通信的知识,可以清晰的看出Binder采用的是C/S架构的模式来进行进程间通信的,而其核心点也就是内核空间的Binder驱动。下面呢,我们会通过一个具体的实例从源码的角度来解析Binder的进程间通信。

    首先我们这里给出一副价值200万的图(一点也不夸张哈!)


    Binder通信架构.png
    1. AIDL(Android Interface Definition Language),即Android接口定义语言。通俗来讲就是定义一个大家都懂的语言,这样进程间交流才不会又障碍。
    2. Stub:接收底层C/C++Binder引用的回调。
    • function:就是我们定义的一些接口方法。
    • Proxy:用来访问底层的Binder驱动。
    1. 本地服务:就是在我们当前进程里的服务(后面会有具体的涉及)。
    2. Binder驱动:这里看图就行了,不做过多解释。

    OK!价值200万的图大家赶紧收好。接下来我们就具体来分析源码。

    一、AIDL创建
    1. 新建com.wf.testaidl.bean.Person类,并实现Parcelable
    public class Person implements Parcelable {...}
    
    1. 在main下面新建aidl文件夹并创建aidl文件(注意:包名和类名必须相同
    目录结构.png
    1. 编写aidl接口
    // Person.aidl
    package com.wf.testaidl.bean;
    
    // Declare any non-default types here with import statements
    parcelable Person;
    

    Preson.aidl做一下映射避免下面文件编译的时候报错。

    // PersonAidl.aidl
    package com.wf.testaidl;
    
    // Declare any non-default types here with import statements
    import com.wf.testaidl.bean.Person;
    
    interface PersonAidl {
        /**
         * Demonstrates some basic types that you can use as parameters
         * and return values in AIDL.
         */
        /*void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat,
                double aDouble, String aString);*/
    
        void addPerson(in Person person);
        List<Person> getPersonList();
    }
    

    这个文件就具体定义了我们需要的接口。

    1. Make Project
      编译成功后会生成app\build\generated\source\aidl\debug\com\wf\testaidl\PersonAidl.java文件。
    二、解析PersonAidl.java源码
    package com.wf.testaidl;
    public interface PersonAidl extends android.os.IInterface {
        /**
         * Local-side IPC implementation stub class.
         */
        public static abstract class Stub extends android.os.Binder implements com.wf.testaidl.PersonAidl {
            ...
            private static class Proxy implements com.wf.testaidl.PersonAidl {...}
            ...
        }
            
        /**
         * Demonstrates some basic types that you can use as parameters
         * and return values in AIDL.
         *//*void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat,
                double aDouble, String aString);*/
        public void addPerson(com.wf.testaidl.bean.Person person) throws android.os.RemoteException;
        public java.util.List<com.wf.testaidl.bean.Person> getPersonList() throws android.os.RemoteException;
    }
    

    是不是很熟悉,没错就是我们上面那幅200万的图。这里可以很清晰的看出PersonAidl里面包含了我们定义的接口方法和一个静态内部抽象类Stub,这个Stub继承了Binder并且实现了PersonAidl接口,这个Stub里面又定义了一个Proxy同样实现了PersonAidl接口。


    Stub
    ok!接下来我们来看看这个神奇Stub。

        /**
         * Local-side IPC implementation stub class.
         */
        public static abstract class Stub extends android.os.Binder implements com.wf.testaidl.PersonAidl {
            private static final java.lang.String DESCRIPTOR = "com.wf.testaidl.PersonAidl";
    
            public Stub() {...}
    
            /**
             * Cast an IBinder object into an com.wf.testaidl.PersonAidl interface,
             * generating a proxy if needed.
             */
            public static com.wf.testaidl.PersonAidl asInterface(android.os.IBinder obj) {...}
    
            @Override
            public android.os.IBinder asBinder() {...}
    
            @Override
            public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException {...}
    
            private static class Proxy implements com.wf.testaidl.PersonAidl {...}
            
            static final int TRANSACTION_addPerson = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
            static final int TRANSACTION_getPersonList = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1);
        }
    

    上面就是这个Stub的整个的一个结构,这里有两个方法名映射的int常量,这个后面我们会用到。
    下面我们就来一步一步的分析它里面的代码。

    1. DESCRIPTOR
    private static final java.lang.String DESCRIPTOR = "com.wf.testaidl.PersonAidl";
    

    这个很简单吧,就是用一个静态常量来存储了我们AIDL的类名,有什么用呢?后面你就知道了。

    1. Stub() 构造函数
    public Stub() {
        this.attachInterface(this, DESCRIPTOR);
    }
    

    构造函数里面就调了一个方法,而这个attachInterface自己没有,那么是谁的呢?没错,是它爹Binder的。

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

    那么,这个方法干了一个什么事儿呢?它就是把自己和这个DESCRIPTOR保存在了当前这个类里,做了一个关联关系,那么有什么用呢?我们继续往后面看。

    1. asInterface()
    public static com.wf.testaidl.PersonAidl asInterface(android.os.IBinder obj) {
        if ((obj == null)) {
             return null;
        }
        android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
        if (((iin != null) && (iin instanceof com.wf.testaidl.PersonAidl))) {
            return ((com.wf.testaidl.PersonAidl) iin);
        }
        return new com.wf.testaidl.PersonAidl.Stub.Proxy(obj);
    }
    

    这个方法主要就干了一个事儿,那就是当发起IPC请求时把我们从Binder驱动拿到的这个IBinder描述转换成一个具体的AIDL接口对象,后面才能调用相应的接口方法。这里面首先调用了一个queryLocalInterface,我们来看看为什么要先调用这个方法呢?

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

    这里可以看出它是拿这个descriptor去判断是不是调用的自己,是就直接返回自己(明白了上面那个attachInterface的作用了吧)。这又是为什么呢?这里就有一个要注意的点了,上面我们给出的那个200万的图是不是有一个本地服务,那么为什么会有一个本地服务呢?这里我就要反问一个问题了:是不是我们需要访问的服务一定是在不同的进程呢?答案是不是的,我们也有可能会访问自己的服务,对吧!好了,那么我想你应该明白了这里为什么会有一个queryLocalInterface了吧。
    好了,我们接着往下看,如果我们访问的不是自己就需要拿这个IBinder描述去创建一个远程的AIDL接口对象。

    1. onTransact()
    @Override
    public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException {
        switch (code) {
            case INTERFACE_TRANSACTION: {
                reply.writeString(DESCRIPTOR);
                return true;
            }
            case TRANSACTION_addPerson: {
                data.enforceInterface(DESCRIPTOR);
                com.wf.testaidl.bean.Person _arg0;
                if ((0 != data.readInt())) {
                    _arg0 = com.wf.testaidl.bean.Person.CREATOR.createFromParcel(data);
                } else {
                    _arg0 = null;
                }
                this.addPerson(_arg0);
                reply.writeNoException();
                return true;
            }
            case TRANSACTION_getPersonList: {
                data.enforceInterface(DESCRIPTOR);
                java.util.List<com.wf.testaidl.bean.Person> _result = this.getPersonList();
                reply.writeNoException();
                reply.writeTypedList(_result);
                return true;
            }
        }
        return super.onTransact(code, data, reply, flags);
     }
    

    这个方法就是来处理我们的IPC请求的,根据不同的code值最终会调到它的实现类的对应的接口方法,然后将处理后的结果写入replay返回给客户端。这里面的代码都很简单,就不一一分析了,相信大家自己也能看明白。

    1. class Proxy这个我们单独放到下一节来讲。
    2. 此刻我想你一定有一个问题:服务不是要注册到ServiceManager嘛,上面没有哪里讲到注册了啊?其实它在我们上面讲的构造函数里面就已经完成了注册,无参构造函数的初始化首先会调用父类的无参构造函数,那我们就来看看它的父类Binder的无参构造函数:
    public Binder() {
        init();
        ...
    }
    
    private native final void init();
    

    从上面我们可以看出它调用了一个native方法,就是在这里进行注册的。那么你又会问了:这里没有传任何数据啊?其实这里native方法它会隐式的把this传过去,那我们之定义的DESCRIPTOR就注册过去了。
    到这里我们的Stub服务端就基本讲完了。



    Proxy
    上面我们剖析了一下Stub的源码,理解到了Stub其实是作为一个服务端注册到ServiceManager来响应我们的请求的。接下来我们就来看我么是怎么样发起请求的呢?首先我们来看一下这个Proxy是什么。

    private static class Proxy implements com.wf.testaidl.PersonAidl {
        private android.os.IBinder mRemote;
    
        Proxy(android.os.IBinder remote) {
            mRemote = remote;
        }
    
        @Override
        public android.os.IBinder asBinder() {
            return mRemote;
        }
    
        public java.lang.String getInterfaceDescriptor() {
            return DESCRIPTOR;
        }
    
        @Override
        public void addPerson(com.wf.testaidl.bean.Person person) throws android.os.RemoteException {...}
    
        @Override
        public java.util.List<com.wf.testaidl.bean.Person> getPersonList() throws android.os.RemoteException { ...}
    }
    

    Proxy作为一个Stub的静态内部类主要用来发起跨进程的请求,那一个进程为什么既有Stub又有Proxy呢?因为一个进程它既可以是客户端也可以是服务端。好了,接下来我们一步步分析Proxy的源码,看看它是怎么工作的。

    1. 构造函数
    Proxy(android.os.IBinder remote) {
        mRemote = remote;
    }
    

    初始化的时候传入一需要访问的进程的IBinder描述,也就是我们发起IPC请求的时候从ServiceManager里面查询寻到的IBinder,这样我们才知道是要去访问哪个进程。

    1. 实现接口方法addPerson()和getPersonList()
    @Override
    public void addPerson(com.wf.testaidl.bean.Person person) throws android.os.RemoteException {
        android.os.Parcel _data = android.os.Parcel.obtain();
        android.os.Parcel _reply = android.os.Parcel.obtain();
        try {
            _data.writeInterfaceToken(DESCRIPTOR);
            if ((person != null)) {
                _data.writeInt(1);
                person.writeToParcel(_data, 0);
            } else {
                _data.writeInt(0);
            }
            mRemote.transact(Stub.TRANSACTION_addPerson, _data, _reply, 0);
            _reply.readException();
        } finally {
            _reply.recycle();
            _data.recycle();
        }
    }
    
    @Override
    public java.util.List<com.wf.testaidl.bean.Person> getPersonList() throws android.os.RemoteException {
        android.os.Parcel _data = android.os.Parcel.obtain();
        android.os.Parcel _reply = android.os.Parcel.obtain();
        java.util.List<com.wf.testaidl.bean.Person> _result;
        try {
            _data.writeInterfaceToken(DESCRIPTOR);
            mRemote.transact(Stub.TRANSACTION_getPersonList, _data, _reply, 0);
            _reply.readException();
            _result = _reply.createTypedArrayList(com.wf.testaidl.bean.Person.CREATOR);
        } finally {
            _reply.recycle();
            _data.recycle();
        }
        return _result;
    }
    

    首先初始化两个Parcel对象,都是通过Parcel.obtain()去Parcel池(队列)里面拿的(节约内存)。然后将我们要访问的进程(DESCRIPTOR)和传递的参数写入Parcel。接下来就调用IBinder引用的transact()去连接 Binder驱动。

    mRemote.transact(Stub.TRANSACTION_addPerson, _data, _reply, 0);
    

    这里有一个很有意思也是最简单的做法,它把要访问的方法定义成了一个int类型的常量TRANSACTION_addPerson和TRANSACTION_getPersonList,这两个常量是定义在它外部类stub里面的,这样IBinder引用拿到后就知道是要去调用哪个方法了。
    接下来我们看看这个transact()做了些什么事情?

    public final boolean transact(int code, @NonNull Parcel data, @Nullable Parcel reply,
                int flags) throws RemoteException {
        if (false) Log.v("Binder", "Transact: " + code + " to " + this);
    
        if (data != null) {
            data.setDataPosition(0);
        }
        boolean r = onTransact(code, data, reply, flags);
        if (reply != null) {
            reply.setDataPosition(0);
        }
        return r;
    }
    

    当Binder驱动拿到我们的请求和数据后,它就会去连接我们要访问的服务端进程,然后通知它回调onTransact()方法。到这里我们的整个请求就算完成了。


    小结

    1. 一个进程既可以是服务端Stub,也可以是客户端Proxy。
    2. 一个IPC请求发起时,首先会调用Proxy去连接Binder驱动,然后Binder驱动再去连接Stub
    3. 要实现跨进程通信,两个进程必须要又相同的AIDL接口
    三、调用流程分析
    1. ServiceManager的注册和获取
      上面我们讲到了Binder进程间通信时所有的Server都时由ServiceManager来管理的,那么你可能会问了ServiceManager也时一个独立进程,那么它是什么时候启动?又是什么时候注册的呢?
      系统启动的时候回去执行\system\core\rootdir\init.rc,在这里面就启动了ServiceManager,启动之后会去执行service_manager.c,那这里面干了什么呢?
    int main(int argc, char **argv)
    {
        struct binder_state *bs;
        void *svcmgr = BINDER_SERVICE_MANAGER;
    
        bs = binder_open(128*1024);
    
        if (binder_become_context_manager(bs)) {
            ALOGE("cannot become context manager (%s)\n", strerror(errno));
            return -1;
        }
    
        svcmgr_handle = svcmgr;
        binder_loop(bs, svcmgr_handler);
        return 0;
    }
    

    打开bind驱动,并且分配128K大小,紧接着把自己注册为Service 大管家,然后就开启loop来读取Binder驱动的消息处理各种服务请求。

    1. 客户端发起IPC请求
    private PersonAidl mPersonAidl;
    //绑定服务
    Intent intent = new Intent(this, AidlService.class);
    bindService(intent, new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            mPersonAidl = PersonAidl.Stub.asInterface(service);
        }
    
        @Override
        public void onServiceDisconnected(ComponentName name) {
            mPersonAidl = null;
        }
    }, BIND_AUTO_CREATE);
    //请求接口
    mPersonAidl.addPerson(person);
    List<Person> personList = mPersonAidl.getPersonList();
    

    上面我们是通过显示意图来绑定的,因为我们是写在一起然后配置在不同进程的,当然你也可以用隐式意图来绑定服务,那么接下来我们就来看看bindService做了什么?

    @Override
    public boolean bindService(Intent service, ServiceConnection conn, int flags) {
        return mBase.bindService(service, conn, flags);
    }
    

    那我们就来看看mBase.bindService又是干了什么?

    public abstract boolean bindService(@RequiresPermission Intent service,
                @NonNull ServiceConnection conn, @BindServiceFlags int flags);
    

    我们可以看到它调用的是一个抽象方法,那么我们就自然要去找到它的实现类,最终我们找到了ContextImpl.java里面。

    @Override
    public boolean bindService(Intent service, ServiceConnection conn, int flags){
        warnIfCallingFromSystemProcess();
        return bindServiceCommon(service, conn, flags, mMainThread.getHandler(), getUser());
    }
    

    那么我再来看看 bindServiceCommon又干了些啥?

    private boolean bindServiceCommon(Intent service, ServiceConnection conn, int flags, Handler handler, UserHandle user){
        ...
        int res = ActivityManager.getService().bindService(...)
        ...
    }
    

    好,到这里就开始跨进程访问了,通过ActivityManagerService去获取远程服务,最终会在ActivityThread.java的handleBindService()方法里回调远程服务的onBind方法返回一个IBinder引用,这样我们就能通过这个IBinder引用去调用相应的接口来进程跨进程访问。后面具体的底层源码这里就不贴出来了,感兴趣的话可以自己去翻阅源码。

    1. 自定义服务端注册
    public class AidlService extends Service {
    
        private List<Person> mPersons = new ArrayList<>();
        private IBinder mIBinder = new PersonAidl.Stub() {
            @Override
            public void addPerson(Person person) throws RemoteException {
                mPersons.add(person);
            }
    
            @Override
            public List<Person> getPersonList() throws RemoteException {
                return mPersons;
            }
        };
    
        @Nullable
        @Override
        public IBinder onBind(Intent intent) {
            return mIBinder;
        }
    }
    

    上面的代码就是我们自定义的一个服务,它里面实现了我们的PersonAidl.Stub,上一节我们分析的Stub源码里的onTransact最终会回调到这里我们具体的实现。并且这里可以看到它里面实现了一个onBind方法来返回这个IBinder引用。

    <service
        android:name=".service.AidlService"
        android:exported="true"
        android:enabled="true"
        android:process=":aidl"/>
    

    上面这段代码就是把我们的服务声明到aidl进程,这里没有什么要说的,都是很标准的写法。

    总结

    好了,到这里呢,我们这次的Binder进程间通信机制的探索就告一段落了。主要掌握以下几点:

    1. Linux内存空间分为用户空间和内核空间,用户空间不能共享,内核空间可以共享;
    2. Binder机制的四个重要角色:Client、Server、ServiceManager、Binder驱动;
    3. Binder机制采用的是C/S架构,Binder驱动是他们之间的桥梁亦是整个架构的核心。
    4. Android中Binder主要的表现形式就是AIDL,重点理解Stub和Proxy模式。

    以上就是我个人对Binder机制的一个理解,如果有说错的或者理解不到位的地方都可以指出来,我们一起学习改正,谢谢!
    最后我们给出举例的源码地址:https://github.com/WangFion/TestAidl

    相关文章

      网友评论

        本文标题:Android:从源码角度来赏析Binder机制的优美

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