在 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万的大图片。因此,你可以先用线程把它缩小后,再用主线程去显示。这样就解决了主线程显示一个非常大的图片可能会比较耗时的问题。
本文讨论了解决Delphi Android应用中大图载入导致UI卡顿的问题,建议使用线程异步加载文件并内存处理,确保主线程负责界面更新。
597

被折叠的 条评论
为什么被折叠?



