【长文预警⚠️】只有 Android 中高级工程师能看懂 Binder 精讲原理——面试篇

前言

年末了,得加紧复习、复习、再复习。

如果有和我一样打算明年春招跳槽的小伙伴可以关注我的【Github】,里面有我从Android 大V 那里收集整理的众多一线互联网大厂的 Android 核心面试知识点。欢迎大家的阅读,如果觉得赞的话,可以在我的Github中点个Star哦!

【长文预警⚠️】只有 Android 中高级工程师能看懂 Binder 精讲原理——面试篇_第1张图片

Github 地址:https://github.com/733gh/xiongfan

⚠️干货预警,前方高能!!!

  • 你是否了解Binder机制?
  • Binder这么好用,那为什么Zygote的IPC通信机制用Socket而不用Binder?
  • 为什么说Binder是安全的?
  • Intent跨进程传大图为什么会崩溃?
  • AIDL的oneWay和非oneway有什么区别?
  • 本文将针对以上问题进行原理分析
【长文预警⚠️】只有 Android 中高级工程师能看懂 Binder 精讲原理——面试篇_第2张图片

一、IPC机制

Inter-Process Communication 简称 IPC ,即为进程间通信。Android与Liunx都有自己的IPC机制。虽然说Android是继承自Linux的,但是IPC通信机制并不是完全继承的。如果要你设计一套进程间通信,你会如何设计,数据简单传递流程是如何的?

【长文预警⚠️】只有 Android 中高级工程师能看懂 Binder 精讲原理——面试篇_第3张图片
image

Linux

  • 管道

    继承自Unix,它是 Unix早期的一个重要通信机制。主要思想是,在内存中创建一个共享文件,从而使通信双方利用这个共享文件来传递信息。这个文件本质是内核缓冲区( 4k ),通过pipe系统函数调用即可得到描述符,创建管道,一个用来读,一个用来写。通信方式为半双工,数据自己读不能写,数据读走后,便不在管道中,不能反复读取。模型如下:

    【长文预警⚠️】只有 Android 中高级工程师能看懂 Binder 精讲原理——面试篇_第4张图片
  • 信号

    举个例子,我们在电脑上按 Ctrl + C 会中断程序,这是通过信号机制来停止了程序。信号就是一种异步通信方式,内核可以利用信号来通知用户空间的进程发生了哪些系统事件,但是不适用于信息交换。每个信号都有一个名字和编号,名字一般都以SIG开头。例如Android里面的杀掉进程方法。

  • 信号量

    信号量是一个计数器,用来控制多个进程对共享资源的访问,它作为一种锁机制,防止某进程正在访问共享资源时,其它进程也访问该资源,主要作为进程间以及同一进程内不同线程之间的同步手段,通过控制这种资源的分配来实现互斥和同步的。

  • 消息队列

    双链表结构,存在内存中,由一个或多个进程读写信息,信息会复制两次,对于频繁、信息量大的通信不宜使用消息队列。

  • 共享内存

    多个进程可以直接读写的一块内存,优点在于内核空间专门留出了一块内存可以由需要访问的进程映射到自己的私有地址空间,进程就可以不需要进行数据拷贝,而直接读写数据。

    【长文预警⚠️】只有 Android 中高级工程师能看懂 Binder 精讲原理——面试篇_第5张图片
  • 套接字

    套接字可用于不同机器之间的进程通信,例如Socket,全双工的,即可读又可写,可以用在两个无亲缘关系的进程间。

Android

  • AIDL

    Android interface definition Language 即 Android 接口定义语言,专门用来处理进程间通信

  • Messenger

    以串行的方式来处理客户端发来的消息,如果有大量消息发送到服务端,服务端仍然一个一个的处理再相应客户端显然不合适,并且在进程间只能用于数据传递,不能像AIDL一样,支持方法调用。

  • Bundle

    Bundle实现了Parcelable 接口,所以它可以方便的在不同进程间传输。Activity、Service、Receive都是在Intent中通过Bundle来进行数据传递的。

  • 文件共享

  • ContentProvider

    ContentProvider为存储和获取数据了提供统一的接口,它可以在不同的应用程序之间共享数据,本身就是适合进程间通信的。ContentProvider底层实现也是Binder。例如通讯录,音视频等,这些操作本身就是跨进程进行通信。

  • Binder

    对于消息队列、Socket、管道而言,数据先从发送方的缓存区拷贝到内核开辟的缓存区中,再从内核缓存区拷贝到接收方的缓存区,一共拷贝两次,如下图

    【长文预警⚠️】只有 Android 中高级工程师能看懂 Binder 精讲原理——面试篇_第6张图片

    对于Binder来说,数据从发送方的缓存区拷贝到内核的缓存区,而接收方的缓存区与内核的缓存区是映射到一块物理地址的(其实是拿到描述符后同时映射到两个进程的空间,一个进程写,另一个进程就能读到)。如下图

    【长文预警⚠️】只有 Android 中高级工程师能看懂 Binder 精讲原理——面试篇_第7张图片

    这个就体现出了Binder的性能比Socket好,少了一次数据拷贝,那Binder的安全体现在哪呢?

    首先Socket或命名管道,别人知道IP地址或者管道名就可以和它进行连接,写数据,这里容易被恶意使用,没有对使用者的信息进行验证。这种信息要想不出问题,只能由IPC机制本身在内核态添加,例如Binder,它就是这么做的,Android里面这种身份信息标识其实就是UID。

    这里要留意一下,一般Binder不会传递太大的数据,然而通过文件跨进程传递数据,效率有点低,作为内存优化手段可以选择使用MemoryFile,这个类是Android匿名共享内存里面的高性能文件类。

  • Scoket

    常见于Zygote 和 AMS通信,Zygote接受AMS的请求就是用的Scoket,init进程 fork 出 zygote进程后,会执行Zygote的main函数,内部会注册一个本地Scoket,然后进入循环来处理数据。

二、Binder驱动

1、Android 启动时 Binder 通信流程

【长文预警⚠️】只有 Android 中高级工程师能看懂 Binder 精讲原理——面试篇_第8张图片
image
  • Android 底层 Linux启动之后,用户空间启动的第一个进程为 init 进程

  • init 进程 加载 init.rc 配置,通过 fork + exec 系统启动配置文件定义的系统服务,包括 Zygote进程、ServiceManager进程、SurfaceFlinger进程等

  • Zygote进程启动 ( frameworks/base/cmds/app_process/app_main.cpp )。 启动完虚拟机、注册好JNI 函数后,会继续启动 SystemServer 进程,也就是说会执行SystemServer进程的main函数

    com.android.server.SystemServer.java
    public static void main(String[] args) {
          new SystemServer().run();
    }
    //这里就会启动 AMS WMS PMS 等服务
    private void run() {
    .....
        // 开机引导服务、核心服务、其他服务
        startBootstrapServices();
        startCoreServices();
        startOtherServices();
    .....
     }
    
    

    这里以AMS 为例,创建好AMS实例后会调用它的以下方法

    // ActivityManagerService extends IActivityManager.Stub
     public void setSystemProcess() {
            try {
                ServiceManager.addService(Context.ACTIVITY_SERVICE, this, true);
                ServiceManager.addService(ProcessStats.SERVICE_NAME, mProcessStats);
                ServiceManager.addService("meminfo", new MemBinder(this));
                ServiceManager.addService("gfxinfo", new GraphicsBinder(this));
                ServiceManager.addService("dbinfo", new DbBinder(this));
                if (MONITOR_CPU_USAGE) {
                    ServiceManager.addService("cpuinfo", new CpuBinder(this));
                }
                .....
      }
    

    可以看到这里会将AMS和一些其它服务注册到ServiceManager里面

    我们继续看一下是如何注册的

    android.os.ServiceManager.java
    //addService
    public static void addService(String name, IBinder service) {
         try {
             getIServiceManager().addService(name, service, false);
         } catch (RemoteException var3) {
            Log.e("ServiceManager", "error in addService", var3);
         }
    ​
    }
    //getService
    private static IServiceManager getIServiceManager() {
            if (sServiceManager != null) {
                return sServiceManager;
            } else {
            //重点 这里通过 BinderInternal 拿到一个Native的 BpBinder,然后转换成Java层的BinderProxy对象
                sServiceManager = ServiceManagerNative.asInterface(BinderInternal.getContextObject());
                return sServiceManager;
            }
        }
    
       //ServiceManagerNative.asInsterface
           static public IServiceManager asInterface(IBinder obj)
        {
            if (obj == null) {
                return null;
            }
            IServiceManager in =
                (IServiceManager)obj.queryLocalInterface(descriptor);
            if (in != null) {
                return in;
            }
    ​
            return new ServiceManagerProxy(obj);
        }
    

    看到这里,大概就清楚了,从底层拿到BpBinder,然后转换成BinderProxy对象,调用addService方法,放入到ServiceManager 中管理。addService 方法对应于 native 层的 frameworks/native/libs/IServicManager.cpp 中的 addService 方法,后面在对 SurfaceFlinger 注册服务 会分析此方法。

  • ServiceManager进程启动。我们直接看它的main函数,里面会打开Binder驱动、映射内存,开启Binder Loop循环,等待Client 和 Service 的请求 (这里说的Service是指系统服务)

    frameworks/native/cmds/sedrvicemanager/service_manager.c
    int main(int argc, char** argv)
    {
    ....
        if (argc > 1) {
            driver = argv[1];
        } else {
            driver = "/dev/binder";
        }
    ....
        //1.打开 Binder 驱动 映射内存 128 k
        bs = binder_open(driver, 128*1024);
    ​
    .....
    // 2\. Binder 成为了上下文的管理者,告诉Binder驱动,我就是ServiceManager,注册、查询都找我
        if (binder_become_context_manager(bs)) {
            ALOGE("cannot become context manager (%s)\n", strerror(errno));
            return -1;
        }
    ....
    //3.开启binder loop循环 处理消息
        binder_loop(bs, svcmgr_handler);
    ​
        return 0;
    }
    ​
    frameworks/native/cmds/sedrvicemanager/binder.c
    void binder_loop(struct binder_state *bs, binder_handler func)
    {
       ......
    ​
        readbuf[0] = BC_ENTER_LOOPER;
        binder_write(bs, readbuf, sizeof(uint32_t));
    
        //4.for 循环 把 binder驱动发来的数据读过来 ,然后 binder_parse 处理请求
        for (;;) {
            bwr.read_size = sizeof(readbuf);
            bwr.read_consumed = 0;
            bwr.read_buffer = (uintptr_t) readbuf;
    ​
            res = ioctl(bs->fd, BINDER_WRITE_READ, &bwr);
    ​
            if (res < 0) {
                ALOGE("binder_loop: ioctl failed (%s)\n", strerror(errno));
                break;
            }
    ​
            res = binder_parse(bs, 0, (uintptr_t) readbuf, bwr.read_consumed, func);
           .......
        }
    }
    
    

    这里说明一下,binder_open 中会通过mmap 内存映射 128 k 的内存空间,但是这仅仅在 ServiceManager 进程中,其它Binder 服务进程 BINDER_VM_SIZE ((1 * 1024 * 1024) - sysconf(SC_PAGE_SIZE) * 2)

    的内存空间,

    SC_PAGE_SIZE 为 一个 page 页大小, 估摸着为1M - 8K的样子。

    那为什么网上有人说Binder内存限制是1M - 8K,而一次调用最大传输数据只有 507 K呢?

    这是因为 Binder 线程池数量默认是 15 个 (不计 某个指令创建的 Binder主线程,否则为 16 个),15 个线程共享这么多内存空间,所以实际传输大小并没有这么大。

  • SurfaceFlinger 进程启动

    frameworks/native/services/surfaceflinger/main_surfaceflinger.cpp
    int main(int, char**) {
        startHidlServices();
    ​
        signal(SIGPIPE, SIG_IGN);
        //1\. Binder线程池数量设置为了4
        // When SF is launched in its own process, limit the number of
        // binder threads to 4.
        ProcessState::self()->setThreadPoolMaxThreadCount(4);
        //2.启动线程池
        // start the thread pool
        sp ps(ProcessState::self());
        ps->startThreadPool();
        //3.初始化 SurfaceFlinger
        // instantiate surfaceflinger
        sp flinger = new SurfaceFlinger();
    ​
        setpriority(PRIO_PROCESS, 0, PRIORITY_URGENT_DISPLAY);
    ​
        set_sched_policy(0, SP_FOREGROUND);
    ......
    ​
        // initialize before clients can connect
        flinger->init();
    ​
    //4\. 重点来了,发布 SurfaceFlinger到 ServiceManager 中
        // publish surface flinger
        sp sm(defaultServiceManager());
        sm->addService(String16(SurfaceFlinger::getServiceName()), flinger, false);
    ​
        // publish GpuService
        sp gpuservice = new GpuService();
        sm->addService(String16(GpuService::SERVICE_NAME), gpuservice, false);
    .......
    ​
        // run surface flinger in this thread
        flinger->run();
    ​
        return 0;
    }
    

    我们可以看到上述代码的第 4 处 注释,它会发布 SurfaceFlinger 服务到 ServiceManager 中,那你有可能会想,ServiceManager 进程还没有启动完成,SurfaceFlinger 进程就来获取呢?

    frameworks/native/libs/IServiceManager.cpp
    sp defaultServiceManager()
    {
        if (gDefaultServiceManager != NULL) return gDefaultServiceManager;
    ​
        {
            AutoMutex _l(gDefaultServiceManagerLock);
            while (gDefaultServiceManager == NULL) {
                gDefaultServiceManager = interface_cast(
                    ProcessState::self()->getContextObject(NULL)); // 1\. 这里拿到的是一个BpBinder对象
                if (gDefaultServiceManager == NULL)
                    sleep(1);
            }
        }
    ​
        return gDefaultServiceManager;
    }
    

    可以看到获取 gDefaultServiceManager 利用了 while循环,会知道获取到后才会停止

    我们再看看注释 4 处的 addService 代码 (getService 类似)

        virtual status_t addService(const String16& name, const sp& service,
                bool allowIsolated)
        {
            Parcel data, reply;
            data.writeInterfaceToken(IServiceManager::getInterfaceDescriptor());
            data.writeString16(name);
            //1\. 把服务写到 Parcele 里了
            data.writeStrongBinder(service);
            data.writeInt32(allowIsolated ? 1 : 0);
            //2\. 发送请求
            status_t err = remote()->transact(ADD_SERVICE_TRANSACTION, data, &reply);
            return err == NO_ERROR ? reply.readExceptionCode() : err;
        }
    

    注释 2 处 的 transact 调用的是IPCThreadState中的代码,真正底层交互还是通过 IPCThreadState 来的

    status_t IPCThreadState::transact(int32_t handle,
                                      uint32_t code, const Parcel& data,
                                      Parcel* reply, uint32_t flags)
    
  • 最后贴一个Android系统启动流程图

    【长文预警⚠️】只有 Android 中高级工程师能看懂 Binder 精讲原理——面试篇_第9张图片

2、Binder通信分层架构

【长文预警⚠️】只有 Android 中高级工程师能看懂 Binder 精讲原理——面试篇_第10张图片
  • 对于BinderProxy ,我们通过上一小节中 Zygote 进程启动 的 ServiceManagerNative.asInsterface 方法可以看到,拿到远程Binder实体后,往往会封装一层,比如该 asInsterface 方法返回的 ServiceManagerProxy ,这就是一个BinderProxy 对象

  • BpBinderBinderProxy 差不多,一个是Native 层,一个是Java 层的,BpBinder 内部持有了一个 binder 句柄 handler,我们直接看上一小节 分析的 SurfaceFlinger 进程启动中 的 defaultServiceManager 中的 拿 Binder 的 getContextObject 方法

    sp ProcessState::getContextObject(const sp& /*caller*/)
    {
        return getStrongProxyForHandle(0);
    }
    ​
    sp ProcessState::getStrongProxyForHandle(int32_t handle)
    {
        sp result;
    ​
        AutoMutex _l(mLock);
    ​
        handle_entry* e = lookupHandleLocked(handle);
    ​
        if (e != NULL) {
            IBinder* b = e->binder;
            if (b == NULL || !e->refs->attemptIncWeak(this)) {
                if (handle == 0) {
    ​
                    Parcel data;
                    status_t status = IPCThreadState::self()->transact(
                            0, IBinder::PING_TRANSACTION, data, NULL, 0);
                    if (status == DEAD_OBJECT)
                       return NULL;
                }
                // BpBinder
                b = new BpBinder(handle); 
                e->binder = b;
                if (b) e->refs = b->getWeakRefs();
                result = b;
          ......
        }
    ​
        return result;
    }
    
  • ProcessState 是进程单例,一个进程只有一个,主要是负责打开Binder驱动,映射内存,负责mmap调用。这里你可能会问ProcessState是在哪里创建的?什么时候创建的?

    Zygote进程启动后,都是通过Socket接受SystemServer和APP进程的信息,在要创建SystemServerAPP进程时,都会在新进程执行onZygoteInit 方法,启动线程池

    virtual void onZygoteInit()
        {
            sp proc = ProcessState::self();
            ALOGV("App process: starting thread pool.\n");
            proc->startThreadPool();
    }
    

    至于系统服务,例如SurfaceFlingerProcessState 启动时机,可以看该上一小节对该进程的简单分析。而且所有的Binder线程池都设定了大小为 4

  • IPCThreadState 线程单例,主要是负责binder驱动与其它命令的通信。在 上一小节分析的 SurfaceFlinger 进程启动,把服务发布到 ServiceManager 中,就利用了 IPCThreadState,我们看一下它的 transact 方法

    status_t IPCThreadState::transact(int32_t handle,
                                      uint32_t code, const Parcel& data,
                                      Parcel* reply, uint32_t flags)
    {
        status_t err = data.errorCheck();
    ​
        flags |= TF_ACCEPT_FDS;
    ​
        IF_LOG_TRANSACTIONS() {
            TextOutput::Bundle _b(alog);
            alog << "BC_TRANSACTION thr " << (void*)pthread_self() << " / hand "
                << handle << " / code " << TypeCode(code) << ": "
                << indent << data << dedent << endl;
        }
    ​
        if (err == NO_ERROR) {
            LOG_ONEWAY(">>>> SEND from pid %d uid %d %s", getpid(), getuid(),
                (flags & TF_ONE_WAY) == 0 ? "READ REPLY" : "ONE WAY");
                //重点 BC_TRANSACTION 指令
            err = writeTransactionData(BC_TRANSACTION, flags, handle, code, data, NULL);
        }
    ​
        if (err != NO_ERROR) {
            if (reply) reply->setError(err);
            return (mLastError = err);
        }
    ​
        if ((flags & TF_ONE_WAY) == 0) {
           .....
            if (reply) {
                err = waitForResponse(reply);
            } else {
                Parcel fakeReply;
                err = waitForResponse(&fakeReply);
            }
    .....
        } else {
            err = waitForResponse(NULL, NULL);
        }
    ​
        return err;
    }
    
  • 整体流程就是Proxy 发起 transact 调用,然后把数据打包到 Parcel 中,然后会向下调用 BpBinder,在BpBinder 中调用IPCThreadStatetransact 方法,持有句柄值,IPCThreadState 负责执行具体Binder指令。Server端通过IPCThread接受到Client的请求,然后层层向上,最后回调到Stub的 onTransact 方法

3、Binder数据传递流程

非oneway

【长文预警⚠️】只有 Android 中高级工程师能看懂 Binder 精讲原理——面试篇_第11张图片
image

oneway

【长文预警⚠️】只有 Android 中高级工程师能看懂 Binder 精讲原理——面试篇_第12张图片
image
  • 在AIDL中写代码时,如果接口标记了oneway,表示Server端串行化处理(从异步队列中拿出消息指令一个个分发)、异步调用。这个关键字主要是用于修改远程调用的行为,就如上面的两个图一样。非oneway关键字的AIDL类,客户端需要挂起线程等待休眠,相当于调用了Sleep函数。例如WMSAMS等相关系统Binder调用都是oneway的。

  • oneway和非oneway的AIDL文件简单区别如下,可以用Android Studio 实践后,看一下生成的文件,查看调用链。

    //非oneway
    interface BookCaller{
        void initBook(ICallback callback);
    }
    --->>>>
    public void initBook(Icallback callback){
        ....
        mRemote.transact(Stub.TRANSATION_initbook,_data,_reply,0);
        ....
    }
    ​
    //onway
    oneway interface BookCaller{
        void initBook(ICallback callback);
    }
    ---->>>>
    public void initBook(Icallback callback){
        ....
        mRemote.transact(Stub.TRANSATION_initbook,_data,null,IBinder.FLAG_ONEWAY);
        ....
    }
    
  • 从上面得知oneway和非oneway的区别最关键的在于最后一个参数,继续看 IPCThreadStatetransact 方法

    status_t IPCThreadState::transact(int32_t handle,
                                      uint32_t code, const Parcel& data,
                                      Parcel* reply, uint32_t flags)
    {
        status_t err = data.errorCheck();
    .....
        if (err == NO_ERROR) {
            LOG_ONEWAY(">>>> SEND from pid %d uid %d %s", getpid(), getuid(),
                (flags & TF_ONE_WAY) == 0 ? "READ REPLY" : "ONE WAY");
                // 1\. cmd = BC_TRANSACTION
            err = writeTransactionData(BC_TRANSACTION, flags, handle, code, data, NULL);
        }
    ​
        if (err != NO_ERROR) {
            if (reply) reply->setError(err);
            return (mLastError = err);
        }
    // 2\. 判断是否oneway
        if ((flags & TF_ONE_WAY) == 0) {
            #if 0
            if (code == 4) { // relayout
                ALOGI(">>>>>> CALLING transaction 4");
            } else {
                ALOGI(">>>>>> CALLING transaction %d", code);
            }
            #endif
            if (reply) {
                err = waitForResponse(reply);
            } else {
                Parcel fakeReply;
                err = waitForResponse(&fakeReply);
            }
            #if 0
            if (code == 4) { // relayout
                ALOGI("<<<<<< RETURNING transaction 4");
            } else {
                ALOGI("<<<<<< RETURNING transaction %d", code);
            }
            #endif
    ​
            IF_LOG_TRANSACTIONS() {
                TextOutput::Bundle _b(alog);
                alog << "BR_REPLY thr " << (void*)pthread_self() << " / hand "
                    << handle << ": ";
                if (reply) alog << indent << *reply << dedent << endl;
                else alog << "(none requested)" << endl;
            }
        } else {
            err = waitForResponse(NULL, NULL);
        }
    ​
        return err;
    }
    

    我们看注释 2 处,如果 flags 是 0 也就是 非oneway,会调用 waitForResponse``(reply),否则会调用waitForResponse(NULL,NULL),这两个函数的区别就是,前者需要等待对方返回结果,后者是不需要等待对方返回结果。

    仔细查看,你还可以通过上面代码知道向驱动发送的消息cmdBC_TRANSATION,如果你再看看 waitForResponse 方法里,你会发现收消息,都是BR开头的。也就是说向Binder 驱动发送消息的指令都是以 BC_ 开头,由Binder驱动向外发送的指令都是以 BR_ 开头

    status_t IPCThreadState::waitForResponse(Parcel *reply, status_t *acquireResult)
    {
        uint32_t cmd;
        int32_t err;
    ​
        while (1) {
        .....
            cmd = (uint32_t)mIn.readInt32();
    ...
    ​
            switch (cmd) {
            //oneway 的话 就直接 goto finish了,因为参数为NULL
            case BR_TRANSACTION_COMPLETE:
                if (!reply && !acquireResult) goto finish;
                break;
    .....
    ​
            case BR_FAILED_REPLY:
                err = FAILED_TRANSACTION;
                goto finish;
    ​
            case BR_ACQUIRE_RESULT:
                {
                    ALOG_ASSERT(acquireResult != NULL, "Unexpected brACQUIRE_RESULT");
                    const int32_t result = mIn.readInt32();
                    if (!acquireResult) continue;
                    *acquireResult = result ? NO_ERROR : INVALID_OPERATION;
                }
                goto finish;
    ​
            case BR_REPLY:
                {
                    binder_transaction_data tr;
                    err = mIn.read(&tr, sizeof(tr));
                    ALOG_ASSERT(err == NO_ERROR, "Not enough command data for brREPLY");
                    if (err != NO_ERROR) goto finish;
    ​
                    if (reply) {
                       .......
                    } else {
                        freeBuffer(NULL,
                            reinterpret_cast(tr.data.ptr.buffer),
                            tr.data_size,
                            reinterpret_cast(tr.data.ptr.offsets),
                            tr.offsets_size/sizeof(binder_size_t), this);
                        continue;
                    }
                }
                goto finish;
    ​
            default:
                err = executeCommand(cmd);
                if (err != NO_ERROR) goto finish;
                break;
            }
        }
    ​
    finish:
        if (err != NO_ERROR) {
            if (acquireResult) *acquireResult = err;
            if (reply) reply->setError(err);
            mLastError = err;
        }
    ​
        return err;
    }
    

三、Binder跨进程传递大图方案

在刚入门Android开发时,可能会直接通过Intent传递Bitmap,然后发现TransactionTooLargetException异常。这是为什么呢?那又该如何解决呢?

  • 问题分析

    Bundle bundle = new Bundle();
    b.putParcelable("bitmap",mBitmap);
    intent.putExtras(b);
    startActivity(intent);
    

    我们对这段造成异常的代码进行分析,抛出异常的地方是BinderProxy.transactNative(Native Method) 方法

    frameworks/base/core/jni/android_util_Binder.cpp
    ​
    {"transactNative",      "(ILandroid/os/Parcel;Landroid/os/Parcel;I)Z", (void*)android_os_BinderProxy_transact}
    ​
    static jboolean android_os_BinderProxy_transact(JNIEnv* env, jobject obj,
            jint code, jobject dataObj, jobject replyObj, jint flags) // throws RemoteException
    {
        ......
        Parcel* data = parcelForJavaObject(env, dataObj);
        if (data == NULL) {
            return JNI_FALSE;
        }
     .......
        status_t err = target->transact(code, *data, reply, flags);
    .........
    ​
        signalExceptionForError(env, obj, err, true /*canThrowRemoteException*/, data->dataSize());
        return JNI_FALSE;
    }
    

    我们继续看一下 signalExceptionForError 方法

    void signalExceptionForError(JNIEnv* env, jobject obj, status_t err,
            bool canThrowRemoteException, int parcelSize)
    {
        switch (err) {
            case UNKNOWN_ERROR:
                jniThrowException(env, "java/lang/RuntimeException", "Unknown error");
                break;
            case NO_MEMORY:
                jniThrowException(env, "java/lang/OutOfMemoryError", NULL);
                break;
        .......
            case PERMISSION_DENIED:
                jniThrowException(env, "java/lang/SecurityException", NULL);
                break;
    .........
            case FAILED_TRANSACTION: {
                ALOGE("!!! FAILED BINDER TRANSACTION !!!  (parcel size = %d)", parcelSize);
                const char* exceptionToThrow;
                char msg[128];
               //注释 1
                if (canThrowRemoteException && parcelSize > 200*1024) {
                    // bona fide large payload
                    exceptionToThrow = "android/os/TransactionTooLargeException";
                    snprintf(msg, sizeof(msg)-1, "data parcel size %d bytes", parcelSize);
                } else {
    
                    exceptionToThrow = (canThrowRemoteException)
                            ? "android/os/DeadObjectException"
                            : "java/lang/RuntimeException";
                    snprintf(msg, sizeof(msg)-1,
                            "Transaction failed on small parcel; remote process probably died");
                }
                jniThrowException(env, exceptionToThrow, msg);
            } break;
    .........
            default:
                jniThrowException(env, canThrowRemoteException
                        ? "android/os/RemoteException" : "java/lang/RuntimeException", msg.string());
                break;
        }
    }
    ​
    }
    

    注释 1 处 说明了 如果 transact 失败 并且 parcelSize 大于 200 k 的话,大概率会抛这个异常。你可能会问这个FAILED_TRANSACTION 是什么时候抛出来的?

    我们可以看一下之前分析 IPCThreadState 中的 transact 方法内调用的 waitForResponse 方法

    status_t IPCThreadState::waitForResponse(Parcel *reply, status_t *acquireResult)
    {
    .....
        while (1) {
        //注释 1 与 binder 进行通信 ioctl
         if ((err=talkWithDriver()) < NO_ERROR) break;
        ....
        cmd = (uint32_t)mIn.readInt32();
        ....
        switch (cmd) {
       ......
    ​
            case BR_FAILED_REPLY:
                err = FAILED_TRANSACTION;
                goto finish;
        .......
    }
    

    可以看到当 Binder驱动 返回 BR_FAILED_REPLY 时,才会设置 err 为 FAILED_TRANSACTION , 那什么时候 Binder驱动会发送 BR_FAILED_REPLY 指令呢?

    注释 1 处 是与 binder 的通信,内部调用了 ioctl 函数,第二个参数指定为了 BINDER_WRITE_READ ,代表向驱动 读取和写入数据,可同时读写

    ioctl 函数内调用了 binder_transaction 方法

    /drivers/stagine/android/binder.c  //注意这个文件和ServerMnager 那里调用的binder.c不一样,这个是内核的
    static void binder_transaction(struct binder_proc *proc,
                       struct binder_thread *thread,
                       struct binder_transaction_data *tr, int reply)
     {
     ....
         t->buffer = binder_alloc_buf(target_proc, tr->data_size,
                tr->offsets_size, !reply && (t->flags & TF_ONE_WAY));
        if (t->buffer == NULL) {
            return_error = BR_FAILED_REPLY;
            goto err_binder_alloc_buf_failed;
        }
     .....
     }
    

    我们可以看到如果 data_size 大小的buffer申请失败,则会将 BR_FAILED_REPLY 错误码发送到客户端。

  • 问题解决方案

    至于解决方案,我们需要从所有跨进程传递的方式里去找。

    这里不考虑使用文件形式来保存图片,因为要进行磁盘操作,而且跨进程操作,没加文件锁,性能和数据都不稳定。其它方式也不行,可以从内存拷贝次数、内存泄漏方面进行阐述。

    直接进入主题,使用AIDL通过Binder进行IPC调用来传递图片。

    Bundle bundle = new Bundle();
    bundle.putBinder("binder",new IRemoteCaller.Stub(){
        @Override
        public Bitmap getBitmap() throw RemoteException{
            return mBitmp;
        }
    });
    //注 putBinder(@Nullable String key, @Nullable IBinder value)
    

    那么你会问,为什么这种方案可以实现大图传递呢?

  • 我们直接看startActivity中序列化过程

    int startActivity(...){
        Parcel data = Parcel.obtain();
        ...
        intent.writeToParcel(data,0);
        ...
        mRemote.transact(START_ACTIVITY_TRANSACTION,data,reply,0);
        ...
    }
    intent.wirteToParcel(data,0) -->>
    public void writeToParcel(Parcel out,int flags){
        out.writeBundle(mExtras);
        ....
    }
    writeBundle() -->>
    public final void wirteBundle(Bundle  val){
        val.writeToParcel(this,0);
    }
    
我们再分析一下`Bundle`的 `writeToParcel` 方法

```
    @Override
    public void writeToParcel(Parcel parcel, int flags) {
    //注释 1
        final boolean oldAllowFds = parcel.pushAllowFds((mFlags & FLAG_ALLOW_FDS) != 0);
        try {
            super.writeToParcelInner(parcel, flags);
        } finally {
            parcel.restoreAllowFds(oldAllowFds);
        }
    }
```

注释 1 处的 含义是 是否允许传递 描述符

上面这个方法最终调用的是 `super.wirteToParcelInner` 方法,该方法会将数据存到map里,然后再一次写入到Parcel,最终是会调用 Bitmap 的 wirteToParcel 方法的(这里省略了部分调用链),而Bitmap的这个方法内部调用了这个native方法

```
nativeWriteToParcel(mNativePtr, mIsMutable, mDensity, p)
​
static jboolean Bitmap_writeToParcel(JNIEnv* env, jobject, ...) {
    // 拿到 Native 的 Bitmap                                
    auto bitmapWrapper = reinterpret_cast(bitmapHandle);
.....
// 往parcel里写 Bitmap 的各种配置参数
    int fd = bitmapWrapper->bitmap().getAshmemFd();
    //图像不能更改 并且 Parcel 允许带 FD,就将fd写到Parcel中
    if (fd >= 0 && !isMutable && p->allowFds()) {
        status = p->writeDupImmutableBlobFileDescriptor(fd);
        return JNI_TRUE;
    }
​
    // 不满足上面的条件
    android::Parcel::WritableBlob blob;
    // 得到一块缓冲区 
    status = p->writeBlob(size, mutableCopy, &blob);
  //获取像素数据
  const void* pSrc =  bitmap.getPixels();
  //将数据拷贝到缓冲区
   memcpy(blob.data(), pSrc, size);
}
```

`writeBlob` 函数 (分析直接在代码里写)

```
status_t Parcel::writeBlob(size_t len, bool mutableCopy, WritableBlob* outBlob)
{
   // 如果不允许带 fd ,或者这个数据小于 BLOB_INPLACE_LIMIT = 16K  
    if (!mAllowFds || len <= BLOB_INPLACE_LIMIT) {
     // 就直接将图片存在Parcel 中
        status = writeInt32(BLOB_INPLACE);
        //Parcel的buffer中找一块偏移
        void* ptr = writeInplace(len);
        outBlob->init(-1, ptr, len, false);
        return NO_ERROR;
    }
  //允许带描述符情况
    //开辟一个匿名共享内存
    int fd = ashmem_create_region("Parcel Blob", len);
    //映射到内存空间 
    void* ptr = ::mmap(NULL, len, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
    ...
    //将fd写到Parcel中
    status = writeFileDescriptor(fd, true /*takeOwnership*/);
        outBlob->init(fd, ptr, len, mutableCopy);
    return status;
}
```

从上面可以看出 `allowFds`打开后,如果图片大于**16k** ,将会开辟一个匿名共享内存空间 用来存 `Bitmap`,然后就不会导致异常。

那为什么直接用`Intent`传递不可以呢?

大家都知道 `startActivity` 后都会调用 `Instrumentation 的 execStartActivity` 方法

```
 public ActivityResult execStartActivity(
        Context who, IBinder contextThread, IBinder token, String target,
        Intent intent, int requestCode, Bundle options) {
        ....
intent.prepareToLeaveProcess(who);
            int result = ActivityManager.getService()
                .startActivity(whoThread, who.getBasePackageName(), intent,
                        intent.resolveTypeIfNeeded(who.getContentResolver()),
                        token, target, requestCode, 0, null, options);
                        .....
  }
```

我们看一下 `intent.prepareToLeaveProcess` 方法

```
public void prepareToLeaveProcess(boolean leavingPackage) {
        setAllowFds(false);
}
public void setAllowFds(boolean allowFds) {
        if (mExtras != null) {
            mExtras.setAllowFds(allowFds);
        }
 }
```


这里将 `allowFds` 设置成了 `false`, 然后就会将 `Bitmap` 直接写到 `Parcel` 缓冲区,太大就出问题了。

四、问题解答

1、你是否了解Binder机制?

  根据第二节的Binder通信架构图,从手机启动过程中的一些进程阐述

  Binder分层架构图

  Binder的oneway和非oneway数据传递图

  Binder的优缺点: 性能(拷贝一次)、安全(校验UID、PID)

2、Binder这么好用,那为什么Zygote的IPC通信机制用Socket而不用Binder?

  如果用了binder,zygote要先启动binder机制,打开binder驱动,获得描述符,mmap进程内存映射,注册binder线程,还要创建一个binder对象注册到serviceManager,另外AMS要想zygote发起创建应用进程请求的话,要先从serviceManager查询zygote的binder对象,再发起binder调用,非常繁琐。

 相比之下,zygote和systemserver本就是父子关系,对于简单的消息通信,用管道或者socket非常方便。

 如果zygote用了binder机制,再fork systemServer,那systemServer就继承了zygote的描述符和映射的内存,这两个进程在binder驱动层就会共用一套数据结构,这肯定是不行的。那还得把旧的描述符关掉,再重新启动一遍binder机制,自找麻烦。 

3、为什么说Binder是安全的?

 在数据传输过程中有身份的校验,通过UID、PID进行校验

4、Intent跨进程传大图为什么会崩溃?

常规的intent传递数据,在startActivity时将Bundle的 allowFds 设置成了false, 然后就会将 Bitmap直接写到 Parcel 缓冲区。如果通过 bundle.putBinder形式传递Bitmap,会开辟一个块共享匿名内存用来存Bitmap的数据,而Parcel 缓冲区只是存储 FD 。

5、AIDL的oneWay和非oneway有什么区别?

 oneway和非oneway的架构图,oneway server端是串行处理,异步调用,Client端不用休眠等待驱动返回数据。
【长文预警⚠️】只有 Android 中高级工程师能看懂 Binder 精讲原理——面试篇_第13张图片

Github 地址:https://github.com/733gh/xiongfan

你可能感兴趣的:(【长文预警⚠️】只有 Android 中高级工程师能看懂 Binder 精讲原理——面试篇)