RecyclerView:异步图像加载

时间:2014-10-23 10:20:22

标签: android android-asynctask android-recyclerview

我正在使用RecyclerView来显示包含imageView的列表。为了使UI更流畅,我将保存在SD卡上的58dp缩略图加载到这些具有asyncTask的imageViews中。

问题是,一旦childView进入可视化显示,其他数据中的旧图像将被重用,然后在AsyncTask完成后替换。我可以通过在imageView中将onPreExecute位图设置为null来停止改组。

有没有办法真正重复使用旧图像,或者每次新的View到位时我是否真的需要从SD卡加载图像?这使得视图非常难看,因为首先存在错误的图像或者图像是纯白色的。

7 个答案:

答案 0 :(得分:32)

由于视图重用,您将使用已有内容的视图获取视图,如果您使用的是ListViews模式,这也是ViewHolder的问题。

这里有两个解决方案,好的做法和糟糕的黑客攻击:

  • 在良好实践中,您将ImageView设置为不显示任何内容 bindViewHolder(VH holder, int position)开始使用 setDrawable(null)或类似。

  • 在糟糕的黑客行为中,您不会回收/重复使用视图,不会强制执行ViewHolder模式,并且每次都会对其进行充气,但这只是允许的在ListView和其他旧组件中。

答案 1 :(得分:8)

您应该查看universal image loader。 它具有内存缓存,磁盘缓存,并且它异步加载您的图像,因此不会阻止ui。您可以设置默认图像和/或无法获取图像等。它可以对图像进行采样以减少位图的内存占用。我真的建议你用它来拍照。

不要为您的情况禁用可回收,因为它毫无意义。必须回收图像,因为如果没有正确采样,它们的位图可绘制会产生非常高的内存过载。

RecyclerViewAdapter中的示例用法:

@Override
public void onBindViewHolder(CustomViewHolder viewHolder, int position) {
    String imageUri = "";//local or remote image uri address
    //viewHolder.imgView: reference to your imageview
    //before you call the displayImage you have to 
    //initialize imageloader in anywhere in your code for once.   
    //(Generally done in the Application class extender.)
    ImageLoader.getInstance().displayImage(imageUri, viewHolder.imgView);
}

编辑: 如今,我认为Glide是我的主要图像加载和缓存库。  您可以像这样使用它:

Glide.with(context)
    .load(imageUri)
    .placeholder(R.drawable.myplaceholder)
    .into(imageView);

答案 2 :(得分:4)

您应该在开始新请求之前取消旧请求,但无论是否取消,如果两个图像在同一个容器/视图持有者上同时或多或少地加载,则仍然可以显示错误的图像(发生轻松快速滚动和小图像。)

解决方案是:

  1. 在onBindViewHolder期间在View Holder中存储一些唯一标识符(这是同步发生的,因此如果VH被回收,这将被覆盖)
  2. 然后异步加载图像(使用AsynchTask,RxJava等)并在异步调用中传递此唯一ID以供参考
  3. 最后,在用于后处理的图像加载方法(onPostExecute for AsyncTasks)中,检查异步请求中传递的id是否与View Holder中当前的id相同。
  4. 使用RxJava从后台应用加载图标的示例:

     public void loadIcon(final ImageView appIconView, final ApplicationInfo appInfo, final String uniqueAppID) {
        Single.fromCallable(() -> {
                return appIconView.getContext().getPackageManager().getApplicationIcon(appInfo);
            })
              .subscribeOn(Schedulers.computation())
              .observeOn(AndroidSchedulers.mainThread())
              .subscribe( drawable -> {
                     if (uniqueAppID.equals(mUniqueAppID)) { // Show always the correct app icon
                        appIconView.setImageDrawable(drawable);
                     }
                 }
             );
    }
    

    此处mUniqueAppID是onBindViewHolder更改的视图持有者的字段

答案 3 :(得分:1)

您必须在" onBindViewHolder"中取消旧请求方法:

try{
        ((SpecialOfferViewHolder)viewHolder).imageContainer.cancelRequest();
}catch(Exception e) {

}

请记得在viewHolder中保存图像容器:

public void onResponse(ImageContainer response, boolean arg1) {
                ((SpecialOfferViewHolder)viewHolder).imageContainer=response;

}

答案 4 :(得分:0)

MLProgrammer所说的是非常正确的。

解决方案,幸运的是,这很简单:停止回收

holder.setIsRecyclable(false);

这种选择有其后果,因为做我上面建议的做法会抑制RecyclerView的主要目的之一:回收。

  • 您的RecyclerView滚动速度会变慢(每次都必须创建一个新的Holder,再次从资源中充气);
  • 你对内存的使用会更大。

答案 5 :(得分:0)

我想补充一下好习惯:

  

在良好实践中,您将ImageView设置为在bindViewHolder(VH holder,int position)的开头使用setDrawable(null)或类似内容显示任何内容。

不显示任何内容,而是显示加载程序图像,以便向用户提供反馈,以便在该视图中进行某些处理,并在一段时间内看到结果。只看空白视图不是一种好习惯,您需要向用户提供反馈。

答案 6 :(得分:-1)

您应该使用Picasso适用于Android的强大的图片下载和缓存库。 易于使用和强大的库。使用此库,您可以从资源,资产,文件,内容提供程序异步或同步地获取图像。