【Delphi】使⽤RADDelphiFMX开发安卓APP经常很不稳定且
闪退的原因
FMX开发android和iOS越来越稳定完善,期待delphi能够有更多新⼈接⼒。
下⾯说说在FMX开发中APP经常莫名其妙闪退的⼀些原因:
1)线程访问UI: 优先排查最常见的线程访问UI控件没有加同步保护,下⾯是相应的建议;
为了避免界⾯UI因为⼀些耗时较长的调⽤(⽹络访问,阻塞请求等)导致APP提⽰⽆响应,建议在各种⽤户交互操作中使⽤以下代码,此代码⼏乎是各种操作通⽤的调⽤⽅法:
procedure TfmDemo.Button1Click(Sender: TObject);
begin
TThread.CreateAnonymousThread(procedure begin
//Do
//update UI
TThread.Synchronize(nil, procedure begin
Button1.Text := 'done';
end);
end).Start;
end;
2)内存泄漏:检查变量的创建和释放,访问等相关代码,排除空指针,⽆效变量,⽆效类实例等调⽤。
这⾥没什么好说的,枯燥⽆聊的排查,让⼈沉浸其中且烦闷的过程,不过也有以下⽅法:
A.可以将程序先输出为Windows平台,设置 System.ReportMemoryLeaksOnShutdown := True; 来开启内存泄漏检测(当然也有
其他内存泄漏检测组件包,⼤同⼩异),实际中需详细排查,此⽅法排查跨平台代码。
B.在可调试条件下,直接调试并在出错的地⽅看CallStack是最直观的,可⼀层⼀层剥开看到出错的地
⽅(程序员做的最多就是且应该
是这种操作了)
C.输出运⾏⽇志,⽐较基础的操作就是利⽤Log.D或者其他⾃主实现的记录⽇志⽅法,在程序各个函数的⼊⼝出⼝处记录参数和结果
(在VCL中经常使⽤OutputDebugString的⼈会⽐较习惯)
D.单元测试:相信这个⽅法90%以上的⼈做不到,当然也包括本⼈,不过⼤公司规范严格的会有此类要求,接触过的⼈会⽐较习惯3)运⾏时函数的⾮法参数:系统时间⽇期格式问题
这个问题多半出现在将时间字符串转成TDateTime过程中,该动作在取数据库时间,在显⽰⽇期时间等功能中经常使⽤,
假如使⽤了StrToDateTime,EncodeDate,EncodeTime等RTL函数,因为⼀些习惯问题,很多⼈认为时间字符串格式默认就
是“YYYY-MM-DD hh:mm:ss”,但是很多时候Windows系统Android系统,甚⾄iOS系统都不是这个格式,具体有系统设置区域语⾔控制,这时候调⽤上⾯的RTL函数会出现异常错误,⽽在⼿机上此类错误会导致直接闪退。
解决办法就是先设置TFormatSettings中与时间格式相关的字段,可参考如下:
function StrToDateTime(sDateTime: string; const Default: TDateTime): TDateTime;
var
aFormatSettings: TFormatSettings;
begin
aFormatSettings := TFormatSettings.Create;
aFormatSettings.TimeSeparator := ':';
aFormatSettings.ShortTimeFormat := 'hh:mm:ss';
aFormatSettings.LongTimeFormat := 'hh:mm:ss';
if sDateTime.Contains('/') then
begin
aFormatSettings.ShortDateFormat := 'YYYY/MM/DD';
aFormatSettings.LongDateFormat := 'YYYY/MM/DD';
aFormatSettings.DateSeparator := '/';
end
else
begin
aFormatSettings.ShortDateFormat := 'YYYY-MM-DD';
aFormatSettings.LongDateFormat := 'YYYY-MM-DD';
aFormatSettings.DateSeparator := '-';
end;
Result := SysUtils.StrToDateTimeDef(sDateTime, Default, aFormatSettings);
end;
4)编写习惯错误:新版本的变量释放⽤法,当然这⾥只是说明⼀下在传统VCL转到FMX后经常碰到问题时的⼤概率原因。
在VCL开发中有些⼈会经常使⽤destroy来释放创建的类⽰例(Create和Destroy或Free⼀⼀对应),
这也是因为在VCL中的Free内部其实也是调⽤Destroy,导致很多⼈释放时直接调⽤Destroy。
⽽Delphi FMX在开发Android APP时,经常出现APP崩溃闪退也有很⼤可能性是因Destroy导致的。
因为在FMX,全局变量和类变量采⽤ARC引⽤计数来释放,⽽计数的增加很多时候是隐藏在代码背后⾃动添加的,属于FMX框架运⾏时所需的计数量。
对于Delphi语法来说 Create 应该和 Free(或FreeAndNil)⼀⼀对应来使⽤。
但在FMX中,经常有⼈反馈占⽤内存越来越⼤,因为Free并不能直接释放内存,因此Delphi⼜提供了DisposeOf来释放(并不是强制释放,仍与计数有关),
安卓app开发用什么框架我们在开发中如确定实例已经不再被需要了,可以调⽤DisposeOf来释放,正常情况都可以直接释放。
简单调⽤如下
procedure TfmDemo.DoProcess();
var
aObj: TDemoObj;
begin
aObj:=TDemoObj.Create;
try
//.....
finally
aObj.DisposeOf;
// 建议标准做法: aObj.Free; aObj := nil;
/
/ FreeAndNil⽬前由于调⽤时会增加新的引⽤计数,所以不推荐,若以后Delphi有针对该⽅法优化则可直接调⽤
end;
end;
为什么在VCL调⽤Destroy可以正常,⽽在FMX中却容易出错
注:当然并不⼀定会出错,在⾮类⽅法的纯函数(⾯向过程)中仍可正常调⽤(只是实际中测试正常,⽆法保证随着以后的升级更新后仍然正常)
通过Delphi提供的TObject源码可知,若直接调⽤Destroy,则会导致类⽰例的Destroy⽅法被执⾏并直接释放内存,也就相当于“内
容”被擦除了,但程序中某些地⽅仍会使⽤实例变量,且变量的引⽤计数仍在,所以当FMX框架内部在引⽤计数为0时的⾃动释放变量所指的实例过程中,会再次擦除不存在的实例“内容”,相当于访问不存在的⾮法内存,就会导致崩溃,且调试中⼤部分⽆法直接定义代码位置,若没有该经验,最终觉得毫⽆头绪。。。
就像上⾯说的,全局变量或类变量会有引⽤计数的影响,⽽且需要注意的是,匿名函数在编译时也会被当作匿名类处理,其局部变量也会转换为类变量。
当我们编译匿名函数(最常⽤的是匿名线程中的各种调⽤)时,若有未使⽤变量的警告,提⽰内容⼀般是:
[DCC Hint] Unit1.pas(56): H2164 Variable 'aSSS' is declared but never used in '(null).[0]'
这⾥的 (null).[0] 就是编译器⾃动创建的匿名函数类名称。
补充:
根据EMB的说法,未来将会取消ARC,所以⽬前代码的编写习惯,建议是保持Create和Free ⼀⼀对应即可,没必要纠结内存增长,⽽归根结底内存增长其实是由于编码不规范导致的,不能认为是编译器或者FMX框架的BUG,变量的交叉关联引⽤都会导致ARC⽆法正常释放,所以改善编码质量,局部变量及时释放;全局变量或类变量避免重复创建,合理释放即可;
个⼈认为保持代码的编译兼容性最为重要,对于移交代码,跨平台编译,多环境编译⾮常有⽤。
5)补充⼀个⾮常冷门的原因,代码优化BUG:常出现在APP使⽤DEBUG编译时运⾏正常,但使⽤RELEASE编译运⾏则崩溃
这种情况下,⾸先确认你的⽔平处于中⾼以上时,对⾃⼰的代码已经⾮常确定⽆错误,但实际中编译出来仍然会出现崩溃,可尝试关闭代码优化,DCC的编译器BUG是存在的,特别是对代码优化的处理上,每个版本的更新都会被报告或多或少⼏个编译器BUG。 所以关闭代码优化,可能可以解决崩溃问题!
需要注意的是,使⽤DEBUG和使⽤RELEASE编译使⽤的dcu不同,甚⾄代码中可能使⽤{$IFDEF DEBUG}做编译开关导致处理代码不同,因此需要优先排查此部分代码是否存在BUG。
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论