控件的晚释放DelayCleanup
原生的duilib库中,在容器类CContainerUI中有一个布尔变量m_bDelayedDestroy,如果被赋值TRUE,则当调用Remove释放子控件时,并不实际释放(delete)子控件内存,而是把子控件指针加入到CPaintManagerUI的晚释放队列里由CPaintManagerUI在合适的时机释放内存,CContainerUI只是简单地把子控件向量列表清空。
产生的问题
duilib的类CContainerUI中m_bDelayedDestroy的默认值是TRUE,所以派生类都是晚释放的,所以CListUIEx也是。
在进销存的商品管理中,当用户第一次刷新列表时,问题不大,当第二次刷新列表时,虽然先调用了列表的DeleteAllItems,但是由于是晚释放,所以项目的内存并没有实际释放,再加载数据插入列表时,相当于使用了双倍内存,在商品数量少时内存占用不明显,如果商品数量多达几千时,内存占用飙升至1.5G以上,导致后面插入的项目分配内存失败,即使不闪退,也不会产生新的单元格导致数据无法显示,继而点击图片时也显示不了。
为解决该问题,在CListUIEx的构造函数中,设置m_bDelayedDestroy为FALSE,并且对类CContainerListItem和CListUIExCell都采取同样的设置。
引出的新问题
上面的解决方法虽然能解决内存占用过大的问题,但会引出一个新问题。
如上图,当用户点击了删除按钮时,duilib内核的代码如下:
当走到红色圆圈1723行时,在界面的Notify函数中,可能会执行删除列表项的操作,由于项目内存即时释放,项目中的按钮也被释放掉,也就是说,在1723行以下,Msg.pSender已经被释放掉了,所以再执行下去就会非法内存访问导致闪退。
新问题的解决方法
duilib的原生框架里,晚释放标志是放在容器类CContainerUI中的,现在把该标志提升到控件类CControlUI中。原生的框架里,一个容器类的子控件要么全部晚释放,要么全部即时释放,改造后,则可以为每个子控件指定是晚释放还是即时释放。
-
将m_bDelayedDestroy移到CControlUI中
同时将函数IsDelayedDestroy和SetDelayedDestroy也移到CControlUI中,并在构造函数中设置为false。
-
将CContainerUI的Add和AddAt函数改为虚函数。
这样就可以在CContainerListItem和CListUIExCell中重载这两个函数,当添加的子控件是按钮时,就设置成晚释放。
-
修改CContainerUI::Remove和RemoveAll()函数。
如果子控件是晚释放,就加入到到CPaintManagerUI的晚释放队列里。
-
CListUIExCell中重载Add和AddAt函数。
如果添加的控件是按钮,就设置成晚释放:
-
CBrowseEditWnd的处理
因为下拉窗口会有一个自身的paintmanager,下拉窗口消失后它的paintmanager也就释放了,所以该窗口包含的所有控件都不能晚释放。在CBrowseEditWnd::HandleMessage中的WM_CREATE处理模块中,所有new的控件都调用SetDelayedDestroy(false):
-
CBrowseEditUI::RemoveAll()
原因同上,所有子控件立马delete: