【问题标题】:laggy Listview with ImageView and Executor Framework带有 ImageView 和 Executor Framework 的滞后 Listview
【发布时间】:2014-04-14 00:39:03
【问题描述】:

我有一个带有自定义项目的 ListView,比如这个:

灰色方块是一个 ImageView。填充 ListView 的数据来自 Cursor 形式的数据库。但是图片并不是直接存储在数据库中,而是在 SDCard 中,数据库中只保存了对它们的 String 引用。

一开始我是从覆盖的 CursorAdapter 的 bindView() 回调方法中将图像解码为位图:

 Bitmap bmp = BitmapFactory.decodeFile(imageLocation);
 holder.imageHolder.setImageBitmap(bmp);

但 ListView 滚动非常滞后。所以我阅读了 Executor 框架并实现了它,将之前的代码替换为以下代码:

 ImageView imageView = holder.imageHolder;
 asyncImageLoader.DisplayImage(imageLocation, imageView);

并创建 AsyncImageLoader 类。它在其构造函数中创建了一个最多包含 5 个工作线程的线程池,以处理发送到工作队列的 Runnables。然后,当我从自定义 CursorAdapter 调用 DisplayImage() 方法时,它会检查位置字符串是否包含 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 中的 View 在滚动发生时会被重用,因此从工作线程返回的 Bitmap 会在单个 ImageView 中设置多次。 所以可能的解决方案是:

  • 避免在 ListView 项目已被重用时设置旧的位图。
  • 或者更好的是,取消任务。

我正在使用 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);
    }
    ...
}   

}   

每次重用项目视图时,我都会检查持有者的未来对象是否为已完成()。如果不是,我使用 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 项目已被重用时防止设置旧的位图。但我不知道该怎么做。有什么建议吗?

【问题讨论】:

    标签: android listview concurrency lag executor


    【解决方案1】:

    好的,最初我从 Web 服务获取图像,并将它们存储在 SDCard 中。从我下载的样本中,我相信该服务会返回所有具有相同尺寸的图像。错误的!其中一些比预期的要大,并且在 ImageView 中设置时会导致延迟。我只需要按比例缩小它们。 Load a Scaled Bitmap Version into Memory

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2019-07-26
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多