1. 项目概述:从“BBinder”这个类名开始,我们到底在分析什么?
如果你刚打开Android源码,在frameworks/native/libs/binder/目录下翻了几百行,突然看到一个叫BBinder的类,第一反应可能是:“这名字怎么这么别扭?B开头的类,是Base?Binder?还是别的什么缩写?”——别急,这不是命名随意,而是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/Stub(Java/Kotlin层),中间是Native层的BpBinder/BBinder,最底层是内核的binder_driver。BBinder就稳稳站在Native层的服务端侧中心位置。要理解它的坐标,必须看清它和两个关键邻居的关系:
首先是BpBinder(Binder 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 sp<DeathRecipient>& 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_cast<BBinder*>((char*)weakref - offsetof(BBinder, mRefs))的方式,从weakref_type*反向计算出BBinder*的地址。这个计算成立的前提,就是mRefs必须是BBinder的第一个成员,否则偏移量offsetof会出错,导致强引用计数混乱,进而引发UAF(Use-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的强引用归零(即所有sp<BBinder>都被释放),RefBase的析构函数会遍历mRefs的死亡通知链表,逐一调用binderDied()。这个机制,是Binder实现“服务端意外退出,客户端能立刻获知”的技术基础。mRefs的第二个关键角色,是强引用计数的载体。BBinder本身不直接管理强引用,而是通过sp<BBinder>智能指针间接操作。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分为三类:系统保留code(PING、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参数是服务端要返回给客户端的Parcel,onTransact()需要往里面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 sp<DeathRecipient>& 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(内核硬编码,通常为1MB),transact()就会失败。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很高,且错误码是-2147483648(FAILED_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()调用仍然发送出去,最终在驱动层被拦截,返回-32(EBADF)。调试这类问题,核心是理解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()开头,打印线程ID:
ALOGI("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()返回null | ServiceManager未成功注册,或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::atomic<bool>标志位,确保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()传递sp<BBinder>。
注意:
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,排查了三天