Universal-Image-Loader: wrong Bitmaps are attached to ImageView
Asked Answered
B

5

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.

Beacham answered 29/12, 2012 at 8:46 Comment(3)
@samintechvalens Please don't bold keywords, if you want to highlight code-related keywords please use backticks for inlining.Xanthochroid
Do you use PauseOnScrollListener?Amblygonite
@NOSTRA yes, I do use it.Beacham
A
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.

Amblygonite answered 29/12, 2012 at 10:51 Comment(12)
Yeah, I just did it. Will test the app for a couple of days to see if works fine.Beacham
Unfortunately, .resetViewBeforeLoading() didn't help - the problem happened again today.Beacham
It seems you inited ImageLoader not once. Are you sure you call imageLoader.init(configuration); only in one place in your app?Amblygonite
yes, I do it only once, in Adapter's constructor. Where should I do it?Beacham
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?Amblygonite
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.Beacham
The correct answer to this question is: do not initialize UIL more than once. Thanks for your help.Beacham
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.Amblygonite
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?Varner
Please read github.com/nostra13/Android-Universal-Image-Loader#user-support attentively.Amblygonite
@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?Ruwenzori
No, it won't override.Amblygonite
B
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);
Barre answered 4/8, 2015 at 7:43 Comment(0)
D
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.

Dianoia answered 29/12, 2012 at 8:52 Comment(2)
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.Beacham
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.Dianoia
T
-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);
Techno answered 29/12, 2012 at 8:57 Comment(0)
R
-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

Reticulum answered 5/2, 2015 at 18:17 Comment(1)
If newView() is called for every list item, the concept of holder goes for a toss! So this is not the right thing direction.Ruwenzori

© 2022 - 2024 — McMap. All rights reserved.