zoukankan      html  css  js  c++  java
  • 【Delphi】 使用RAD Delphi FMX 开发安卓APP经常很不稳定且闪退的原因

    FMX开发android和iOS越来越稳定完善,期待delphi能够有更多新人接力。

    下面说说在FMX开发中APP经常莫名其妙闪退的一些原因:

    1)线程访问UI: 优先排查最常见的线程访问UI控件没有加同步保护,下面是相应的建议;

         为了避免界面UI因为一些耗时较长的调用(网络访问,阻塞请求等)导致APP提示无响应,建议在各种用户交互操作中使用以下代码,此代码几乎是各种操作通用的调用方法:

    procedure TfmDemo.Button1Click(Sender: TObject);
    begin
      TThread.CreateAnonymousThread(procedure begin
        
        //Do something here...
    
        //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来释放(并不是强制释放,仍与计数有关),

    我们在开发中如确定实例已经不再被需要了,可以调用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。

  • 相关阅读:
    Jenkins持续集成
    爬豆瓣保存到sqlite3
    爬豆瓣保存到Excel
    sqlite3数据库的增删查改
    用pandas和matplotlib对用户消费行为分析
    TCP请求
    fastjson
    断言
    将结果写入文件
    加解密
  • 原文地址:https://www.cnblogs.com/caibirdy1985/p/9470236.html
Copyright © 2011-2022 走看看