【问题标题】:RecyclerView: Async image-loadingRecyclerView:异步图像加载
【发布时间】:2014-10-23 10:20:22
【问题描述】:

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

问题是,一旦childView 出现在视觉显示中,来自另一个数据的旧图像将被重复使用,然后在AsyncTask 完成后替换。我可以通过将onPreExecute 中的imageView 位图设置为null 来停止洗牌。

有没有办法真正重用旧图像,或者每次新的View 出现时我真的必须从 sd 卡加载图像?这使得视图非常难看,因为要么首先有错误的图像,要么图像是纯白色的。

【问题讨论】:

    标签: android android-asynctask android-recyclerview


    【解决方案1】:

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

    这里有两种解决方案,好的做法和坏的 hack:

    • 在最佳实践中,您将 ImageView 设置为在 bindViewHolder(VH holder, int position) 的开头使用 setDrawable(null) 或类似名称。

    • 1234563 /p>

    【讨论】:

    • 所以附着在视图上的旧位图一旦离开屏幕就会被丢弃?
    • 您可以在读取它们时或删除它们之前将它们放入 LruCache 中,稍后再重新获取它们。
    • 启动ImageView还是必须使用setDrawable(null)
    • 不起作用我尝试使用 setDrawable / setBitmap = null 不起作用
    • 不工作,甚至更多 setBitmap(null) 延迟洗牌,但最后问题仍然存在
    【解决方案2】:

    您应该检查universal image loader。 它具有内存缓存、磁盘缓存,并且异步加载图像,因此不会阻塞用户界面。您可以设置默认图像和/或无法获取图像等。它可以对图像进行采样以减少位图的内存占用。我真的建议您将其用于图像。

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

    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);
    

    【讨论】:

    • 为什么 Glide 为什么不使用 Picasso 呢?有什么优势吗?
    • 毕加索更好+1
    • @mhdjazmati 这取决于要求... Glide 支持动画 GIF 和缩略图(适用于列表视图)。
    【解决方案3】:

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

    解决办法是:

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

    使用 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改变的view holder的一个字段

    【讨论】:

    • 如果id不一样那么imageview drawable会一直为空,这种情况我们应该怎么办?
    • @Duna 是正确的,如果 id 不同,则意味着 viewHolder 已被回收,并且 drawable 不是此视图的正确对象。最终应该会发生具有正确 id 的异步加载。如果视图保持空白,则可能意味着异步图像加载(在 id 检查之前)刚刚失败。
    • 我理解了你的观点,但是假设我们有 10 个可见的项目,每个项目都调用了 onBindView。每张图片的加载需要 1 秒,但 mUniqueAppID 已设置为最新项目(未通过 1 秒) -> 然后只显示最后一张图片
    • @Duna 我看错了,mUniqueAppID是ViewHolder中的一个字段,和ViewHolder中引用的view一样,所以只要view不被回收,就会有1个@ 987654324@ per viewHolder
    【解决方案4】:

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

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

    记得在viewHolder中保存图片容器:

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

    }

    【讨论】:

      【解决方案5】:

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

      【讨论】:

        【解决方案6】:

        我想补充一下良好的做法:

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

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

        【讨论】:

          【解决方案7】:

          MLProgrammer 说的很对。

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

          holder.setIsRecyclable(false);
          

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

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

          【讨论】:

          • 如果您建议使用此解决方案,请至少提及后果,因为这是一种不好的做法。
          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2017-12-10
          • 2011-12-23
          • 2013-04-08
          • 1970-01-01
          相关资源
          最近更新 更多