框架概述
BaseLooper框架仿照Android中的looper和handler概念并有所扩展。
主要思想是采用looper对象来代替线程,用handler来表示组件
每个looper相当于一个线程
每个handler表示一个组件
经研究发现handler和looper有很多共性,所以我们把looper作为handler的子类,这点是BaseLooper框架首创的,目前没有在其他框架中看到。经实际使用,效果良好。
handler和looper可以互相挂靠, 挂靠是通过AddChild接口来做的
handler和handler,looper和looper之间也可以互相挂靠,层次没有限制,这个特点在实际编码时会很方便,能和各种业务需求完美匹配。
可以把handler和looper都看做一个节点,节点可以添加任意多个子节点,子节点也可以添加自己的子节点。
所有的handler和looper就构成了节点树,每个节点都可以直接和其他节点通讯,并且是线程安全的。
同时BaseLooper框架引入了一个MainLooper的概念,相当于根节点。框架给MainLooper提供了一些专用接口但并不强制使用此机制,MainLooper需要由用户可以指定。
parent/child管理
parent对child弱引用
child对parent强引用
销毁时parent依次通知各child
child count为0时,才能销毁parent
BaseHandler生命周期管理
每个object有且仅有一个parent,root object的parent为nullptr
object可以有多个childobject,多个object可形成objecttree,不能有环
.提供object tree dump机制,可辅助调试
dump采用消息机制,可保证跨looper运行时的安全
.object tree销毁时序:保证child object在parent之前析构
正常运行时,parent对child做弱引用(不能做强引用,原因见下)
child可分为两种类型,一种是自主型,另一种是被动型
自主型是指生命周期由object自己的当前的未决业务来保证,当没有未决业务时能自动销毁,比如windowsiocp版的BaseTcpClient,
BaseTcpClient在有未完成的IoContext时会一直存在,在所有IoContext已完结时能自毁
被动型是指对外提供服务,但没有未决业务这个概念,比如GameTcpHandler
如果parent对所有child一视同仁做强引用,则自主型child在业务完结时不能自动销毁
如果parent不对被动型child做强引用,则被动型child在创建之后马上上自毁了
发现被动型可以用mSelfRef来保活,当BM_DESTROY时再自毁
所以app中parent对child只做弱引用,而child对parent做强引用
app结束时,parent对所有child发BM_DESTROY,收到BM_DESTROY时主动型child会取消所有未决业务,被动型child会清除mSelfRef,所以都可保证完结
当顶级looper定时检测到use_count()为1表示没有child引用它,此时可安全退出looper
这里所说的object在代码中由BaseHandler来实现,而BaseObject只是一个最简化的基类
.被动型mSelfRef的处理
在AddChild时会设定被动型object的mSelfRef
在收到BM_DESTROY时会清除mSelfRef
要主动销毁时,可调用PostDispose(mSelfRef);
建议不要主动调用postMessage(BM_DESTROY),原因如下:
多次调用PostDispose(mSelfRef)是无害的,但多次postMessage(BM_DESTROY)时,第一个BM_DESTROY销毁,后面的BM_DESTROY会导致crash
对于iocp业务,每当提交一个未决请求时,采用shared_ptr来保证handler的有效性
对于epoll和kqueue来说,在销毁handler时可清除未决业务,所以可保证销毁handler之后不会再收到过时的epoll/kqueue事件
PostDispose
采用PostDispose来延迟清除对象
PostDispose用来解决如下问题
假定ObjectA强引用ObjectB
当ObjectB回调ObjectA的api时,有可能ObjectA需要释放对ObjectB的强引用
如果ObjectA直接释放,则当控制返回到ObjectB时,OjbectB已经析构,但ObjectB没意识到这一点,仍然引用this来做操作,造成crash
PostDispose的解决办法:
new一个结构来缓存指向ObjectB的shared_ptr,投递消息给looper,looper在消息时删除shared_ptr
//testok
.BaseLooper是不是一种BaseHandler
BaseLooper和BaseHandler有很多接口相同,把它们统一起来会比较方便
但BaseLooper是BaseHandler的基石,它不同于普通的BaseHandler
BaseLoop和BaseLooper也要整理
//已解决,BaseLoop从BaseHandler继承了
.需要实现一个框架,可设定多个object之间的依赖关系,当app退出时自动根据依赖关系来完结各object业务
可归纳为消息通知,即制定好规则后在合适的时机发送消息
.object的树形结构和依赖绑定
c++ object生命周期管理
object实例对象存在的意义
app创建object都是为了实现某个功能需求,当功能需求已满足或消失后就不再需要此object,也就是说可以清除object了
object之间的关系
怎么定义多个objects之间的关系,有几种可能
按tree树形结构来定义的话,每个object有一个parent,有0个或多个child
按group群结构来定义,则不存在parent/child关系,而是按照StrongRef,WeakRef即强/弱引用比较直观
tree是group的一个特例
为简化起见,可采用tree来实现app的object管理
app的根从main入口函数开始,以Deviceapp来说,root object就是MainLooper
一般来说,创建两个object的关系是比较简单的,难点在于何时解除关系
感觉BaseHandler中parent/child和ref应该独立出来
parent/child表示的是在objecttree中的层次
ref表示的是业务依赖
这两者有时是一致的,但有时没有关联
parent和child的强弱引用
哪方对另一方做强引用,还是双方互相强引用,需要分析一下
parent对child做强引用,看起来天经地义,但会遇到问题
比如在app结束时,为安全退出app,所有object都必须完结业务并销毁
想到了一个比较好的idea
正常运行时,parent对child做强引用
app结束时,parent对所有child添加一个弱引用(仅供调试使用),然后通知所有child反转强引用,即child对parent做强引用,parent不再强引用child
当顶级looper定时检测到use_count()为1表示没有child引用它,此时可安全退出looper
parent对所有child添加一个弱引用是为了方便调试,比如当有bug导致looper一直不能退出时,可递归查询是哪些child引起的
Timer定时器设计
采用时间轮来做定时器
每个looper内置一个timer管理器
创建,删除,触发timer都要做到是O(1)的复杂度
在OnTimer(timerId)中能做如下事情
.删除当前BaseHandler的当前timerId定时器
.删除当前BaseHandler的其他timerId定时器
.删除其他BaseHandler的定时器
.创建其他BaseHandler的定时器
.创建当前BaseHandler的当前定时器
.创建当前BaseHandler的其他宣时器
要保证删除定时器后,此定时器不再被触发
经过仔细设计和测试验证,BaseLooper框架能保证上述要求。
代码风格
文件名大小写问题
google建议c++类命名时用大小写,而文件名全部采用小写。这个其实是有点别扭的,我们觉得像java那样文件名也采用大小写更加直观一点。
之所以有这个问题是因为windows系的文件系统做的比较好,不区分文件名大小写,而偷懒的linux系文件系统区分大小写。从比较死板的角度来说,确实应该区分大小写,而从使用方便的角度来说,不应该区分大小写。双方都有道理。
安装了AndroidStudio的朋友可以打开AndroidStudioSdk\sources\android-24\java\lang,看google自家的java产品是怎么命名文件的,它采用了大小写,而AndroidStudio是支持Windows,Linux和OSX等平台的,照样用的好好的。所以我们觉得用大小写也是可行的,如果因为换了个死板的文件系统编译时因为文件路径大小写不匹配就报错,那是软件开发人员在#include时的问题,有问题改正就行了。