原文自:http://android.eoe.cn/topic/ui
BitmapFactory的decode()方法,在Load Large Bitmaps Efficiently要点中进行讨论,不应该执行在主UI线程如果要读取源数据从磁盘或网络位置(或相对内存来说任何别的真实来源).该数据需要加载的时间是不可预知的,并取决于多种因素(从磁盘或网络的读取速度,图像大小,CPU的功率,等).如果这些任务阻塞UI线程,系统标志您的应用程序无响应,用户可以选择关闭它响应(有关更多信息,请参阅Designing for Responsiveness).
这节课将引导您通过在后台线程中使用AsyncTask处理位图,并告诉您如何处理并发问题.
使用一个异步任务
AsyncTask类提供了一种简单的方式来在一个后台线程中执行许多任务,并且把结果反馈给UI线程.使用的方法是,创建一个继承与它的子类并且实现提供的方法.这里是一个使用AsyncTask和decodeSampledBitmapFromResource()加载一个大图片到ImageView中的例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
class BitmapWorkerTask extends AsyncTask {
private final WeakReference imageViewReference;
private int data = 0;
public BitmapWorkerTask(ImageView imageView) {
// Use a WeakReference to ensure the ImageView can be garbage collected
imageViewReference = new WeakReference(imageView);
}
// Decode image in background.
@Override
protected Bitmap doInBackground(Integer... params) {
data = params[0];
return decodeSampledBitmapFromResource(getResources(), data, 100, 100));
}
// Once complete, see if ImageView is still around and set bitmap.
@Override
protected void onPostExecute(Bitmap bitmap) {
if (imageViewReference null) {
final ImageView imageView = imageViewReference.get();
if (imageView != null) {
imageView.setImageBitmap(bitmap);
}
}
}
}
|
把ImageView设置成WeakReference,是因为这能够确保它所指向的ImageView和任何东西在垃圾回收时不被AsyncTask所阻止掉. 并不能保证ImageView在任务要结束时仍然存在,所以你必须在onPostExecute()方法中检查它的引用. ImageView可能不再存在了,例如,如果在任务要结束之前用户已经离开了当前Activity或者屏幕发生了旋转.
为了异步地加载位图,简单地创建一个新的任务并且执行它:
1 2 3 4 |
public void loadBitmap(int resId, ImageView imageView) {
BitmapWorkerTask task = new BitmapWorkerTask(imageView);
task.execute(resId);
}
|
处理并发
常见的视图组件例如ListView和GridView如在上一节中当和AsyncTask结合使用时引出了另外一个问题.为了优化内存,当用户滚动时这些组件回收了子视图.如果每个子视图触发一个AsyncTask,当它完成时没法保证,相关的视图还没有被回收时已经用在了别的子视图当中.此外,还有异步任务开始的顺序是不能保证他们完成的顺序.
这篇文章透过Multithreading for Performance功能讨论处理并发,并且提供了一个当任务完成后ImageView将一个引用存储到后面能被检查的AsyncTask的解决方案.使用类似的方法,从上一节的AsyncTask可以扩展到遵循类似的模式.
创建一个专用的Drawable的子类来存储一个引用备份到工作任务中.在这种情况下,一个BitmapDrawable被使用以便任务完成后一个占位符图像可以显示在ImageView中:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
static class AsyncDrawable extends BitmapDrawable {
private final WeakReference bitmapWorkerTaskReference;
public AsyncDrawable(Resources res, Bitmap bitmap,
BitmapWorkerTask bitmapWorkerTask) {
super(res, bitmap);
bitmapWorkerTaskReference =
new WeakReference(bitmapWorkerTask);
}
public BitmapWorkerTask getBitmapWorkerTask() {
return bitmapWorkerTaskReference.get();
}
}
|
执行BitmapWorkerTask前,你创建一个AsyncDrawable,并将其绑定到目标ImageView:
1 2 3 4 5 6 7 8 9 |
public void loadBitmap(int resId, ImageView imageView) {
if (cancelPotentialWork(resId, imageView)) {
final BitmapWorkerTask task = new BitmapWorkerTask(imageView);
final AsyncDrawable asyncDrawable =
new AsyncDrawable(getResources(), mPlaceHolderBitmap, task);
imageView.setImageDrawable(asyncDrawable);
task.execute(resId);
}
}
|
如果别的正在运行的任务已经和这个ImageView关联,cancelPotentialWork引用在上面的代码示例检查中.如果这样,它试图通过调用cancel())取消先前的任务.在少数情况下,新的任务数据匹配现有的任务,而且并不需要做什么.下面是实现 cancelPotentialWork:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
public static boolean cancelPotentialWork(int data, ImageView imageView) {
final BitmapWorkerTask bitmapWorkerTask = getBitmapWorkerTask(imageView);
if (bitmapWorkerTask != null) {
final int bitmapData = bitmapWorkerTask.data;
if (bitmapData != data) {
// Cancel previous task
bitmapWorkerTask.cancel(true);
} else {
// The same work is already in progress
return false;
}
}
// No task associated with the ImageView, or an existing task was cancelled
return true;
}
|
一个帮助方法,getBitmapWorkerTask(),使用以上来检索一个和特定ImageView相关的任务:
1 2 3 4 5 6 7 8 9 10 |
private static BitmapWorkerTask getBitmapWorkerTask(ImageView imageView) {
if (imageView != null) {
final Drawable drawable = imageView.getDrawable();
if (drawable instanceof AsyncDrawable) {
final AsyncDrawable asyncDrawable = (AsyncDrawable) drawable;
return asyncDrawable.getBitmapWorkerTask();
}
}
return null;
}
|
这最后一步是在BitmapWorkerTask更新onPostExecute()方法,以便任务取消时并且当前任务和这个ImageView关联时进行检查:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
class BitmapWorkerTask extends AsyncTask {
...
@Override
protected void onPostExecute(Bitmap bitmap) {
if (isCancelled()) {
bitmap = null;
}
if (imageViewReference != null && bitmap != null ) {
final ImageView imageView = imageViewReference.get();
final BitmapWorkerTask bitmapWorkerTask =
getBitmapWorkerTask(imageView);
if (this == bitmapWorkerTask && imageView != null) {
imageView.setImageBitmap(bitmap);
}
}
}
}
|
现在这个实现适合使用ListView和GridView控件组件以及回收其子视图的任何其他组件.在你正常地给你的ImageView控件设置图片时简单地调用loadBitmap就行了.例如,在一个GridView中实现的方式是在支持的适配中的android.view.View, android.view.ViewGroup) getView()方法中.
相关推荐
当后台计算完成新图像后,会先将结果存储到一个临时的缓存中,然后在合适的时间(比如在UI线程空闲时)将这个缓存替换到前台显示的位图,从而避免频繁的内存交换和UI重绘。 4. **自定义控件**:项目提到可以修改...
在双线程模型中,事件循环通常在主线程运行,处理UI事件,而后台线程则负责处理那些可能需要较长时间才能完成的事件。 在实现双事件处理线程时,需要注意线程间的同步问题,以防止数据竞争和死锁。例如,当后台线程...
4. **异步加载**:为了防止UI线程阻塞,通常会采用异步加载位图的方式,如使用`AsyncTask`或`Loader`。这样在后台线程完成图片解码和压缩,然后在UI线程更新显示,提高用户体验。 5. **位图格式选择**:选择合适的...
总结,MFC多线程显示图片的关键在于正确地创建和管理线程,有效地进行图片处理,并确保UI更新的线程安全性。通过遵循以上步骤,我们可以提高应用的响应速度,为用户提供更流畅的体验。在Visual Studio 2015中,利用...
由于UI线程不能长时间阻塞,因此对于耗时的操作(如位图处理),需要在后台线程执行,避免影响用户体验。活动对象通过继承CActive类,并重写DoRun()方法来实现这一目的。BitmapScaler的实现很可能利用了活动对象来...
在多线程环境下使用GDI需要注意线程安全问题,因为多个线程可能同时访问GDI资源,如画刷、字体、位图等,这可能导致数据冲突和意外的行为。 1. **创建多线程**: - 在MFC中,你可以通过派生自`CWinThread`的类来...
- 考虑使用异步加载,如AsyncTask或Loader,避免阻塞UI线程。 8. **模拟器测试**: - 提到的例子已在模拟器上实现,这意味着开发者可以通过Android Studio的AVD管理器创建模拟器进行调试和测试。 9. **代码注释*...
1. **位图处理**:理解和熟练使用位图操作,包括位图的加载、裁剪、合并和透明度处理,是DirectUI界面设计的基础。开发者需要了解如何通过GDI+或DirectX等图形库进行位图操作。 2. **坐标系统和渲染**:DirectUI...
在单线程应用中,所有的任务都在一个线程中执行,这可能导致UI更新被阻塞,尤其是在进行耗时操作如绘图时。多线程则允许我们同时运行多个独立的任务,一个线程负责处理绘图,另一个线程则负责用户交互,这样可以提高...
为了避免UI线程阻塞,大型图片的加载应该在子线程中进行。可以使用`AsyncTask`或其他异步框架如`Handler`或`Loader`来实现。Android也提供了`Glide`、`Picasso`等库,它们可以方便地处理图片的加载、缓存和显示。 ...
在这个场景下,我们可以创建一个后台线程来接收和处理来自服务器的屏幕截图数据。 1. **创建网络连接** 使用`System.Net.Sockets.TcpClient`类建立与服务器的TCP连接。在客户端初始化时,我们需要指定服务器的IP...
3. **httpui.c**:这个文件可能是用户界面(UI)的实现代码,处理与用户交互相关的事件,如窗口创建、消息处理、控件操作等。在Windows编程中,UI通常由消息循环和窗口过程函数构成,通过消息队列来处理用户的输入和...
8. **多线程编程**:Windows支持多线程,开发者可以创建多个并发执行的线程,提高程序的响应性和效率。 9. **错误处理**:Windows API函数返回值通常用于表示操作成功与否,开发者需要正确处理错误,以确保程序的...
总结,创建一个MFC聊天UI界面涉及多个步骤,包括项目初始化、UI设计、控件关联、网络通信、多线程处理、数据存储以及界面美化。通过VS2010提供的工具和MFC库的支持,这些任务可以相对容易地完成。虽然在这个例子中...
5. **异步加载**:为了防止UI线程阻塞,通常会采用异步加载位图,如使用AsyncTask或者现代的库如 Glide 或 Picasso,它们能自动处理位图的下载、解码和显示,同时优化了内存使用。 6. **自定义ImageView**:在某些...
另外,合理使用线程可以避免UI阻塞,提升用户体验。 七、总结 J2ME高级UI设计涉及到多个方面,从触摸屏操作到自定义组件的开发,再到资源管理和性能优化。掌握这些技术,开发者可以构建出功能强大、交互性好且具有...
UI的异步绘制可以通过layer的代理方法`displayLayer:`实现,由代理生成对应的位图并设置为layer的contents属性,这样可以在后台线程进行,避免阻塞主线程。 七、离屏渲染 离屏渲染发生在GPU不在当前屏幕缓冲区中...
5. **AsyncTask**或**Loader**: 加载大图时,应在后台线程进行,以避免阻塞UI。可以使用`AsyncTask`或者`Loader`来异步加载位图,加载完成后再更新到UI上。 6. **Bitmap.createBitmap()**: 有时我们需要根据需求...
7. **多线程和异步操作**:在大型应用程序中,了解如何在DirectUI中实现多线程和异步操作以提高用户体验也很关键。 8. **调试技巧**:学习如何使用Visual Studio中的调试工具,如断点、监视窗口、调用堆栈等,以...