11

I've been evaluating NOSTRA's Universal-Image-Loader library to asynchronously download images and show them in ListView. So far it works fine except for one problem.

Sometimes Bitmaps from memory cache get attached to wrong ImageViews when the list is being scrolled. After scrolling is stopped, correct images are attached. This situation is quite rare and I couldn't find a 100% way to reproduce it. I shot a video last time it happened.

Here is the ArticleAdapter code, both the UIL config and the bindView() method can be found there.

public class ArticleAdapter extends CursorAdapter {
    private LayoutInflater inflater;
    private ViewHolder holder;

    public ArticleAdapter(Context context, Cursor cursor, boolean autoRequery) {
        super(context, cursor, autoRequery);
        imageLoader = ImageLoader.getInstance();
        DisplayImageOptions options = new DisplayImageOptions.Builder()
                .showStubImage(R.drawable.download_progress_thumb)
                .cacheInMemory()
                .cacheOnDisc()
                .imageScaleType(ImageScaleType.IN_SAMPLE_POWER_OF_2)
                .build();
        ImageLoaderConfiguration configuration = new ImageLoaderConfiguration.Builder(context)
                .threadPriority(Thread.NORM_PRIORITY - 2)
                .threadPoolSize(4)
                .discCache(new UnlimitedDiscCache(Utils.getCacheDirectory(context)))
                .defaultDisplayImageOptions(options)
                .build();
        imageLoader.init(configuration);

        titleIndex = cursor.getColumnIndex(Articles.TITLE);
        descriptionIndex = cursor.getColumnIndex(Articles.DESCRIPTION);
        isUnreadIndex = cursor.getColumnIndex(Articles.IS_UNREAD);
        isNewIndex = cursor.getColumnIndex(Articles.IS_NEW);
        urlIndex = cursor.getColumnIndex(Articles.URL);
        hostIndex = cursor.getColumnIndex(Articles.HOST);
        timeIndex = cursor.getColumnIndex(Articles.PUBLISH_TIME);

        bkgUnreadArticle = context.getResources().getColor(R.color.list_bkg_unread_article);
        bkgReadArticle = context.getResources().getColor(R.color.list_bkg_read_article);
        textUnreadTitle = context.getResources().getColor(R.color.list_text_unread_title);
        textReadTitle = context.getResources().getColor(R.color.list_text_read_title);

        inflater = LayoutInflater.from(context);
    }

    @Override
    public void bindView(View view, Context context, Cursor cursor) {
        String date = Utils.format(cursor.getLong(timeIndex), Utils.DATE);
        holder = (ViewHolder) view.getTag();

        holder.titleView.setText(cursor.getString(titleIndex));
        holder.descriptionView.setText(date);

        int isNew = cursor.getInt(isNewIndex);
        if (isNew == 1)
            holder.isNewView.setVisibility(View.VISIBLE);
        else
            holder.isNewView.setVisibility(View.INVISIBLE);

        int isUnread = cursor.getInt(isUnreadIndex);
        if (isUnread == 1){
            holder.titleView.setTextColor(textUnreadTitle);
            holder.rowLayout.setBackgroundColor(bkgUnreadArticle);
        } else {
            holder.titleView.setTextColor(textReadTitle);
            holder.rowLayout.setBackgroundColor(bkgReadArticle);
        }

        String url = cursor.getString(urlIndex);
        String host = cursor.getString(hostIndex);
        if (host.equalsIgnoreCase(Consts.HOST_LENTA) || host.equalsIgnoreCase(Consts.HOST_REALTY)) {
            holder.thumbView.setVisibility(View.VISIBLE);
            imageLoader.displayImage(Utils.makeImageUrl(url, Utils.THUMBNAIL), holder.thumbView);
        } else 
            holder.thumbView.setVisibility(View.GONE);
    }

    @Override
    public View newView(Context context, Cursor cursor, ViewGroup parent) {
        View v = inflater.inflate(R.layout.articlelist_item, null);
        ViewHolder holder = new ViewHolder();
        holder.titleView = (TextView) v.findViewById(R.id.list_title);
        holder.descriptionView = (TextView) v.findViewById(R.id.list_description);
        holder.thumbView = (ImageView) v.findViewById(R.id.list_thumb);
        holder.isNewView = (TextView) v.findViewById(R.id.list_read_unread);
        holder.rowLayout = (LinearLayout) v.findViewById(R.id.list_row);

        v.setTag(holder);
        return v;
    }
}

I would really appreciate any help on this matter.

Community
  • 1
  • 1
Timur D.
  • 123
  • 1
  • 5

5 Answers5

20

For ListViews, GridViews and other lists which are used view re-using in its adapters you should call .resetViewBeforeLoading() in DisplayImageOptions to prevent this effect.

Also documentation says:

Init ImageLoader with configuration only once

Do you do it only once? Adapter's constructor isn't good place for it.

UPD: Sorry, my answer isn't useful. .resetViewBeforeLoading() doesn't help because you use .showStubImage(...). So you should have correct UIL work but you don't. And it's very strange.

nostra13
  • 12,377
  • 3
  • 33
  • 43
  • Yeah, I just did it. Will test the app for a couple of days to see if works fine. – Timur D. Dec 29 '12 at 14:20
  • Unfortunately, .resetViewBeforeLoading() didn't help - the problem happened again today. – Timur D. Dec 30 '12 at 02:28
  • It seems you inited ImageLoader not once. Are you sure you call ```imageLoader.init(configuration);``` only in one place in your app? – nostra13 Dec 30 '12 at 09:08
  • yes, I do it only once, in Adapter's constructor. Where should I do it? – Timur D. Dec 30 '12 at 13:22
  • In application class. Look into example project. Also I updated my answer. Your problem is really strange. Can you describe more details: when this bug is reproduced? After some actions? Often? Constantly? Rarely? Android version? UIL version? – nostra13 Dec 31 '12 at 10:13
  • I guess I found what the problem might be. I did eventually initialize ImageLoader twice in the app. The reason I did that is I need two different configurations: one in ListActivity and the other in Activity that displays the Article. Will refactor it and get back to you later. – Timur D. Dec 31 '12 at 12:02
  • The correct answer to this question is: do not initialize UIL more than once. Thanks for your help. – Timur D. Jan 02 '13 at 11:14
  • Actually the 2nd and following initializations of the ImageLoader do nothing. In you app only the first config works, the 2nd config wasn't considered. If you want to have ImageLoaders with 2 or more different configurations then you should extend ImageLoader class. – nostra13 Jan 02 '13 at 20:10
  • I got the same exact problem even though I called `init(config)` only once. It only loaded correct images for the first time! After that, the images are completely wrong. I called image loader instance via `ImageLoader.getInstance()`, and I couldn't figure out why this occured. Any suggestion? – roxrook Jan 25 '13 at 12:25
  • Please read https://github.com/nostra13/Android-Universal-Image-Loader#user-support attentively. – nostra13 Jan 25 '13 at 12:59
  • @NOSTRA will .resetViewBeforeLoading() help in this situation: think of 2 images added to the same ImageView via fast scroll.. and let's say the first one takes longer to load than the 2nd (correct) one. Won't the 1st (wrong) image then override the 2nd image? – Sarang Jun 24 '15 at 06:20
  • No, it won't override. – nostra13 Jul 11 '15 at 21:58
3

I had this problem on a regular basis, even though I was only initiating the ImageLoader once, I wasn't doing it only when I needed it (in the adaptor), after I changed the init() part in Application class it worked brilliantly. I haven't even had to use restartViewOnLoading() or setStubImage(). Here's the code if necessary.

import android.content.Context;

import com.nostra13.universalimageloader.core.DisplayImageOptions;
import com.nostra13.universalimageloader.core.ImageLoader;
import com.nostra13.universalimageloader.core.ImageLoaderConfiguration;

public class Application extends android.app.Application {

    private static Context mContext;

    @Override
    public void onCreate() {
        super.onCreate();
        mContext = getApplicationContext();

        DisplayImageOptions imgOptions = new DisplayImageOptions.Builder()
            .cacheInMemory(true)
            .showImageOnLoading(R.drawable.default_picture)
            .build();
        ImageLoaderConfiguration imgConfig = new ImageLoaderConfiguration.Builder(mContext)
            .defaultDisplayImageOptions(imgOptions)
            .build();
        ImageLoader.getInstance().init(imgConfig);
    }

    public static Context getAppContext(){
        return mContext;
    }
}

EDIT: You can check this conversation here for a deeper understanding of the issue. Basically there are 3 solutions

1) Set android:layout_width and android:layout_height parameters for ImageViews in dips ('wrap_content' and 'match_parent' are not acceptable)

2) Call ImageLoader after ImageView was drawn (in imageView.post(...):

imageView.post(new Runnable() {
        @Override
        public void run() {
            imageLoader.displayImage(imageUri, imageView);
        }
     });

3) Pass ImageViewAware (instead of ImageView) which doesn't consider actual view size:

Intead of:

imageLoader.displayImage(imageUri, imageView);

do following:

ImageAware imageAware = new ImageViewAware(imageView, false)
imageLoader.displayImage(imageUri, imageAware);
DoruChidean
  • 7,941
  • 1
  • 29
  • 33
0

Just see how to set Holders because I think you have written faulty logic inside your Adapter thats why it is repeating views.

There is also Custom Cursor Adapter with Holder and Get View & BindView discussion.

Community
  • 1
  • 1
Chintan Rathod
  • 25,864
  • 13
  • 83
  • 93
  • I'm afraid that's not the case. It's only the image that has problems, everything else on the item (title and date) is displayed correctly. – Timur D. Dec 29 '12 at 09:03
  • Um.. Have you tried `SimpleImageLoadingListener` in `imageLoader.displayImage`?? Because it will give you some notification like `onLoadingCancelled`,`onLoadingComplete`,`onLoadingStarted` and `onLoadingFailed`. You can perform other changes in this listeners. – Chintan Rathod Dec 29 '12 at 09:10
-1

Add this line in your code ::

holder.thumbView.setTag(Utils.makeImageUrl(url, Utils.THUMBNAIL).get(position));
imageLoader.displayImage(Utils.makeImageUrl(url, Utils.THUMBNAIL), view_holder.image);
AndroidLearner
  • 4,500
  • 4
  • 31
  • 62
-1

I have same problem and fixed it. It is not because Universal-Image-Loader library. It is because you use holder in wrong logic to load image.

Try to replace

    @Override
    public View newView(Context context, Cursor cursor, ViewGroup parent) {
        View v = inflater.inflate(R.layout.articlelist_item, null);
        ViewHolder holder = new ViewHolder();
        holder.titleView = (TextView) v.findViewById(R.id.list_title);
        holder.descriptionView = (TextView) v.findViewById(R.id.list_description);
        holder.thumbView = (ImageView) v.findViewById(R.id.list_thumb);
        holder.isNewView = (TextView) v.findViewById(R.id.list_read_unread);
        holder.rowLayout = (LinearLayout) v.findViewById(R.id.list_row);

        v.setTag(holder);
        return v;
    }

With

@Override
    public View newView(Context context, Cursor cursor, ViewGroup parent) {
        View v = inflater.inflate(R.layout.articlelist_item, null);
        ViewHolder holder = new ViewHolder();
        holder.titleView = (TextView) v.findViewById(R.id.list_title);
        holder.descriptionView = (TextView) v.findViewById(R.id.list_description);
        ImageView thumbView = (ImageView) v.findViewById(R.id.list_thumb);
        imageLoader.displayImage("Your image URL", thumbView);
        holder.isNewView = (TextView) v.findViewById(R.id.list_read_unread);
        holder.rowLayout = (LinearLayout) v.findViewById(R.id.list_row);

        v.setTag(holder);
        return v;
    }

And remember to remove imageloader in your bindView function

Phien
  • 148
  • 1
  • 14
  • If newView() is called for every list item, the concept of holder goes for a toss! So this is not the right thing direction. – Sarang Jun 24 '15 at 06:05