问题描述
我正在为iOS和Android开发Firemonkey App。我注意到,每次使用TFDMemTable REST API数据和结构在运行时创建TGrid时,该应用在iOS和Android调试中的性能都会降低。
我已经应用FreeAndNil(TGrid1);
来清理TGrid,然后再创建一次。
一个值得注意的事件,每次创建TGrid时,行都以固定的7列增加,因此性能会变慢。通常,当我到达10行或记录时会发生这种情况。
您认为开销在哪里导致性能下降?
-
TGrid —我已经在创建
FreeAndNil(TGrid1);
之前应用了它。 -
TFMemTable —我没有检查过,也不知道如何
在这种情况下,我们假设所有其他组件都工作正常。如果您愿意的话,我可以与您分享一些代码,但可以指导我您要看到的代码。
更新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.
解决方法
您在这里面临的问题是,由于ARC在Delphi中的工作方式,TTeeGrid
并没有真正被破坏。
那是为什么?
将Parent设置为tgd1
组件后,对其的引用就会添加到lyt1
控件集合中,该控件列出了所有子组件。因此,当您调用FreeAndNil(tgd1);
时,只能从全局变量TTeeGrid
中释放对tgd1
对象的引用,但布局控件集合中的引用仍然保留。而且由于您的TTeeGrid
参考计数尚未达到零,因此该对象也不会被破坏。
所以不要使用:
FreeAndNil(tgd1);
您需要使用:
tgd1.DisposeOf;
tgd1 := nil;
这可确保无论对象引用计数如何,都执行TTeeGrid
对象的析构函数,这反过来通知Layout TTeeGrid
对象被销毁,因此需要将其从Layouts控件中删除集合,因此允许TTeeGrid
对象引用计数达到零。
实际上,您应该使用DisposeOf
在运行时销毁任何组件。
我建议您在How to free a component in Android / iOS
中阅读有关此主题的更多信息。EDIT仅在使用ARC的Android和iOS等移动平台上会遇到此问题。在Windows上,您的代码可以正常运行。这可能就是其他人无法重现您的问题的原因。
还请注意,由于在Delphi 10.4 ARC中已删除您的代码,因此也应该可以使用。但是我猜您不是在使用最新版本的Delphi。
您可能要编辑问题并包括您的Delphi版本,以改进此问题,因为所用的Delphi版本会影响问题的答案。
,坦率地说,我怀疑这里是否有人可以解决您的问题,因为我们无法访问您的REST源,因此其他人无法真正复制该问题。相反,我建议您回溯到my answer here,以解决您先前有关将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;
,
在运行时创建TTeeGrid会累积开销,从而在某些时候降低性能。
为解决此问题,我在运行时删除了TTeeGrid的创建和释放,取而代之的是,我在设计时放置了TTeeGrid可视组件,并在每次由新触发器触发时通过属性将其连接刷新为true或false。 API提供的一组数据和结构。