关于大图片加载的速度太慢导致界面卡顿的问题

本文讨论了解决Delphi Android应用中大图载入导致UI卡顿的问题,建议使用线程异步加载文件并内存处理,确保主线程负责界面更新。

在 Delphi 盒子论坛上有人提出这个问题。我写了一大段回复,结果提示有非法字符,没法提交。所以把回复帖到这里来。

原帖地址:盒子论坛 v2.1

原文内容如下:

本人菜鸟一枚。LoadFromFile载入大图会造成UI卡顿,之前在Win平台使用PostMessage方法解决,请问在安卓平台解决载入大图卡顿的方法。
我尝试使用匿名线程的方式,网上查询会造成阻塞。求解决办法。
 

procedure TMainForm.ListBoxItem13Click(Sender: TObject);
var
  SearchField: string; // 要查找的字段名
  SearchValue: string; // 要查找的字段值
  ai: TAniIndicator;
begin
  TabControl3.TabIndex := 0;
  Text1.Text := '规划图';
  MainTab2CiyeTab.ExecuteTarget(self);

  SearchField := 'name';
  SearchValue := '%' + '规划图' + '%';
  ImageViewer1.Bitmap:=nil;
  if ImageViewer1.Bitmap.IsEmpty then
  begin
  TThread.CreateAnonymousThread(
    procedure()
    begin
      try
        TThread.Synchronize(TThread.CurrentThread,
          procedure()
          begin
          ai := TAniIndicator.Create(ImageViewer1);
          ai.Parent := ImageViewer1;
          ai.Style := TAniIndicatorStyle.Circular;
          ai.StyleLookup := 'aniindicatorstyle';
          ai.Height := 50;
          ai.Width := 50;
          ai.Position.X := (ImageViewer1.Width - ai.Width) / 2;
          ai.Position.Y := (ImageViewer1.Height - ai.Height) / 2;
          ai.Visible := true;
          ai.Enabled := true;
          end);
          sleep(10);
          TThread.Synchronize(TThread.CurrentThread,
          procedure()
          begin
          if System.SysUtils.FileExists(TPath.Combine(TPath.GetPublicPath,'规划图')) then
          begin
//          Application.ProcessMessages;
          ImageViewer1.Bitmap.LoadFromFile
          (TPath.Combine(TPath.GetPublicPath, '规划图.jpg'))
          end
          else
          begin
          TThread.Synchronize(TThread.CurrentThread,
          procedure()
          begin
          Toast('照片载入中...');
          with FDQuery3 do
          begin
          close; // 先关闭数据模块中的Query
          Sql.Clear; // 清空Query中的SQL值
          Sql.Add('select * from  heliushuixitu where ' + SearchField
          + ' like ''%' + SearchValue + '%''');
          Open();
          TBlobField(FieldByName('image'))
          .SaveToFile(TPath.Combine(TPath.GetPublicPath,'规划图.jpg'));
//          Application.ProcessMessages;
          ImageViewer1.Bitmap.LoadFromFile
          (TPath.Combine(TPath.GetPublicPath, '规划图.jpg'));
          end;
          end);
          end;
          end);
      finally
        ai.Enabled := False;
        ai.Visible := False;
        ai.Free;
      end;
      sleep(1000);
    end).start;
  end;
  ToolButtion.Visible := true;
  ToolShareButton.Visible := true;
end;

我的回复如下:

首先看原文的这段代码:

TThread.Synchronize(TThread.CurrentThread,
          procedure()
          begin
          if System.SysUtils.FileExists(TPath.Combine(TPath.GetPublicPath,'规划图')) then
          begin
//          Application.ProcessMessages;
          ImageViewer1.Bitmap.LoadFromFile
          (TPath.Combine(TPath.GetPublicPath, '规划图.jpg'))
          end
end

这段代码,是把从文件加载图片到 ImageViewer 的过程,放进主进程执行。那么,你又何必用线程?

如果 ImageViewer1.Bitmap.LoadFromFile 因为文件大导致很慢导致 UI 卡,那么,你这段代码,一样卡。因为你这段代码和线程没有任何关系。

如果 ImageViewer1.Bitmap.LoadFromFile 因为文件大导致很慢,你执行 ProcessMessage 也没有任何意义。ProcessMessage 通常是用于在主线程里面有个循环很耗时,比如循环执行10万次,那么,在循环里面,每执行一次,执行一次 ProcessMessage 让主线程可以处理一下 Windows 的消息让界面不会不响应鼠标键盘等等,让界面不要看起来像是卡住死掉一样。

如果是非常耗时的代码,不想让界面卡住,那耗时的代码用线程来执行而不是用主线程来执行,是比较好的。比如上面我说的那种10万次的循环。

但是,一旦要动界面元素,比如 ImageViewer1 那么,必须用主线程。用线程去操作界面元素,是会出问题的。

因此,这里唯一可以用线程去做事的地方就是加载文件,以及把文件变成 Bitmap;

所以,你可以试试分两步:


1. 用线程加载文件;比如 MemoryStream.LoadFromFile;把文件加载进内存;你可以在 LoadFromFile 的前面和后面加上 GetTickCount 看看前后用了多少时间;


2. 把内存数据变成 Bitmap,比如用 Bitmap.LoadFromStream;同样,你可以加上 GetTickCount 看看这句话会消耗多少时间;

最后,你可以在主线程里面,使用 ImageViewer1.Bitmap.Assign(YourBitMap) 的方式,把加载到内存里面的 bitmap 显示出来。这句话因为是操作界面元素,必须放到主线程里面。同样,你也可以用 GetTickCount 看看这句话需要多少时间。

最后,也就是 3 楼说的,你可以在线程里面,直接用一个内存变量 TBitmap 加载文件,而不是我上面说的分两步加载。你也可以看看这样做需要多少时间。

总结一下:


1. 加载文件数据进内存如果很耗时,这个代码如果在主线程执行,就会把主线程卡住。那么这个加载的代码,可以用线程来执行,避免卡住界面。

2. 最终要显示到界面上,比如把图片的内存数据赋值给 TImage 或者 TImageViewer,因为会影响界面变动,必须要使用主线程来操作。

3. 所以,假设时间消耗比较多是在第一步,那么你把第一步放进线程,会解决界面卡住的问题;如果时间消耗比较多是在第二步,因为第二步必须在主线程执行,如果它消耗时间导致界面被卡住,那就没有任何办法了。

BTW:


上面第三条,我说没有办法了。其实办法还是有的,那就是:
1. 不改动界面的情况下,在内存里面,把图片缩小;这个缩小比例的代码,可以用线程来执行;


2. 显示缩小的图片,肯定比显示非常大的图片,耗时更短。


3. 你甚至可以把图片缩小为几个档次,然后界面首先加载最小的图片;然后再逐渐加载更大的图片。假设你的界面非常大,就会看到图片一开始很模糊,逐渐变清楚。如果你的界面尺寸没有多大,实际上你也没必要显示一个非常大的图片,你只需要显示这个图片的缩小版本就好了。这就好比,你把一个像素为 1万X1万的图片,显示到手机的小屏幕上,和显示像素为1千 X 1千的图片,在手机屏幕上看,清晰度是没有区别的。因此你没有必要显示那个像素是 1万 X 1万的大图片。因此,你可以先用线程把它缩小后,再用主线程去显示。这样就解决了主线程显示一个非常大的图片可能会比较耗时的问题。

如果在C#中使用DataGridView加载量数据时速度,可以考虑以下几个优化方案: 1. 使用虚拟模式(Virtual Mode):DataGridView的虚拟模式允许你按需加载数据,而不是一次性加载所有数据。通过实现DataGridView的VirtualMode属性和相关事件(比如CellValueNeeded事件),可以在需要显示数据时进行查询或加载。这样可以减少初始化时间和内存占用。 2. 数据分页:如果你的数据量很,可以考虑将数据进行分页加载。只加载当前页需要显示的数据,然后根据用户操作切换到其他页时再加载相应的数据。这样可以减少内存占用和加载时间。 3. 使用异步加载:将数据加载操作放在后台线程中进行,可以避免阻塞UI线程,提升用户体验。你可以使用Task或BackgroundWorker等技术来实现异步加载。 4. 数据筛选:如果数据量较,可以考虑在加载数据之前进行筛选,只加载符合条件的数据。这样可以减少加载的数据量。 5. 避免频繁刷新:在加载数据时,暂时禁用DataGridView的自动刷新功能,待数据加载完毕后再手动刷新一次。频繁的刷新会导致界面卡顿和性能下降。 6. 使用数据绑定:使用数据绑定可以简化代码,并提升性能。可以考虑使用BindingSource、DataTable或List等进行数据绑定,而不是直接操作DataGridView的行和列。 通过以上优化方案,你可以改善C#中DataGridView加载量数据时的速度问题。根据具体情况选择合适的优化方法,或者结合多种方法进行优化。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值