我有一个包含自定义项目的ListView,如下所示:
灰色方块是ImageView。填充ListView的数据来自Cursor形式的数据库。但是图像不是直接存储在数据库中,而是在SDCard中,数据库只保存对它们的String引用。
一开始我用覆盖CursorAdapter的bindView()回调方法将Image解码为Bitmap:
Bitmap bmp = BitmapFactory.decodeFile(imageLocation);
holder.imageHolder.setImageBitmap(bmp);
但ListView滚动非常滞后。所以我阅读了Executor框架并实现了它,用以下内容替换了以前的代码:
ImageView imageView = holder.imageHolder;
asyncImageLoader.DisplayImage(imageLocation, imageView);
创建AsyncImageLoader类。它在其构造函数中创建一个最多包含5个工作线程的线程池,以处理发送到工作队列的Runnables。然后,当我从自定义CursorAdapter调用DisplayImage()方法时,它会检查位置String是否包含url。如果是,则将ImageLoader Runnable发送到线程池的工作队列。如果位置包含" N / A",则将默认图像设置为ImageView。
当可用的工作线程处理ImageLoader Runnable时,SDCard中的图像被解码为Bitmap,并且ImageDisplayer Runnable被发送到主线程的消息队列,以在UI中显示图像:
public class AsyncImageLoader {
ExecutorService executorService;
Handler handler = new Handler();
public AsyncImageLoader() {
this.executorService = Executors.newFixedThreadPool(5);
}
public void DisplayImage(String location, ImageView imageView) {
if(!location.matches("N/A")) {
queueImageDecoding(location, imageView);
} else {
imageView.setImageDrawable(imageView.getContext().getResources().getDrawable(R.drawable.not_available));
}
}
private void queueImageDecoding(String location, ImageView imageView) {
executorService.execute(new ImageLoader(location, imageView));
}
class ImageLoader implements Runnable {
private String location;
private ImageView imageView;
public ImageLoader(String location, ImageView imageView) {
this.location = location;
this.imageView = imageView;
}
@Override
public void run() {
Bitmap bmp = BitmapFactory.decodeFile(location);
handler.post(new ImageDisplayer(bmp, imageView));
}
}
class ImageDisplayer implements Runnable {
private Bitmap bitmap;
private ImageView imageView;
public ImageDisplayer(Bitmap bitmap, ImageView imageView) {
this.bitmap = bitmap;
this.imageView = imageView;
}
@Override
public void run() {
if(bitmap != null) {
imageView.setImageBitmap(bitmap);
}
}
}
}
问题是我仍然在进行Laggy滚动。如果我摆脱ImageLoader.run()方法中的代码,滚动是完美的。那个代码应该在工作线程中处理吗?我在这里缺少什么?
更新
由于在滚动发生时重复使用ListView中的视图,因此从工作线程返回的位图在单个ImageView中设置了几次。 所以可能的解决方案是:
我正在使用Future对象取消任务。哪个存储在标记为自定义CursorAdapter内的项View的持有者中:
public class MyCustomAdapter extends CursorAdapter {
...
public AsyncImageLoader asyncImageLoader;
private static class ViewHolder {
ImageView imageHolder;
TextView text1Holder;
TextView text2Holder;
TextView text3Holder;
Button buttonHolder;
Future<?> futureHolder;
}
public MyCustomAdapter(Context context, Cursor c, int flags) {
...
this.asyncImageLoader = new AsyncImageLoader();
}
@Override
public View newView(Context context, Cursor cursor, ViewGroup parent) {
...
return view;
}
@Override
public void bindView(View view, Context context, Cursor cursor) {
ViewHolder holder = (ViewHolder)view.getTag();
String location = ...;
ImageView imageView = holder.imageHolder;
if(holder.futureHolder == null) {
holder.futureHolder = asyncImageLoader.DisplayImage(location, imageView);
} else {
if(!holder.futureHolder.isDone())
holder.futureHolder.cancel(true);
holder.futureHolder = asyncImageLoader.DisplayImage(location, imageView);
}
...
}
}
每次重复使用项目视图时,我都会检查持有者的未来对象是否为Done()。如果不是,我用Future.cancel取消任务(true)。但现在,问题是任务完成得太快而无法取消。如果我让工作线程进入睡眠状态,让我们说1秒钟,那么任务持续时间足以取消并且ListView滚动效果更好。但是我必须等待1秒才能显示图像,我不想要它。
public class AsyncImageLoader {
....
public Future<?> DisplayImage(String location, ImageView imageView) {
if(!location.matches("N/A")) {
return executorService.submit(new ImageLoader(location, imageView));
} else {
imageView.setImageDrawable(imageView.getContext().getResources().getDrawable(R.drawable.not_available));
return null;
}
}
class ImageLoader implements Runnable {
private String location;
private ImageView imageView;
public ImageLoader(String location, ImageView imageView) {
this.location = location;
this.imageView = imageView;
}
@Override
public void run() {
boolean interrupted = false;
try {
if(!Thread.currentThread().isInterrupted()) {
Thread.sleep(1000);
Bitmap bmp = BitmapFactory.decodeFile(location);
handler.post(new ImageDisplayer(bmp, imageView));
}
} catch (InterruptedException consumed) {
interrupted = true;
} finally {
if(interrupted)
Thread.currentThread().interrupt();
}
}
}
...
}
第二种解决方案是让任务完成,但是当ListView项目已经被重用时,阻止设置旧的Bitmap。但我无法弄清楚如何做到这一点。有什么建议吗?
答案 0 :(得分:1)
好的,最初我从Web服务获取图像,并将它们存储在SD卡中。从我下载的示例中,我相信该服务正在返回具有相同尺寸的所有图像。错误!其中一些比预期的要大,并且在ImageView中设置时会导致延迟。我只需要缩小它们。 Load a Scaled Bitmap Version into Memory