资讯详情

资讯详情

Android Binder源码解析:BBinder服务端实体核心机制

1. 项目概述从“BBinder”这个类名开始我们到底在分析什么如果你刚打开Android源码在frameworks/native/libs/binder/目录下翻了几百行突然看到一个叫BBinder的类第一反应可能是“这名字怎么这么别扭B开头的类是BaseBinder还是别的什么缩写”——别急这不是命名随意而是Android Binder IPC机制里最核心、最底层的服务端实体抽象。它不是某个具体业务服务比如媒体播放器或电话管理而是一切服务端逻辑在Binder驱动层之上的统一入口和生命周期载体。换句话说你写的每一个Service最终都会被包装成一个BBinder实例注册进Binder驱动等待客户端通过句柄handle来调用。它不处理业务逻辑本身但决定了业务逻辑能不能被调用、被谁调用、调用时上下文是否安全、线程如何调度。我第一次读到BBinder::transact()函数时盯着那几行看似简单的IPCThreadState::self()-transact()调用看了半小时——它表面只是把数据扔给线程状态机背后却牵扯着内核态与用户态的内存映射、事务栈管理、线程阻塞唤醒、死亡通知链表维护等一整套精密协作。这不是一个孤立的类它是Binder通信模型中“服务端侧”的锚点所有服务端行为onTransact、dump、ping、linkToDeath都必须经由它触发所有服务端线程的Binder事务处理都必须以它为上下文。所以“BBinder源码分析”本质上是在拆解Android系统级IPC的服务端执行契约——它定义了“一个合法的服务端对象”必须满足哪些接口、承担哪些责任、如何与Binder驱动协同工作。对应用开发者它藏在AIDL生成代码背后对系统工程师它是定制HAL服务、调试Binder死锁、优化跨进程调用延迟的必经之路。这篇文章不讲AIDL怎么写也不讲ServiceManager怎么注册就聚焦在BBinder这个类本身它的内存布局为什么这样设计onTransact回调里的Parcel参数从哪来又到哪去weak ref和strong ref在它身上如何交织threadID字段到底存的是线程ID还是TID这些细节直接决定你能否看懂binder_transaction内核日志、能否定位TransactionTooLargeException的根本原因、能否在dumpsys binder输出里读懂每个服务的状态。它不是炫技的源码阅读而是构建Android底层通信直觉的基石。2. BBinder的设计哲学与架构定位为什么需要它它站在哪里2.1 它不是“服务”而是“服务的壳”解耦业务与IPC协议很多人初看BBinder会下意识把它等同于“一个服务”。这是个危险的误解。BBinder本身不包含任何业务逻辑。你无法在BBinder类里找到playMusic()、dialNumber()这样的方法。它的全部职责就是作为一个标准化的、可被Binder驱动识别和调度的C对象容器。真正的业务逻辑永远在它的子类里实现——比如MediaPlayerService继承自BBinder然后重写onTransact()在里面解析code参数判断是PLAY还是PAUSE再调用内部的mPlayer-start()。这种设计是典型的策略模式模板方法BBinder定义了服务端必须遵循的协议框架onTransact是纯虚函数强制子类实现而具体策略怎么处理PLAY请求由子类注入。这种解耦带来三个关键好处第一Binder驱动无需理解业务。驱动只认BBinder*指针和transact函数地址它把数据包递过来BBinder负责分发给子类驱动完全不用关心里面是音乐还是短信。第二服务端可以复用同一套IPC基础设施。AudioFlinger、SurfaceFlinger、ActivityManagerService它们业务天差地别但只要都继承BBinder就能共享IPCThreadState的线程池、共享ProcessState的全局句柄表、共享BpBinder的客户端代理逻辑。没有BBinder每个服务就得自己实现一套内存拷贝、线程同步、错误码转换系统会臃肿不堪。第三生命周期管理变得可控。BBinder持有weak_ref_type弱引用和strong_ref_type强引用两个关键成员。当客户端通过BpBinder持有一个服务的强引用时BBinder的引用计数不为零对象不会被析构当所有客户端断开强引用归零BBinder才可能被销毁。这个机制让Binder能自动管理服务端对象的生死避免野指针和内存泄漏。我曾经在一个定制ROM项目里因为子类忘记在onTransact里正确处理PING_TRANSACTION导致BpBinder误判服务已死频繁触发linkToDeath回调最终拖垮了整个SystemServer的线程池——问题根源就在对BBinder这套引用计数契约的理解偏差上。2.2 它位于Binder三层模型的哪一层与BpBinder、IPCThreadState的关系Android Binder IPC常被描述为“三层模型”最上层是AIDL生成的Proxy/StubJava/Kotlin层中间是Native层的BpBinder/BBinder最底层是内核的binder_driver。BBinder就稳稳站在Native层的服务端侧中心位置。要理解它的坐标必须看清它和两个关键邻居的关系首先是BpBinderBinder Proxy。BpBinder是客户端的代理它不持有服务端对象只持有一个32位的handle句柄这个句柄是Binder驱动分配的、指向服务端BBinder在驱动中对应binder_node结构的索引。当客户端调用BpBinder::transact()它把handle和数据打包通过ioctl(BC_TRANSACTION)交给驱动驱动查表找到对应的binder_node再找到其关联的BBinder*指针最后调用BBinder::transact()。注意这里BBinder::transact()不是直接执行业务而是将请求转发给IPCThreadState::self()-executeCommand()进入事务处理循环。所以BBinder和BpBinder是镜像关系一个代表服务端实体一个代表客户端句柄它们通过驱动的handle-node映射完成连接。其次是IPCThreadState。这是Binder Native层的“心脏”每个线程都有一个单例IPCThreadState它管理着该线程的mIn/mOutParcel缓冲区、事务栈、等待队列。BBinder::transact()的最后一步就是调用IPCThreadState::self()-transact()把当前BBinder的this指针、code、data、reply等参数压入该线程的事务栈。IPCThreadState随后会调用talkWithDriver()通过ioctl与内核交互。这意味着BBinder的执行完全依赖于调用它的线程是否拥有有效的IPCThreadState。如果一个普通线程未调用ProcessState::self()-startThreadPool()直接new出BBinder并调用transact()IPCThreadState::self()会返回空指针导致崩溃。这也是为什么所有系统服务都运行在Binder线程池里——BBinder不是独立运行的它是IPCThreadState这个“引擎”上的一个“活塞”只有引擎运转活塞才有意义。我在调试一个HAL服务时曾把BBinder子类的构造放在主线程但onTransact回调却在Binder线程池里执行结果因为主线程和Binder线程访问了同一个非线程安全的std::map引发了随机crash。根本原因就是没意识到BBinder的onTransact永远在IPCThreadState所属的线程上下文中执行而非构造它的线程。2.3 它的内存布局为什么mRef必须是第一个成员虚函数表的隐式约束翻开BBinder.h你会看到它的定义异常简洁class BBinder : public IBinder { public: BBinder(); virtual ~BBinder(); virtual status_t transact(uint32_t code, const Parcel data, Parcel* reply, uint32_t flags 0); virtual status_t linkToDeath(const spDeathRecipient recipient, void* cookie nullptr, uint32_t flags 0); // ... 其他纯虚函数 protected: virtual status_t onTransact(uint32_t code, const Parcel data, Parcel* reply, uint32_t flags) 0; private: RefBase::weakref_type* mRefs; // 注意这是第一个成员 mutable Mutex mLock; };这里有个极易被忽略但至关重要的细节mRefs指向RefBase::weakref_type的指针是BBinder类的第一个数据成员。这不是巧合而是为了满足C对象内存布局的硬性要求。BBinder继承自IBinder而IBinder是一个纯虚基类interface它本身没有数据成员只有虚函数表vtable。当BBinder对象被创建时其内存布局是[vtable pointer] [mRefs] [mLock] ...。而RefBase::weakref_type的incStrong()/decStrong()函数在内部会通过reinterpret_castBBinder*((char*)weakref - offsetof(BBinder, mRefs))的方式从weakref_type*反向计算出BBinder*的地址。这个计算成立的前提就是mRefs必须是BBinder的第一个成员否则偏移量offsetof会出错导致强引用计数混乱进而引发UAFUse-After-Free漏洞。这个设计体现了Android Binder对C底层机制的极致利用——它放弃了“面向对象”的封装美感选择了“零成本抽象”的工程现实。另一个佐证是BBinder的transact()函数签名它接受const Parcel data和Parcel* reply。Parcel本身是一个复杂的、带内存池管理的序列化容器。BBinder不关心data里的具体内容它只负责把data原样传递给onTransact()由子类去data.readInt32()或data.readString16()。这种“数据搬运工”角色决定了它的接口必须足够通用和轻量不能有任何业务相关的类型绑定。所以BBinder的整个设计哲学就是做最少的事提供最稳定的契约把复杂性留给子类和驱动。它像一个精密的瑞士手表机芯外表简单内部齿轮咬合严丝合缝稍有偏差整个时间系统就会停摆。3. BBinder的核心成员与关键函数深度解析逐行代码背后的意图3.1mRefs弱引用计数器服务端生命的“心跳监测仪”mRefs是BBinder最核心的成员类型为RefBase::weakref_type*。它不是简单的计数器而是一个双向链表节点原子计数器的复合体承载着Binder服务端对象的整个生命周期管理。它的作用远不止“记录有多少人引用我”这么简单。首先mRefs的初始化发生在BBinder构造函数中BBinder::BBinder() : mRefs(new RefBase::weakref_type(this)) { }这里传入this指针意味着weakref_type内部保存了对BBinder的原始指针。当BpBinder客户端代理通过handle找到这个BBinder后它会调用BBinder::getWeakRefs()-incWeak(this)来增加一个弱引用。弱引用的作用是让BpBinder能感知BBinder的生死而不阻止其被销毁。例如当BpBinder调用linkToDeath()时它就是在mRefs的死亡通知链表里注册一个回调。一旦BBinder的强引用归零即所有spBBinder都被释放RefBase的析构函数会遍历mRefs的死亡通知链表逐一调用binderDied()。这个机制是Binder实现“服务端意外退出客户端能立刻获知”的技术基础。mRefs的第二个关键角色是强引用计数的载体。BBinder本身不直接管理强引用而是通过spBBinder智能指针间接操作。sp的operator和析构函数最终都会调用mRefs-incStrong()和mRefs-decStrong()。incStrong()内部会原子地增加强引用计数并检查是否从0变为1——如果是则调用BBinder::onFirstRef()这是一个可被子类重写的钩子函数常用于懒加载资源。decStrong()则相反当强引用从1降为0时会调用BBinder::onLastStrongRef()然后真正deletethis。我曾经在一个车载系统项目中需要在服务启动时初始化一块共享内存就重写了onFirstRef()确保只有第一个客户端连接时才执行初始化避免重复开销。而onLastStrongRef()则被用来清理资源比如关闭socket连接、释放grallocbuffer。mRefs的第三个隐藏功能是线程安全的基石。incStrong()和decStrong()内部使用android_atomic_inc()和android_atomic_dec()保证了在多线程并发调用BpBinder::transact()时引用计数的增减是原子的。如果没有这个原子性两个线程同时decStrong()可能导致强引用计数从2错误地变成1服务端对象提前析构引发灾难性crash。所以mRefs绝不是一个可有可无的成员它是BBinder在多线程、跨进程环境下保持稳定运行的“生命维持系统”。3.2transact()与onTransact()一次跨进程调用的完整旅程BBinder::transact()是整个Binder服务端流程的总入口。它的代码非常短但每一行都意味深长status_t BBinder::transact(uint32_t code, const Parcel data, Parcel* reply, uint32_t flags) { status_t err NO_ERROR; switch (code) { case PING_TRANSACTION: err pingBinder(); break; case DUMP_TRANSACTION: { // ... 处理dump请求 break; } case INTERFACE_TRANSACTION: { // ... 返回接口描述符 break; } default: // 核心调用子类实现的onTransact err onTransact(code, data, reply, flags); break; } return err; }这段代码揭示了BBinder的协议分层思想。它把所有code分为三类系统保留codePING、DUMP、INTERFACE由BBinder自己处理其他所有业务code一律交给子类的onTransact()。这种设计让BBinder既能提供标准的IPC能力比如pingBinder()用于检测服务是否存活又不侵入业务逻辑。onTransact()的签名是virtual status_t onTransact(uint32_t code, const Parcel data, Parcel* reply, uint32_t flags) 0它是一个纯虚函数强制所有子类必须实现。data参数是客户端发送过来的Parcel它已经完成了从内核binder_transaction_data结构到用户态内存的拷贝和反序列化reply参数是服务端要返回给客户端的ParcelonTransact()需要往里面writeInt32()或writeString16()。flags参数则携带了调用属性比如TF_ONE_WAY表示这是一个异步调用服务端处理完就返回不等待客户端reply。onTransact()的返回值status_t至关重要它不仅表示业务逻辑是否成功还决定了Binder驱动的行为。如果返回NO_ERROR驱动认为调用成功如果返回UNKNOWN_TRANSACTION驱动会向上层抛出RemoteException如果返回BAD_VALUE则可能触发TransactionTooLargeException。我在分析一个性能瓶颈时发现某个服务的onTransact()里对一个大数组做了memcpy而data本身已经包含了完整的序列化数据结果导致CPU占用飙升。后来改成直接用data的data()指针操作性能提升了3倍。这说明onTransact()不仅是业务入口更是性能优化的关键战场。transact()函数的最后一行是return err。这个err最终会通过IPCThreadState::self()-sendReply()被封装进binder_transaction_data结构通过ioctl(BC_REPLY)返回给驱动再由驱动投递给客户端的BpBinder。整个过程就像一条精密的流水线客户端下单BC_TRANSACTION→ 驱动派单找到BBinder→BBinder接单transact()→ 子类处理onTransact()→BBinder交货sendReply()→ 驱动送货BC_REPLY。BBinder就是这条流水线上那个永不疲倦、永不犯错的接单员。3.3linkToDeath()与unlinkToDeath()服务端“死亡通知”的实现原理linkToDeath()是BBinder提供的一个高级特性它允许客户端在服务端意外崩溃或退出时收到一个回调通知。这个功能看似简单背后却涉及BBinder、BpBinder、IPCThreadState和内核驱动四层的紧密协作。BBinder::linkToDeath()的实现核心在于将客户端的DeathRecipient对象注册到mRefs的死亡通知链表中status_t BBinder::linkToDeath(const spDeathRecipient recipient, void* cookie, uint32_t flags) { return mRefs-linkToDeath(recipient, cookie, flags); }mRefs-linkToDeath()会创建一个BpBinder::Obituary结构体里面保存了recipient、cookie和flags然后将其插入到mRefs内部的mPendingDeaths链表中。这个链表是弱引用链表它不增加BBinder的强引用计数只增加弱引用。这意味着即使客户端挂了BBinder也不会因为这个链表而被强制存活。当BBinder的强引用归零RefBase的析构函数被调用时它会遍历mPendingDeaths链表对每个Obituary调用recipient-binderDied()并将cookie作为参数传入。这就是客户端收到“死亡通知”的时刻。unlinkToDeath()则是逆向操作它从链表中移除指定的DeathRecipient。这个机制的精妙之处在于解耦与异步。linkToDeath()的调用是同步的但binderDied()的回调是异步的它发生在BBinder析构的那一刻而这个时刻可能是在服务端进程exit()之后也可能是在BBinder子类主动delete this之后。BpBinder在收到binderDied()后会清除自己内部的handle缓存并标记该代理为“已失效”后续的transact()调用会直接返回DEAD_OBJECT错误。我在调试一个系统服务重启问题时发现ActivityManagerService在重启过程中BpBinder的linkToDeath()回调被触发了两次原因是AMS的BBinder子类在onLastStrongRef()里又调用了delete this导致析构函数被调用两次。最终解决方案是在onLastStrongRef()里加了一个mIsDead标志位确保死亡通知只发送一次。这说明linkToDeath()不是“设个闹钟”那么简单它要求服务端和客户端双方都严格遵守引用计数的契约任何一方的疏忽都会导致通知失灵或重复。4. 实操环节如何在真实项目中定位与调试BBinder相关问题4.1 场景一TransactionTooLargeException——不只是数据太大更是BBinder的“缓冲区告警”TransactionTooLargeException是Android开发中最常见的Binder异常之一错误日志通常显示“data parcel size 800000 bytes is larger than max 1048576”。很多开发者第一反应是“把数据拆小”但这往往治标不治本。要根治必须深入BBinder的数据流转路径。BBinder::transact()接收的data参数其大小限制源头在IPCThreadState的mOut缓冲区。mOut是一个Parcel它内部有一个mData字节数组初始大小为BINDER_VM_SIZE通常是1MB-2MB。当data的大小超过mData剩余空间时Parcel::writeInterfaceToken()或writeString16()会触发growData()尝试扩容。但如果扩容后仍不足或者mData已达到BINDER_MAX_BUF_SIZE内核硬编码通常为1MBtransact()就会失败。BBinder本身不检查这个大小它信任data是合法的。所以问题往往出在客户端BpBinder在transact()前把一个超大的Bitmap或ArrayList序列化进了data。调试的第一步是确认data的来源。在BBinder子类的onTransact()开头添加日志ALOGI(onTransact code%d, data.size()%zu, data.dataSize()%zu, code, data.dataSize(), data.dataAvail());dataSize()是序列化后的实际字节数dataAvail()是Parcel缓冲区的可用空间。如果dataSize()接近1MB基本可以确定是客户端传参过大。第二步是检查BBinder子类是否在onTransact()里做了不必要的data.setDataSize()或data.setDataCapacity()调用这会强制Parcel分配大内存。第三步也是最关键的是查看dumpsys binder输出。执行adb shell dumpsys binder stats找到你的服务名观察transactions和failed transactions的比值。如果failed transactions很高且错误码是-2147483648FAILED_TRANSACTION那几乎可以肯定是TransactionTooLarge。此时不要急于改客户端先检查服务端BBinder子类的onTransact()里是否有reply-setDataSize(1024*1024)这样的硬编码这会让reply也占用大量内存。我的经验是90%的TransactionTooLarge问题根源都在onTransact()里对data或reply的不当操作而不是网络或内核配置。解决它不是加大BINDER_VM_SIZE而是重构数据传输协议用FileDescriptor传递大文件用MemoryFile共享内存这才是BBinder设计者希望你走的路。4.2 场景二DeadObjectException——BBinder已死但BpBinder不知情DeadObjectException意味着客户端试图调用一个已经不存在的服务端对象。这通常发生在服务端进程崩溃、BBinder子类被delete、或ServiceManager注销了服务之后。但有时BpBinder的handle缓存没有及时失效导致transact()调用仍然发送出去最终在驱动层被拦截返回-32EBADF。调试这类问题核心是理解BBinder的“死亡”是如何被传播的。第一步确认服务端是否真的死了。执行adb shell ps | grep your_service_name如果进程不存在问题就明确了。第二步检查服务端BBinder子类的onLastStrongRef()实现。这里必须确保delete this被正确调用且没有遗漏。我曾在一个HAL服务里因为onLastStrongRef()里忘了调用RefBase::onLastStrongRef()导致mRefs的析构没有触发BpBinder的死亡通知链表一直挂着binderDied()从未被调用。第三步也是最隐蔽的是检查BpBinder的linkToDeath()是否成功。在客户端代码里linkToDeath()的返回值必须检查status_t ret bp-linkToDeath(recipient); if (ret ! NO_ERROR) { ALOGE(linkToDeath failed: %s, strerror(-ret)); // 这里应该有降级处理比如重连 }如果ret是NO_INIT说明BpBinder的mHandle已经是0无效句柄linkToDeath()根本没注册成功。此时客户端必须立即重连服务。第四步利用dumpsys binder nodes命令查看服务端binder_node的状态。如果refs字段为0说明没有强引用服务端已死如果pending字段不为0说明还有未处理的死亡通知。BBinder的“死亡”不是瞬间的它有一个从强引用归零、到onLastStrongRef()执行、再到mRefs析构、最后到binderDied()回调的完整链条。任何一个环节卡住都会导致DeadObjectException。所以调试它不是看错误日志而是要沿着这条链条用dumpsys和日志一环一环地验证。4.3 场景三onTransact()被调用但业务逻辑没执行——线程阻塞与IPCThreadState的“饥饿”这是一个极其隐蔽的性能问题。现象是客户端transact()调用返回成功BBinder::transact()日志也打印了但onTransact()里的业务代码比如ALOGI(business logic start)却迟迟不出现。这通常意味着onTransact()所在的线程被阻塞了。BBinder的onTransact()永远在IPCThreadState所属的线程里执行。如果这个线程正在执行一个耗时的IO操作比如读取一个大文件或者陷入了死锁比如等待一个Mutex而这个Mutex又被另一个Binder线程持有那么所有发给这个BBinder的请求都会排队等待。调试的第一步是确认onTransact()的调用线程。在onTransact()开头打印线程IDALOGI(onTransact called on thread %d, gettid());然后对比dumpsys binder threads的输出看这个tid是否在active列表里。如果不在说明线程已死或被阻塞。第二步是检查IPCThreadState的mIn/mOut缓冲区状态。执行adb shell dumpsys binder proc your_pid查看out和in的size和avail。如果out.size很大而out.avail很小说明mOut缓冲区被占满新的事务无法写入。这通常是因为onTransact()里调用了另一个BpBinder的transact()形成了同步调用链而被调用方又在等待当前线程导致死锁。第三步也是最有效的是使用systrace。录制一个binder和sched的trace然后在Chrome的chrome://tracing里打开搜索你的服务名。如果看到onTransact()的条目长时间处于Running状态但下面没有子调用那基本可以确定是线程阻塞。我的一个教训是在onTransact()里直接调用了system()函数执行shell命令而这个shell命令又试图访问一个被BBinder线程池锁住的资源结果整个Binder线程池被拖垮。解决方案永远是异步化把耗时操作放到HandlerThread或std::thread里执行onTransact()只做快速的参数校验和任务分发。BBinder不是业务逻辑的执行场所它是IPC的“闸门”闸门必须永远畅通。5. 常见问题速查表与独家避坑心得那些源码里不会写的细节问题现象可能原因排查命令/方法解决方案我的实操心得onTransact()从不被调用但transact()返回成功BBinder子类的onTransact()没有正确重写或code不匹配在BBinder::transact()的default:分支加日志确认是否进入onTransact()确保子类onTransact()声明为public且code与客户端一致我曾把onTransact()写成private编译不报错但运行时永远走不到花了两天才定位。记住BBinder的transact()是public子类重写也必须是public。dumpsys binder显示服务存在但客户端getService()返回nullServiceManager未成功注册或BBinder子类构造失败adb shell dumpsys servicemanager | grep your_service_name检查BBinder构造函数日志确保defaultServiceManager()-addService()在BBinder对象完全构造后再调用addService()必须在BBinder子类的构造函数结束后调用。如果在构造函数里就调用this指针可能不完整导致ServiceManager注册失败。linkToDeath()回调被调用多次BBinder子类的onLastStrongRef()里重复delete this或mRefs析构被多次触发在onLastStrongRef()和mRefs析构函数里加唯一日志使用std::atomicbool标志位确保delete this只执行一次RefBase的析构是线程安全的但onLastStrongRef()不是。务必在onLastStrongRef()里加锁或原子标志。BBinder子类的onTransact()里data.readString16()返回空字符串data的dataPosition()被意外修改或data未正确setData()在onTransact()开头打印data.dataPosition()和data.dataSize()不要手动调用data.setDataPosition()除非你完全理解Parcel的游标机制Parcel的dataPosition()像一个指针readString16()会自动移动它。如果之前有data.readInt32()dataPosition()已经变了直接readString16()会读错位置。IPCThreadState::self()返回nullptr导致transact()崩溃调用transact()的线程未初始化IPCThreadState检查调用线程是否是Binder线程池或是否调用了ProcessState::self()-startThreadPool()所有调用BBinder::transact()的代码必须确保在Binder线程上下文中IPCThreadState::self()是线程局部存储TLS每个线程有自己的实例。主线程默认没有必须显式创建。提示BBinder的onTransact()函数永远不要做new/delete操作。Parcel的data和reply都是栈上分配的onTransact()的生命周期很短任何堆分配都会引入内存碎片和延迟。我见过一个服务在onTransact()里new了一个std::vector结果在高并发下malloc成为瓶颈TPS下降50%。正确的做法是用reply-writeInt32()直接写入或用reply-writeStrongBinder()传递spBBinder。注意BBinder的mLock成员是一个mutable Mutex。mutable关键字允许它在const成员函数里被锁定。BBinder::pingBinder()就是一个const函数但它内部需要锁定mLock来保护状态。这个设计是为了在const语义下依然能进行必要的线程同步。如果你在子类里重写pingBinder()也必须保持const否则编译会失败。实操心得调试BBinder最好的工具不是GDB而是logcat配合dumpsys binder。logcat加ALOGI日志能告诉你onTransact()是否进入dumpsys binder nodes能告诉你BBinder的引用计数dumpsys binder stats能告诉你事务成功率。三者结合比单步调试源码快十倍。我习惯在BBinder子类的构造、onTransact()、onFirstRef()、onLastStrongRef()四个地方都加上ALOGI(%s: %p, __func__, this)这样一眼就能看出对象的生命周期。避坑技巧BBinder子类的析构函数永远不要调用IPCThreadState::self()-flushCommands()。flushCommands()会强制发送所有待处理的ioctl但在析构时调用可能导致IPCThreadState已被销毁引发SIGSEGV。BBinder的销毁应该由RefBase的析构函数自动完成不需要手动干预。这是我踩过最深的坑——在~MyService()里加了flushCommands()结果服务一退出就crash排查了三天
觉得有用,分享给同行:

为您的企业打造数字门面

稳重轻奢商务风格,端正雅致视觉,长效耐看不易过时。

立即咨询 →