0

Hi i have my adapter for RecyclerView and i need to show a large list (different lists with images). All images i am loading into ImageView from a separate thread. Each thread for each ImageView. The problem is in that when i am changing data for my adapter and calling notifyDataSetChanged(); - sometimes the new ImageViews are displaying images from a previous data set. I understand that this is happening because of RecyclerView is reusing the ViewHolder item - in which the old thread is still downloading the old image, not new. So i am wondering how can i stop all threads when my dataset in adapter is changing. May be the approach is to put all threads into an array and stop all of them before calling notifyDataSetChanged();? But i think there are can be more elegant solutions? This is my code:

@Override
public void onBindViewHolder(ItemHolder holder, int position) {
            ........
            holder.itemIcon.setImageResource(tree.getDefaultCover());

            BitmapWorkerTask bitmapWorkerTask = new BitmapWorkerTask(holder.itemIcon, tree);
            bitmapWorkerTask.execute(file);
            ......
}

then in my Worker task i am setting Bitmap in postExecute method

    @Override
    protected void onPostExecute(Bitmap result) {
        super.onPostExecute(result);
         mIconView.setImageBitmap(result);
    }
Fedor
  • 83
  • 1
  • 9
  • 1
    You need to have a reference to your task in your view holder and you should cancel and/or terminate the task/thread before scheduling a new one. Or have some sort of a map that holds the references. However, I'd advise not to invent the wheel and use Picasso. It has a decent solution for working inside the Adapter – vkislicins Jan 25 '17 at 12:37
  • The problem is in that my adapter got Bitmap objects, not urls or paths to files. And most of these libraries are not working with Bitmaps, only paths, urls or resources. – Fedor Jan 25 '17 at 12:43
  • I haven't tried using Bitmaps with Picasso, but check this thread out: http://stackoverflow.com/questions/24302431/how-to-load-a-bitmap-with-picasso-without-using-an-imageview – vkislicins Jan 25 '17 at 12:46
  • They are also dealing with image URL-s in that thread(( – Fedor Jan 25 '17 at 12:53
  • 1
    Then I guess you need to build the reference map or keep a reference to the task inside your view holder. Just beware of memory leaks and use weak reference and check for nulls in case your task returns after the holder no longer exists. Also, beware of storing too many bitmaps in memory - you'll be surprised how many devices will allocate very little memory for your app and you might run into OOM troubles. You'll also have to manage recycling the bitmaps yourself, as I don't think that recycled ViewHolder will recycle the bitmap. – vkislicins Jan 25 '17 at 12:59
  • Thanks. I get Bitmaps from DiskLruCache and the display them in my views. Now i have a refernce to BitmapWorkerTask in every ViewHolder item. And before filling it with new data i am now cancelling the worker task – Fedor Jan 25 '17 at 13:05

4 Answers4

1

With AsycTask.execute only one task/thread is actve at a time. the next is started when the previous finishes. in your task you have to check isCanceled() at least in the beginning of doInBackground and in the beginning of onPostExecute.

If holder knows taks and task knows holder you can ask each holder to stop it-s task (call the cancle method).

since you have a cyclic reference between holder and task there is a risk for memory leaks.

you must make shure that the holder forgets it-s task once it is cancleded/finished or there is a configuration change (i.e. screen rotation).

I have done this in APhotoManager. It contains a GalleryCursorFragment with the gridview and a GalleryCursorFragment with an embedded GridCellViewHolder

k3b
  • 14,517
  • 7
  • 53
  • 85
1

Android Dev site has addressed the exact problem for working with images in a list UI. Basically the idea is to define a wrapper over the drawable which keeps a reference to the task which is supposed to load the image for it. Since views are recycled in a list UI, a particular view might get recycled even before the task to load the image for it could be executed. This pattern helps to preempt this and cancel the task.

https://developer.android.com/training/displaying-bitmaps/process-bitmap.html#concurrency

Dibzmania
  • 1,934
  • 1
  • 15
  • 32
0

No need to reinvent a wheel here. Just use Glide.

Glide.with(this).load("img_url").into(imageView);

Done.

Andrej Jurkin
  • 2,216
  • 10
  • 19
  • The problem is in that my adapter got Bitmap objects, not urls or paths to files. And most of these libraries are not working with Bitmaps, only paths, urls or resources. May be i am wrong, but Glide is also not loading Bitmaps – Fedor Jan 25 '17 at 12:43
0

you can stop do in asyncktack to get new image in recycler view use this code for stoping asncktack if (asyncktask != null){ asyncktask .cancel(true);} then call notifyDataSetChanged();

android_jain
  • 788
  • 8
  • 19
  • By the way, canceling an async task doesn't really cancel the async task... – vkislicins Jan 25 '17 at 12:45
  • ya it will stop suddenly and it will not get data – android_jain Jan 25 '17 at 12:49
  • 1
    don't think so. From the docs: `A task can be cancelled at any time by invoking cancel(boolean). Invoking this method will cause subsequent calls to isCancelled() to return true. After invoking this method, onCancelled(Object), instead of onPostExecute(Object) will be invoked after doInBackground(Object[]) returns. To ensure that a task is cancelled as quickly as possible, you should always check the return value of isCancelled() periodically from doInBackground(Object[]), if possible (inside a loop for instance.)` – vkislicins Jan 25 '17 at 12:51