Imageloader图片错位解决方法

引入问题

最近的工作又用到了Imageloader,之前修改过源码以便于imageloader能够接受加密和解密的图片数据显示,最近在写历史数据界面的时候,发现RecyclerView里面item公用ImageView显示本地和网络图片相间的时候,滑动过快导致图片异步加载出现之后错位的情况。

分析问题并确定问题

出现了这个问题有三种导致这个的情况:
1,RecyclerView的item复用机制出现问题(onBindViewHolder方法的问题)。
2,在item显示逻辑里面出现Visible和Gone之类没有设置默认值的问题。
3,imageloader异步回来之后Imageview判断是否为同一个组件有误导致的问题。

首先检查一下onBindViewHolder,工程使用的是hongyangAndroid开源的万能Adapter,通过查看onBindViewHolder方法,发现并不会导致错位的情况;其次观察item的显示逻辑也是做到了都进行初始化的赋值操作,那么问题就只能怀疑Imageloader框架图片异步加载回来之后的问题了,初步确定问题是出现在第三点,导致这个问题也会有两个原因,要么使用上有问题,要么就是源码有问题,偏向于第一种原因。

解决问题

我们来寻找一下这个Imageloader在显示图片的时候是怎么判断是否是当前组件的,通过查看displayImage方法,寻得ImageAware对象,这个接口有两个实现类:NonViewAware和ViewAware,前者不持有View对象,而且我的工程里使用的displayImage不持有NonViewAware,所以先不讨论这个对象。后者用Reference软引用一个View,这个软引用的View在ImageLoaderEngine里面用Map对象缓存了多个view的软引用,那么到底怎么判断当前View是否为我需要的View呢,我们查看源码发现View的Map里面是通过接口ImageAware的getId()方法作为key,所以我们看看getId的方法:

    @Override
    public int getId() {
        View view = viewRef.get();
        return view == null ? super.hashCode() : view.hashCode();
    }

可以看到这里明显使用了view的hashCode作为view的唯一标识,我们也可以看到这个方法的注释:

    /**
     * Returns ID of image aware view. Point of ID is similar to Object's hashCode. This ID should be unique for every
     * image view instance and should be the same for same instances. This ID identifies processing task in ImageLoader
     * so ImageLoader won't process two image aware views with the same ID in one time. When ImageLoader get new task
     * it cancels old task with this ID (if any) and starts new task.
     * <p/>
     * It's reasonable to return hash code of wrapped view (if any) to prevent displaying non-actual images in view
     * because of view re-using.
     */
    int getId();

明确提出了“This ID should be unique for every image view instance and should be the same for same instances.”,说这个id必须是唯一的,就这一点我觉得我找到了问题,这个hashCode在我的RecyclerView里面指定不是唯一的,复用item的view导致了view的hashCode也会被复用,我怀疑可能是我不应该这样用,因为NonViewAware类的getId实现是正确的:

    @Override
    public String getId() {
        return TextUtils.isEmpty(imageUri) ? super.hashCode() : imageUri.hashCode();
    }

我怀疑这个方法才是我应该使用的,但是我又发现它并不持有View对象,那么我并不能使用这个类,那么我迸发出了改变这个局面的想法,首先基于NonViewAware的启发,我可以直接自己更新UI:

    public void displayImage(final String uri, final ImageView imageView, final DisplayImageOptions options) {
        imageView.setTag(uri);
        displayImage(uri, imageView, options, new ImageLoadingListener() {
            @Override
            public void onLoadingStarted(String imageUri, View view) {
                if (uri.equals(imageView.getTag())) {
                    imageView.setImageResource(options.getImageResOnLoading());
                }
            }

            @Override
            public void onLoadingFailed(String imageUri, View view, FailReason failReason) {
                if (uri.equals(imageView.getTag())) {
                    imageView.setImageResource(options.getImageResOnFail());
                }
            }

            @Override
            public void onLoadingComplete(String imageUri, View view, Bitmap loadedImage) {
                if (uri.equals(imageView.getTag())) {
                    imageView.setImageBitmap(loadedImage);
                }
            }

            @Override
            public void onLoadingCancelled(String imageUri, View view) {
                if (uri.equals(imageView.getTag())) {
                    imageView.setImageResource(options.getImageResOnFail());
                }
            }
        });
    }

这样很容易解决这个问题,可是我的界面可不止简单的显示view,还又设置.displayer(new RoundedBitmapDisplayer(360)),所以我不接受这个修改方法,因为这样做还得自己实现圆角图片,我们都知道view要显示的url都是唯一的,当时可能多个view显示同一个url还会导致一些问题,在存软引用key的时候,那么我们就把id改为String,这样组合hashCode和url,一并作为key,这样应该能够应对我遇到的问题,也能够解决我目前想得到的异常情况,那么我们帖一下修改代码:
ImageAware:

public interface ImageAware {
    /**
     * Returns width of image aware view. This value is used to define scale size for original image.
     * Can return 0 if width is undefined.<br />
     * Is called on UI thread if ImageLoader was called on UI thread. Otherwise - on background thread.
     */
    int getWidth();

    /**
     * Returns height of image aware view. This value is used to define scale size for original image.
     * Can return 0 if height is undefined.<br />
     * Is called on UI thread if ImageLoader was called on UI thread. Otherwise - on background thread.
     */
    int getHeight();

    /**
     * Returns {@linkplain import com.fly.arm.utils.imageloader.core.assist.ViewScaleType scale type} which is used for
     * scaling image for this image aware view. Must <b>NOT</b> return <b>null</b>.
     */
    ViewScaleType getScaleType();

    /**
     * Returns wrapped Android {@link android.view.View View}. Can return <b>null</b> if no view is wrapped or view was
     * collected by GC.<br />
     * Is called on UI thread if ImageLoader was called on UI thread. Otherwise - on background thread.
     */
    View getWrappedView();

    /**
     * Returns a flag whether image aware view is collected by GC or whatsoever. If so then ImageLoader stop processing
     * of task for this image aware view and fires
     * {@link import com.fly.arm.utils.imageloader.core.listener.ImageLoadingListener#onLoadingCancelled(String,
     * android.view.View) ImageLoadingListener#onLoadingCancelled(String, View)} callback.<br />
     * Mey be called on UI thread if ImageLoader was called on UI thread. Otherwise - on background thread.
     *
     * @return <b>true</b> - if view is collected by GC and ImageLoader should stop processing this image aware view;
     * <b>false</b> - otherwise
     */
    boolean isCollected();

    /**
     * Returns ID of image aware view. Point of ID is similar to Object's hashCode. This ID should be unique for every
     * image view instance and should be the same for same instances. This ID identifies processing task in ImageLoader
     * so ImageLoader won't process two image aware views with the same ID in one time. When ImageLoader get new task
     * it cancels old task with this ID (if any) and starts new task.
     * <p/>
     * It's reasonable to return hash code of wrapped view (if any) to prevent displaying non-actual images in view
     * because of view re-using.
     */
    String getId();

    /**
     * Sets image drawable into this image aware view.<br />
     * Displays drawable in this image aware view
     * {@linkplain import com.fly.arm.utils.imageloader.core.DisplayImageOptions.Builder#showImageForEmptyUri(
     *android.graphics.drawable.Drawable) for empty Uri},
     * {@linkplain import com.fly.arm.utils.imageloader.core.DisplayImageOptions.Builder#showImageOnLoading(
     *android.graphics.drawable.Drawable) on loading} or
     * {@linkplain import com.fly.arm.utils.imageloader.core.DisplayImageOptions.Builder#showImageOnFail(
     *android.graphics.drawable.Drawable) on loading fail}. These drawables can be specified in
     * {@linkplain import com.fly.arm.utils.imageloader.core.DisplayImageOptions display options}.<br />
     * Also can be called in {@link import com.fly.arm.utils.imageloader.core.display.BitmapDisplayer BitmapDisplayer}.< br />
     * Is called on UI thread if ImageLoader was called on UI thread. Otherwise - on background thread.
     *
     * @return <b>true</b> if drawable was set successfully; <b>false</b> - otherwise
     */
    boolean setImageDrawable(Drawable drawable);

    /**
     * Sets image bitmap into this image aware view.<br />
     * Displays loaded and decoded image {@link android.graphics.Bitmap} in this image view aware.
     * Actually it's used only in
     * {@link import com.fly.arm.utils.imageloader.core.display.BitmapDisplayer BitmapDisplayer}.< br />
     * Is called on UI thread if ImageLoader was called on UI thread. Otherwise - on background thread.
     *
     * @return <b>true</b> if bitmap was set successfully; <b>false</b> - otherwise
     */
    boolean setImageBitmap(Bitmap bitmap);
}

既然更改了这个紧接着必须更改其实现该接口的类(只贴修改的部分):
ViewAware

    @Override
    public String getId() {
        View view = viewRef.get();
        return view == null ? String.valueOf(super.hashCode()) : (view.hashCode() + String.valueOf(view.getTag()));
    }

因为改了key,导致map也应该做相对的改动,这个类在ImageLoaderEngine类,也只贴修改部分吧:

class ImageLoaderEngine {

    ...省略部分···

    private final Map<String, String> cacheKeysForImageAwares = Collections
            .synchronizedMap(new HashMap<String, String>());

    ...省略部分···

    /**
     * Returns URI of image which is loading at this moment into passed {@link import com.fly.arm.utils.imageloader.core.imageaware.ImageAware}
     */
    String getLoadingUriForView(ImageAware imageAware) {
        return cacheKeysForImageAwares.get(String.valueOf(imageAware.getId()));
    }

    /**
     * Associates <b>memoryCacheKey</b> with <b>imageAware</b>. Then it helps to define image URI is loaded into View at
     * exact moment.
     */
    void prepareDisplayTaskFor(ImageAware imageAware, String memoryCacheKey) {
        cacheKeysForImageAwares.put(String.valueOf(imageAware.getId()), memoryCacheKey);
    }

    /**
     * Cancels the task of loading and displaying image for incoming <b>imageAware</b>.
     *
     * @param imageAware {@link import com.fly.arm.utils.imageloader.core.imageaware.ImageAware} for which display task
     *                   will be cancelled
     */
    void cancelDisplayTaskFor(ImageAware imageAware) {
        cacheKeysForImageAwares.remove(String.valueOf(imageAware.getId()));
    }

    ...省略部分···
}

因为上述代码中出现了setTag,所以在对外ImageLoader的displayImage方法,那么我要设置Tag:

public void displayImage(String uri, ImageAware imageAware, DisplayImageOptions options,
                             ImageSize targetSize,
                             ImageLoadingListener listener,
                             ImageLoadingProgressListener progressListener) {
        imageAware.getWrappedView().setTag(uri);
        ...代码省略...
        }

需要注意的是,在不需要异步图片的item里面,请一定要设置默认TAG:

        // 默认设置
        holder.setVisible(R.id.iv_unread,false);
        holder.getView(R.id.iv_message_type).setTag("");
        holder.getView(R.id.iv_message_more).setTag("");

这样修改之后再测试之前错位的情况,一直没有复现,因为也没落下hashCode,我觉得应该不会有所影响。

后记

修改这个问题的时候我总觉得ImageLoader这个大众的框架不应该出现问题,以至于我都就前面两点去解决问题,可是到头来都没能解决问题,但是更改Imageloader之后我目前也没复现这个错位的问题。可能这样改会有问题,如果您正好看到了,希望指正,邮箱redzkh@gmail.com。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值