You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

如何清理FMX动态ListView中Bitmap的内存占用?

FMX Dynamic ListView Bitmap Memory Leak When Clearing Items

兄弟,我太懂这个坑了!FMX的动态ListView加载带Bitmap的项时,内存泄漏是真的头疼,我之前也踩过一模一样的坑。你的问题核心是**TListItemImage里的Bitmap没有被正确释放**,哪怕调用了Items.Clear,要么是异步加载的线程还在偷偷赋值,要么是ListView内部的引用没切断。

先给你拆解原因:

  1. 你的GetIcon用异步线程加载图片,当你调用Clear时,可能有些线程还在跑,之后会把Bitmap赋值给已经被清理的ListItem,导致这些Bitmap永远飘在内存里。
  2. 直接调用Items.Clear不会自动释放每个ListItem里的Bitmap——FMX的动态ListView对项的对象有缓存机制,不会主动销毁Bitmap对象。
  3. 你之前把Bitmap设为nil没用,因为TListItemImage.Bitmap属性不会自动释放旧对象;直接释放项会崩溃,因为ListView还持有这些项的引用。

现在给你一步步的解决方案:

1. 先管好异步加载的线程(最关键!)

首先要把所有加载图片的线程管起来,清理ListView前先停止这些线程,避免它们后续乱赋值。

首先在你的TPluginInstaller类里加个线程列表:

private
  FImageLoadThreads: TThreadList<TThread>;

然后在类的构造和析构里初始化/清理这个列表:

constructor TPluginInstaller.Create(AOwner: TComponent);
begin
  inherited;
  FImageLoadThreads := TThreadList<TThread>.Create;
end;

destructor TPluginInstaller.Destroy;
var
  Thread: TThread;
begin
  // 程序退出时清理所有剩余线程
  for Thread in FImageLoadThreads.LockList do
  begin
    Thread.Terminate;
    Thread.WaitFor;
    Thread.Free;
  end;
  FImageLoadThreads.UnlockList;
  FImageLoadThreads.Free;
  inherited;
end;

接下来修改你的GetIcon方法,把线程加入列表,并且在线程结束时移除,同时确保Bitmap的所有权正确转移:

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

  LoadThread := TThread.CreateAnonymousThread(
    procedure
    var
      imgStream: TMemoryStream;
      LocalBitmap: TBitmap;
    begin
      // 先检查线程是否被终止,避免无效操作
      if TThread.CurrentThread.Terminated then Exit;

      imgStream := TMemoryStream.Create;
      try
        if TThread.CurrentThread.Terminated then Exit;
        TDownloadURL.DownloadRawBytes(aURL, imgStream);
        if TThread.CurrentThread.Terminated then Exit;

        LocalBitmap := TBitmap.CreateFromStream(imgStream);
        try
          TThread.Synchronize(nil,
            procedure
            begin
              if not TThread.CurrentThread.Terminated then
              begin
                // 先释放旧的Bitmap!这步之前你没做
                aIcon.Bitmap.Free;
                aIcon.Bitmap := LocalBitmap;
                LocalBitmap := nil; // 把所有权转给aIcon,避免重复释放
              end;
            end);
        finally
          LocalBitmap.Free; // 只有当所有权没转出去时才释放
        end;
      finally
        imgStream.Free;
        // 线程结束后从列表移除
        FImageLoadThreads.Remove(LoadThread);
      end;
    end);

  // 线程结束后自动释放
  LoadThread.OnTerminate := procedure(Sender: TObject)
    begin
      TThread(Sender).Free;
    end;
  // 把线程加入管理列表
  FImageLoadThreads.Add(LoadThread);
  LoadThread.Start;
end;

2. 清理ListView时手动释放Bitmap

在调用Items.Clear之前,先遍历所有项,手动释放每个TListItemImage的Bitmap,切断引用:

修改你Load方法里的同步部分:

TThread.Synchronize(nil,
  procedure
  var
    I: Integer;
    aItem: TListViewItem;
    aIcon: TListItemImage;
  begin
    spnedtPage.Max := fLastPage;
    spnedtPage.Value := fPage;
    lblPageMax.Text := ' of ' + fLastPage.ToString;

    lvPluginInstaller.BeginUpdate;
    try
      // 第一步:先停掉所有正在加载图片的线程
      var ThreadList := FImageLoadThreads.LockList;
      try
        for var Thread in ThreadList do
        begin
          Thread.Terminate;
          Thread.WaitFor;
        end;
        ThreadList.Clear;
      finally
        FImageLoadThreads.UnlockList;
      end;

      // 第二步:手动释放每个项的Bitmap
      for I := lvPluginInstaller.Items.Count - 1 downto 0 do
      begin
        aItem := lvPluginInstaller.Items[I];
        aIcon := aItem.Objects.FindObjectT<TListItemImage>('Icon');
        if Assigned(aIcon) then
        begin
          aIcon.Bitmap.Free;
          aIcon.Bitmap := nil;
        end;
        // 如果你还有其他带Bitmap的TListItemImage(比如其他图标),这里也要一起释放
      end;

      // 第三步:再调用Clear
      lvPluginInstaller.Items.Clear;

      // 后续添加新项的代码...
    finally
      lvPluginInstaller.EndUpdate;
    end;
  end);

为什么这样就能解决?

  • 先停掉所有加载线程,避免后续给已经被清理的ListItem赋值Bitmap,杜绝“幽灵Bitmap”。
  • 手动释放每个Bitmap,确保内存被立刻回收,而不是等着GC或者ListView的缓存机制。
  • 转移Bitmap的所有权,避免重复释放或者内存泄漏。

按这个方法改完后,你再测试一下,重复加载和清理应该就不会内存持续上涨了。

内容的提问来源于stack exchange,提问作者Adriaan

火山引擎 最新活动