在 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. 所以,假设时间消耗比较多是在第一步,那么你把第一步放进线程,会解决界面卡住的问题;如果时间消耗比较多是在第二步,因为第二步必须在主线程执行,如果它消耗时间导致界面被卡住,那就没有任何办法了。
上面第三条,我说没有办法了。其实办法还是有的,那就是:
1. 不改动界面的情况下,在内存里面,把图片缩小;这个缩小比例的代码,可以用线程来执行;
2. 显示缩小的图片,肯定比显示非常大的图片,耗时更短。
3. 你甚至可以把图片缩小为几个档次,然后界面首先加载最小的图片;然后再逐渐加载更大的图片。假设你的界面非常大,就会看到图片一开始很模糊,逐渐变清楚。如果你的界面尺寸没有多大,实际上你也没必要显示一个非常大的图片,你只需要显示这个图片的缩小版本就好了。这就好比,你把一个像素为 1万X1万的图片,显示到手机的小屏幕上,和显示像素为1千 X 1千的图片,在手机屏幕上看,清晰度是没有区别的。因此你没有必要显示那个像素是 1万 X 1万的大图片。因此,你可以先用线程把它缩小后,再用主线程去显示。这样就解决了主线程显示一个非常大的图片可能会比较耗时的问题。