从内存中清除动态 FMX 列表视图位图

Clearing Dynamic FMX listview bitmaps from memory

我最近开始使用动态列表视图项。它很棒,除了当我尝试使用位图添加和清除项目时。我有一个列表视图,我在其中添加项目并将图像下载到内存流中,并将其分配给动态列表视图项目的位图。这有效,除了当我用 lv.items.clear 清除所有项目时,它不会从内存中删除。

即使我清除了旧项目,内存也一直在上升。有没有办法清除所有位图?

基本上发生的事情是:

  1. 用 10 个项目填充动态列表视图。添加数据和位图。
  2. 看看内存。增加了 2 兆字节。
  3. 使用 lv.items.clear 清除列表视图。
  4. 看看内存。没有变化?
  5. 重复,内存就会越来越多。

我尝试遍历所有列表视图项并将每个位图设置为 nil 但没有导致任何内存更改。我还尝试通过循环释放每个项目,但它只会使应用程序崩溃。

我应该以某种方式清除所有项目或位图吗?如果可以,我该怎么做呢?

这是我加载所有项目的方式:

procedure TPluginInstaller.Load;
begin
  with frmMain.framePluginManager do
  begin
    TThread.CreateAnonymousThread(
      procedure
      begin
        var restClient := TRESTClient.Create(nil);
        var restRequest := TRESTRequest.Create(nil);
        var restResponse := TRESTResponse.Create(nil);
        try
          restRequest.Client := restClient;
          restRequest.Response := restResponse;

          restClient.BaseURL := BASE_URL;
          restClient.UserAgent := APP_USERAGENT;
          restRequest.AddParameter('query', fQuery);
          restRequest.AddParameter('page', fPage.ToString);
          restRequest.AddParameter('sort', SortBy[fSort]);

          if fSort = 0 then
            restRequest.AddParameter('sortdir', 'asc')
          else
            restRequest.AddParameter('sortdir', 'desc');

          restRequest.AddParameter('categories[]', 'rust');

          restRequest.Execute;

          var jdata := restResponse.JSONValue;

          fLastPage := jdata.GetValue<Integer>('last_page', 1);
          fPage := jdata.GetValue<Integer>('current_page', 1);

          TThread.Synchronize(nil,
            procedure
            begin
              spnedtPage.Max := fLastPage;
              spnedtPage.Value := fPage;
              lblPageMax.Text := ' of ' + fLastPage.ToString;

              lvPluginInstaller.BeginUpdate;
              try
                lvPluginInstaller.Items.Clear;

                for var jplugins in (jdata.FindValue('data') as TJSONArray) do
                begin
                  var aItem := lvPluginInstaller.Items.Add;

                  var aIcon := aItem.Objects.FindObjectT<TListItemImage>('Icon');
                  var aDownloadsIcon := aItem.Objects.FindObjectT<TListItemImage>('DownloadsIcon');
                  var aVersionIcon := aItem.Objects.FindObjectT<TListItemImage>('VersionIcon');
                  var aAuthorIcon := aItem.Objects.FindObjectT<TListItemImage>('AuthorIcon');
                  var aUpdatedIcon := aItem.Objects.FindObjectT<TListItemImage>('UpdatedIcon');

                  var aTitle := aItem.Objects.FindObjectT<TListItemText>('Title');
                  var aDescription := aItem.Objects.FindObjectT<TListItemText>('Description');
                  var aVersion := aItem.Objects.FindObjectT<TListItemText>('Version');
                  var aDownloads := aItem.Objects.FindObjectT<TListItemText>('Downloads');
                  var aAuthor := aItem.Objects.FindObjectT<TListItemText>('Author');
                  var aURL := aItem.Objects.FindObjectT<TListItemText>('URL');
                  var aUpdated := aItem.Objects.FindObjectT<TListItemText>('Updated');

                  GetIcon(jplugins.GetValue<string>('icon_url').Trim, aIcon);

                  aDownloadsIcon.ImageIndex := 0;
                  aVersionIcon.ImageIndex := 1;
                  aAuthorIcon.ImageIndex := 2;
                  aUpdatedIcon.ImageIndex := 4;

                  aTitle.Text := jplugins.GetValue<string>('title', 'Unknown Plugin Title');
                  aDescription.Text := jplugins.GetValue<string>('description', 'Unknown Plugin Description');
                  aVersion.Text := jplugins.GetValue<string>('latest_release_version_formatted', 'Unknown Version');
                  aDownloads.Text := jplugins.GetValue<string>('downloads_shortened', 'Unknown Downloads');
                  aAuthor.Text := jplugins.GetValue<string>('author', 'Unknown Author');
                  aURL.Text := jplugins.GetValue<string>('json_url', 'Unknown URL');
                  aUpdated.Text := jplugins.GetValue<string>('latest_release_at', 'Unknown');
                end;
              finally
                lvPluginInstaller.EndUpdate;
              end;
            end);

        finally
          restResponse.Free;
          restRequest.Free;
          restClient.Free;
        end;
      end).Start;
  end;
end;

正在从 url 加载位图:

procedure TPluginInstaller.GetIcon(const aURL: string; aIcon: TListItemImage);
begin
  if aURL = '' then
  begin
    aIcon.ImageIndex := 3;
    Exit;
  end;

  TThread.CreateAnonymousThread(
    procedure
    begin
      var imgStream := TMemoryStream.Create;
      try
        TDownloadURL.DownloadRawBytes(aURL, imgStream);

        TThread.Synchronize(nil,
          procedure
          begin
            aIcon.Bitmap := TBitmap.CreateFromStream(imgStream);
          end);

      finally
        imgStream.Free;
      end;
    end).Start;
end;

设置TListItemImage.OwnsBitmap property to True, otherwise you are responsible for freeing the TBitmap objects manually when you are done using them. Note that starting with Delphi 10.4, ARC is no longer used for object memory management on mobile platforms:

Unified Memory Management

  • Delphi memory management is now unified across all supported platforms - mobile, desktop, and server - using the classic implementation of object memory management. Compared to Automatic Reference Counting (ARC), this offers better compatibility with existing code and simpler coding for components, libraries, and end-user applications. The ARC model remains for string management and interface type references for all platforms.
  • For C++, this change means that the creation and deletion of Delphi-style classes in C++ follow normal memory management just like any heap-allocated C++ class, significantly reducing complexity.