处理方法
我们知道了产生问题出在复用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的关系,当我们发现下载图片使用的url和imageview中存储的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;
}