处理方法

我们知道了产生问题出在复用convertview时候。convertview中,我们的Imagview控件的数量是有限的,但是获取到的url却是很多的。

我们需要明确当前的ImageView中用到的是哪张图片。图片的获取是异步的,这个时间差让我们不容易根据图片本身来判断其应该放在哪里。

我觉得对于这种异步线程问题的处理,我们必须找到一些标志,就像网上很多资料说的加tag,但是具体tag怎么加,加到哪里呢?

首先明确我们的imageview数量是固定的,当imageview显示的时候,有一张图片和它对应,而一个图片又对应一个url地址。也就是说,imageview在显示的时候,其实可以对应一个图片url地址。当其移出屏幕时,由于我们对convertview的复用,这个时候imageview必须对应另外一个url,但是此时我们的异步任务还在为这个imageview加载上一个url中的图片。这样就可能出现乱序的或者闪烁的问题。

可见,我们必须明确imageview中,当前应该对应哪一个url。这个tag也就是应该设置给imagview,而tag的内容就是url,这样就建立起了imagview和url的关系,当我们发现下载图片使用的urlimageview中存储的url不是一个值的时候,说明convertview已经复用,改变了imageview中的tag。此时图片和iamgeview已经不对应了,如果显示这张图片,就会造成错位。

自建异步图片加载类

/**
 * 图片异步加载类
 * @author vonchenchen
 *
 */
public class AsyncImageLoader {

    private static final boolean DEBUG = true; 

    private static final String TAG = "AsyncImageLoader";
    /** 创建一个线程池 */
    private static ExecutorService fixedThreadPool = Executors.newFixedThreadPool(5); 

    final Handler handler = new Handler();
    private LruCache<String, Bitmap> imageCache;

    public AsyncImageLoader() {
        //获取当前程序可用内存大小
        int maxMemory = (int)(Runtime.getRuntime().maxMemory()/1024);
        // 计算缓存大小 
        int cacheSize = maxMemory/8;
        imageCache = new LruCache<String, Bitmap>(cacheSize){
            @Override
            protected int sizeOf(String key, Bitmap bitmap) {

                return bitmap.getByteCount()/1024;
            }
        };
    }

    /**
     * 获取图片成功后回调接口
     */
    public interface ImageViewCallback {
        public void onImageLoad(ImageView iv, Bitmap Bitmap, String url);

        public void onError(String t);
    }

    /**
     * 开启线程,加载图片
     * 
     * 图片控件需要与对应的url绑定,防止在listview中使用时由于convertview复用而出错
     * @param iv           图片控件
     * @param imageUrl     图片url
     * @param imageViewCallback
     * @return
     */
    public Drawable loadDrawable(final ImageView iv, final String imageUrl,
            final ImageViewCallback imageViewCallback) {

        //将当前url和当前图片控件绑定
        iv.setTag(imageUrl);

        fixedThreadPool.execute(new Runnable() {

            @Override
            public void run() {
                loadImg(iv, imageUrl, imageViewCallback);
            }
        });

        return null;
    }


    /**
     * 根据url加载图片
     * @param url
     * @return
     * @throws IOException
     */
    public static Bitmap loadImageFromUrl(String url) throws IOException {
        URL m;
        InputStream i = null;
        m = new URL(url);
        i = (InputStream) m.getContent();

        Drawable d = Drawable.createFromStream(i, "src");
        BitmapDrawable bd = (BitmapDrawable) d;
        Bitmap bm = bd.getBitmap();
        return bm;
    }

    /**
     * 加载图片   先从lru缓存中寻找图片  如果lru中的图片被回收,则请求网络
     * @param iv
     * @param imageUrl
     * @param imageViewCallback
     */
    private void loadImg(final ImageView iv, final String imageUrl,
            final ImageViewCallback imageViewCallback) {

        if (imageCache.get(imageUrl) != null) {

            Log.i(TAG, "**************catch*****************");

            Bitmap bitmap = imageCache.get(imageUrl);
            final Bitmap bitmapTmp = bitmap;
            if (bitmapTmp != null) {
                //放到主线程执行ui操作
                handler.post(new Runnable() {
                    @Override
                    public void run() {
                        //imageViewCallback.onImageLoad(iv, bitmapTmp, imageUrl);
                        setImageView(iv, bitmapTmp, imageUrl);
                    }
                });
                return;
            }
        }else{

            // 尝试从URL中加载
            try {
                Log.i(TAG, "**************url*****************"+imageUrl);
                final Bitmap bitmap = loadImageFromUrl(imageUrl);
                //将图片存入lru缓存中
                if (bitmap != null) {
                    imageCache.put(imageUrl, bitmap);
                }
                handler.post(new Runnable() {
                    @Override
                    public void run() {

                        setImageView(iv, bitmap, imageUrl);
                    }
                });
            } catch (IOException e) {

                e.printStackTrace();
            }
        }
    }

    private void setImageView(ImageView iv, Bitmap bitmap, String url){
        //确保当前图片加载控件是显示控件  (而不是复用了原先的)

        //当前图片控件对应的是这个图片控件中应该使用的url才加载    
        //如果convertview被复用,iv中的tag会变为其他值,和原先url不匹配,则不加载这个图片
        if(iv.getTag().equals(url)){
            iv.setBackgroundColor(0x00000000);
            iv.setImageBitmap(bitmap);
        }
    }
}

具体思路是
1.建立一块lru缓存
2.建立一个线程池
3.调用loadDrawable 传入控件和图片url,绑定imageview控件和url的关系,开启线程。先寻找lrucache中是否有图片内容,url中没有则从网络获取,使用drawable的createFromStream方法从网络获取图片。
4.判断下载图片使用的url与imagview中tag中保存的是否为一个值,如果是,说明当前imagview加载的是其需要的图片,可以加载,否则加载的是乱序的图片,跳过不去加载。

调用方法

我们以在baseAdapter中的getView方法中为例。

public View getView(int position, View convertView, ViewGroup parent) {

        ListItem item = getItem(position);
        ViewHolder viewHolder = null;
        if(convertView == null){
            convertView = LayoutInflater.from(getContext()).inflate(myResourceId,
                    null);
            viewHolder = new ViewHolder();

            viewHolder.appLogo = (ImageView) convertView.findViewById(R.id.iv);
            convertView.setTag(viewHolder);
        }else{
            viewHolder = (ViewHolder) convertView.getTag();
        }

            asyncImageLoader.loadDrawable(viewHolder.appLogo,item.getImgUrl(), null); 

    return convertView;
}
class ViewHolder{
    ImageView appLogo;
}