1

我正在为 iOS 和 Android 开发 Firemonkey 应用程序。我注意到,每次使用 TFDMemTable REST API 数据和结构在运行时创建 TGrid 时,应用程序在 iOS 和 Android 调试中的性能都会变慢。

FreeAndNil(TGrid1);在它一遍又一遍地创建之前,我已经应用了清理 TGrid。

一个值得注意的事件是,每次创建 TGrid 时,行都会随着固定的 7 列而增加,性能会变慢。通常,当我达到 10 行或记录时会发生这种情况。

我的一个大而真实的快速问题:

您认为导致性能下降的开销来自哪里?

  1. TGrid——我FreeAndNil(TGrid1);在创建它之前就已经应用了它。

  2. TFMemTable — 我没有检查过这个,我不知道怎么做

  3. TButton — 触发创建数据并将数据加载到 TGrid 的按钮。大部分代码都在这里

让我们假设在这种情况之前所有其他组件都工作正常。如果您愿意,我可以与您分享一些代码,但请指导我您想查看哪个代码。

更新 1:最小可重复示例

FMX 文件

object Form9: TForm9
  Left = 0
  Top = 0
  Caption = 'MRE TeeGrid Runtime'#13#10
  ClientHeight = 480
  ClientWidth = 294
  FormFactor.Width = 320
  FormFactor.Height = 480
  FormFactor.Devices = [Desktop]
  DesignerMasterStyle = 0
  object btn1: TButton
    Align = Bottom
    Position.Y = 440.000000000000000000
    Size.Width = 294.000000000000000000
    Size.Height = 40.000000000000000000
    Size.PlatformDefault = False
    TabOrder = 9
    Text = 'CREATE TEEGRID'
    OnClick = btn1Click
  end
  object aniSearchProcess: TAniIndicator
    Position.X = 128.000000000000000000
    Position.Y = 216.000000000000000000
  end
  object lyt1: TLayout
    Align = Client
    Size.Width = 294.000000000000000000
    Size.Height = 440.000000000000000000
    Size.PlatformDefault = False
    TabOrder = 11
  end
  object cur1: TFDGUIxWaitCursor
    Provider = 'FMX'
    Left = 32
    Top = 32
  end
  object dvr1: TFDPhysSQLiteDriverLink
    Left = 88
    Top = 32
  end
  object con1: TFDConnection
    Params.Strings = (
      'DriverID=SQLite')
    Connected = True
    LoginPrompt = False
    Left = 144
    Top = 32
  end
  object loc1: TFDLocalSQL
    Connection = con1
    Active = True
    Left = 200
    Top = 32
  end
  object rsc1: TRESTClient
    Accept = 'application/json, text/plain; q=0.9, text/html;q=0.8,'
    AcceptCharset = 'utf-8, *;q=0.8'
    BaseURL = 
      'https://me6hwinr2k.execute-api.ap-southeast-1.amazonaws.com/v0/d' +
      'bqueries?item-var=9&qty=25'
    Params = <>
    Left = 32
    Top = 112
  end
  object rsq1: TRESTRequest
    Client = rsc1
    Params = <>
    Response = rsp1
    SynchronizedEvents = False
    Left = 32
    Top = 176
  end
  object rsp1: TRESTResponse
    ContentType = 'application/json'
    Left = 32
    Top = 240
  end
  object rsd1: TRESTResponseDataSetAdapter
    Active = True
    Dataset = mtb1
    FieldDefs = <>
    Response = rsp1
    Left = 32
    Top = 304
  end
  object mtb1: TFDMemTable
    Active = True
    FieldDefs = <
      item
        Name = 'Category'
        DataType = ftWideString
        Size = 255
      end
      item
        Name = 'ID'
        DataType = ftWideString
        Size = 255
      end
      item
        Name = 'Item'
        DataType = ftWideString
        Size = 255
      end
      item
        Name = 'Qty'
        DataType = ftWideString
        Size = 255
      end
      item
        Name = 'Container'
        DataType = ftWideString
        Size = 255
      end
      item
        Name = 'Size'
        DataType = ftWideString
        Size = 255
      end
      item
        Name = 'Ex temporibus dolore consequatur.'
        DataType = ftWideString
        Size = 255
      end
      item
        Name = 'Et cum aut est nostrum...'
        DataType = ftWideString
        Size = 255
      end
      item
        Name = 'Sequi quibusdam eum.'
        DataType = ftWideString
        Size = 255
      end>
    IndexDefs = <>
    FetchOptions.AssignedValues = [evMode]
    FetchOptions.Mode = fmAll
    ResourceOptions.AssignedValues = [rvSilentMode]
    ResourceOptions.SilentMode = True
    UpdateOptions.AssignedValues = [uvCheckRequired, uvAutoCommitUpdates]
    UpdateOptions.CheckRequired = False
    UpdateOptions.AutoCommitUpdates = True
    StoreDefs = True
    Left = 32
    Top = 368
  end
end

FMX 程序

unit Main;

interface

uses
  System.SysUtils, System.Types, System.UITypes, System.Classes, System.Variants,
  FMX.Types, FMX.Controls, FMX.Forms, FMX.Graphics, FMX.Dialogs,
  FireDAC.UI.Intf, FireDAC.FMXUI.Wait, FireDAC.Stan.ExprFuncs,
  FireDAC.Phys.SQLiteDef, FireDAC.Stan.Intf, FireDAC.Stan.Option,
  FireDAC.Stan.Error, FireDAC.Phys.Intf, FireDAC.Stan.Def, FireDAC.Stan.Pool,
  FireDAC.Stan.Async, FireDAC.Phys, FireDAC.Phys.SQLite, Data.DB,
  FireDAC.Stan.Param, FireDAC.DatS, FireDAC.DApt.Intf, REST.Types,
  FMX.Controls.Presentation, FMX.StdCtrls, FireDAC.Comp.DataSet,
  FireDAC.Comp.Client, REST.Response.Adapter, REST.Client, Data.Bind.Components,
  Data.Bind.ObjectScope, FireDAC.Phys.SQLiteVDataSet, FireDAC.Comp.UI,
  FMXTee.Control, FMXTee.Grid, FMX.Layouts;

type
  TForm9 = class(TForm)
    cur1: TFDGUIxWaitCursor;
    dvr1: TFDPhysSQLiteDriverLink;
    con1: TFDConnection;
    loc1: TFDLocalSQL;
    rsc1: TRESTClient;
    rsq1: TRESTRequest;
    rsp1: TRESTResponse;
    rsd1: TRESTResponseDataSetAdapter;
    mtb1: TFDMemTable;
    btn1: TButton;
    aniSearchProcess: TAniIndicator;
    lyt1: TLayout;
    procedure btn1Click(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
  end;

var
  Form9: TForm9;
  tgd1: TTeeGrid;

implementation

{$R *.fmx}

procedure TForm9.btn1Click(Sender: TObject);
var
  i, CanvassItemId, e : Integer;
begin
  aniSearchProcess.Visible := True;
  aniSearchProcess.Enabled := True;
  {$IFDEF MSWINDOWS}
    Application.ProcessMessages;
  {$ENDIF}
  {$IF DEFINED(iOS) or DEFINED(ANDROID)}
    Application.HandleMessage;
  {$ENDIF}

  FreeAndNil(tgd1); //free old grid

  //create new grid
  tgd1 := TTeeGrid.Create(lyt1);
  With tgd1 do
  begin
    Parent := lyt1;
    Align := TAlignLayout.Client;
    Margins.Top := 5;
    Margins.Left := 5;
    Margins.Right := 5;
    Margins.Bottom := 5;
    ScrollBars.Visible := True;
    Header.Format.Font.Size := 11;
    Cells.Format.Font.Size := 11;
    TabOrder := 0;
    ScrollBars.Visible := False;
  end;

  con1.StartTransaction;
  try
    //define the API here for duplicate/update, initial click and subsequent clicks
    rsc1.BaseURL := 'https://0rgvn0s0gk.execute-api.ap-southeast-1.amazonaws.com/v0/dbqueries?item-var=1&qty=10';
    rsq1.Execute;
    rsd1.Active := True;
    mtb1.Active;
    tgd1.DataSource := mtb1;
    tgd1.Enabled := True;

    // adjust the column properties dynamically
    with tgd1 do
    begin
      for i := 0 to Columns.Count -1 do
      begin
        if i = 0 then
        begin
          Columns[i].Visible := False; // category column
        end
        else if (i = 1) then
        begin
          Columns[i].Visible := False; // id column
        end
        else if (i = 2) then
        begin
          Columns[i].Width.Value := 120; // item column
        end
        else if (i = 3) then
        begin
          Columns[i].Width.Value := 30; // qty column
        end
        else if (i = 4) then
        begin
          Columns[i].Width.Value := 50; // container column
        end
        else if (i = 5) then
        begin
          Columns[i].Width.Value := 50; // size column
        end
        else
        begin
          Columns[i].Width.Value := 50; // subsequent random columns
        end;
      end;
    end;
  finally
    con1.Commit;
  end;

  aniSearchProcess.Visible := False;
  aniSearchProcess.Enabled := False;
  {$IFDEF MSWINDOWS}
    Application.ProcessMessages;
  {$ENDIF}
  {$IF DEFINED(iOS) or DEFINED(ANDROID)}
    Application.HandleMessage;
  {$ENDIF}

end;

end.
4

3 回答 3

3

您在这里面临的问题是,由于 ARC 在 Delphi 中的工作方式,您TTeeGrid并没有真正被破坏。

这是为什么?
只要将 Parent 设置为tgd1组件,就会将对其的引用添加到lyt1列出所有子组件的 Controls 集合中。因此,当您调用时,您只需从全局变量FreeAndNil(tgd1);中释放对对象的引用,但布局控件集合中的引用仍然存在。而且由于您的引用计数尚未达到零,因此对象不会被破坏。TTeeGridtgd1TTeeGrid

所以不要使用:

FreeAndNil(tgd1);

你需要使用:

tgd1.DisposeOf;
tgd1 := nil;

这样可以确保TTeeGrid执行对象的析构函数,而不管对象引用计数如何,这反过来会通知布局您的TTeeGrid对象正在被销毁,因此需要从 Layouts Controls 集合中删除它,从而允许TTeeGrid对象引用计数达到零。

事实上,您应该使用它DisposeOf在运行时销毁任何组件。

我建议您在How to free a component in Android / iOS 中阅读有关此主题的更多信息

编辑此问题仅在使用 ARC 的 Android 和 iOS 等移动平台上遇到。在 Windows 上,您的代码可以正常工作。这可能是其他人无法重现您的问题的原因。

另请注意,由于在 Delphi 10.4 中删除了 ARC,因此您的代码也应该可以工作。但我猜你没有使用最新版本的 Delphi。

您可能需要编辑问题并包含您的 Delphi 版本以改进此问题,因为所使用的 Delphi 版本会影响问题的答案。

于 2020-09-06T22:12:05.380 回答
1

坦率地说,我怀疑这里是否有人可以解决您的问题,因为其他任何人都无法真正重现它,因为我们无权访问您的 REST 源。相反,我建议您回溯到我在此处对您之前关于将 TTeeGrid 与 FDMemTable 一起使用的问题的回答。我建议这样做的原因是因为它提供了一种测试/基准测试两个相当独立的组件的方法,并且不依赖于(对于其他人)对您的 REST 源的访问。您可以使用如下代码来调查您报告的减速是否与重复创建/释放 TTeeGrid 相关(如果是,我会感到惊讶)。

procedure TForm1.FormCreate(Sender: TObject);
var
  AField : TField;
begin
  AField := TWideStringField.Create(Self);
  AField.FieldName := 'ID';
  AField.Size := 255;
  AField.FieldKind := fkData;
  AField.DataSet := FDMemTable1;

  { repeat for other fields}

  FDMemTable1.CreateDataSet;
  { insert test data using FDMemTable1.InsertRecord in a loop}

  { repeat the following to see if TTeeGrid really slows down be repeated creation/freeing}
    { create TTeeGrid1 here }
    { connect FDMemTable1 to TTeeGrid1 here}
    {  TTeeGrid1.Free }
  { until done }
end;
于 2020-09-06T11:55:03.590 回答
1

在运行时创建 TTeeGrid 会累积开销,从而在某些时候降低性能。

为了解决这个问题,我在运行时删除了 TTeeGrid 的创建和释放,相反,我在设计时放置了 TTeeGrid 可视化组件,并让它在每次被新数据集触发时通过启用为 true 或 false 的属性刷新其连接和API提供的结构。

于 2020-09-07T23:01:17.473 回答