如何正确释放FireMonkey控件?带父窗体的子窗体及TFmxObject.Release问题
跨平台下FireMonkey带父窗体子窗体的正确释放姿势
嘿,这个问题我刚好在跨平台开发FireMonkey的时候踩过坑!确实,TFmxObject.Release不仅已经被官方标记为弃用,还只在Windows上能正常工作,Android完全不买账——这是因为FireMonkey的跨平台架构和VCL那套不一样,没有VCL依赖消息循环延迟释放的机制。
如果你想在控件自身的事件处理程序里释放它(比如子窗体的关闭按钮点击事件里释放自己),可以按照下面这些跨平台通用的方法来:
1. 用TThread.Queue延迟释放(最推荐)
这是跨平台最安全的做法,原理是把释放操作放到主消息队列的末尾,等当前的事件处理代码全部执行完之后,再去执行释放操作,从根本上避免了“事件还在执行,控件已经被释放”的访问违规问题。
举个实际的代码例子,比如子窗体的关闭按钮点击事件:
procedure TChildForm.btnCloseClick(Sender: TObject); begin // 先处理你需要的业务逻辑,比如打个日志标记 Log.d('准备释放子窗体'); // 把释放操作丢到主队列,等当前事件跑完再执行 TThread.Queue(nil, procedure begin // 如果需要,可以先断开子窗体和父窗体的关联(其实DisposeOf会自动处理,可选操作) // Self.Parent := nil; Self.DisposeOf; // 要是用的是比较老的Delphi版本,没有DisposeOf的话,就用Free——不过注意ARC平台下Free是引用计数减一,DisposeOf是强制立即释放 end); // 这里的代码会正常执行完,不会被阻塞 Log.d('当前事件处理代码执行完毕'); end;
2. ARC平台(Android/iOS)的特殊注意点
在Android、iOS这类ARC(自动引用计数)平台上,Free本质是给控件的引用计数减一,只有当引用计数降到0时才会真正释放;而DisposeOf是强制绕过ARC,直接调用控件的析构函数释放资源。所以如果你需要确保控件被立即释放(比如要在析构函数里看到日志输出),优先用DisposeOf。
3. 析构函数日志的小细节
如果你在子窗体的析构函数里加了日志,比如:
destructor TChildForm.Destroy; begin Log.d('子窗体析构函数已执行'); inherited; end;
不管是Windows还是Android,只要用上面的TThread.Queue+DisposeOf的组合,析构函数都会被正常触发,你能看到对应的日志输出。
4. 要避开的坑
- 绝对不要直接在事件里调用
Self.Free或者Self.DisposeOf:事件触发时控件正处于被系统引用的状态,直接释放会导致后续代码访问已释放的内存,轻则崩溃,重则出现各种诡异的行为。 - 彻底抛弃
Release方法:已经被弃用,跨平台完全不兼容,别再用了。 - 如果子窗体是用
ShowModal显示的,别在模态窗体内部释放自己:应该在调用ShowModal的地方,用try-finally块来释放,比如:
procedure TMainForm.btnOpenChildClick(Sender: TObject); var ChildForm: TChildForm; begin ChildForm := TChildForm.Create(Self); try if ChildForm.ShowModal = mrOk then begin // 处理子窗体返回的结果 end; finally ChildForm.DisposeOf; end; end;
这样不管是Windows还是Android平台,都能正确释放控件,不会出现各种跨平台兼容问题。
内容的提问来源于stack exchange,提问作者maf-soft
相关产品推荐
相关产品推荐

