您的位置:首页 > 移动开发 > Android开发

ListView常见问题二

2017-03-24 17:20 99 查看
此篇接ListView常见问题一

Android-Universal-Image-Loader jar包下 listview item 图片重复问题



上片提到由于ListView ViewHolder的存在虽然大大优化了ListView但是在加载图片时会有重复的问题



先来简单介绍一下Android-Universal-Image-Loader jar包 Android-Universal-Image-Loader jar包有三个常用的组件DisplayImageOptions、ImageLoader、ImageLoaderConfiguration



ImageLoader讲解


ImageLoader是具体下载图片,缓存图片,显示图片的具体执行类,它有两个具体的方法displayImage(...)、loadImage(...),但是其实最终他们的实现都是displayImage(...)。

需要在全局application里配置



public static ImageLoader imageLoader = ImageLoader.getInstance();


ImageLoader主要有两个方法
displayImage(...)和loadImage(...)


在ImageLoader的源码中可以看出

loadImage(...)方法有四个重载方法
1.loadImage(String uri, ImageLoadingListener listener)

2.loadImage(String uri, ImageSize minImageSize, ImageLoadingListener listener)

3.loadImage(String uri, DisplayImageOptions options, ImageLoadingListener listener)

4.loadImage(String uri, ImageSize targetImageSize, DisplayImageOptions options, ImageLoadingListener listener)

其中前三个方法中只是调用了第四个方法而第四个方法有调用了displayImage(...)方法

所以loadImage(...)方法其实就是displayImage(...)方法



displayImage(...)方法方法也有四个重载方法


1.displayImage(String
uri, ImageView imageView) 


2.displayImage(String uri, ImageView imageView, DisplayImageOptions options)

3.displayImage(String uri, ImageView imageView, ImageLoadingListener listener)

4.displayImage(String uri, ImageView imageView, DisplayImageOptions options, ImageLoadingListener listener) 

而和loadImage(...)方法一样displayImage(...)方法的前三个方法也是只是调用了第四个方法

所以观看源码时 只需查看ImageLoader的displayImage(String uri, ImageView imageView, DisplayImageOptions
options, ImageLoadingListener listener) 的源码即可




displayImage(...)四个参数方法的源码如下


public void displayImage(String uri, ImageView imageView, DisplayImageOptions options, ImageLoadingListener listener) {
this.checkConfiguration();
if(imageView == null) {
throw new IllegalArgumentException("Wrong arguments were passed to displayImage() method (ImageView reference must not be null)");
} else {
if(listener == null) {
listener = this.emptyListener;
}

if(options == null) {
options = this.configuration.defaultDisplayImageOptions;
}

if(TextUtils.isEmpty(uri)) {
this.engine.cancelDisplayTaskFor(imageView);
listener.onLoadingStarted(uri, imageView);
if(options.shouldShowImageForEmptyUri()) {
imageView.setImageResource(options.getImageForEmptyUri());
} else {
imageView.setImageDrawable((Drawable)null);
}

listener.onLoadingComplete(uri, imageView, (Bitmap)null);
} else {
ImageSize targetSize = ImageSizeUtils.defineTargetSizeForView(imageView, this.configuration.maxImageWidthForMemoryCache, this.configuration.maxImageHeightForMemoryCache);
String memoryCacheKey = MemoryCacheUtil.generateKey(uri, targetSize);
this.engine.prepareDisplayTaskFor(imageView, memoryCacheKey);
listener.onLoadingStarted(uri, imageView);
Bitmap bmp = (Bitmap)this.configuration.memoryCache.get(memoryCacheKey);
ImageLoadingInfo imageLoadingInfo;
if(bmp != null && !bmp.isRecycled()) {
if(this.configuration.loggingEnabled) {
L.i("Load image from memory cache [%s]", new Object[]{memoryCacheKey});
}

if(options.shouldPostProcess()) {
imageLoadingInfo = new ImageLoadingInfo(uri, imageView, targetSize, memoryCacheKey, options, listener, this.engine.getLockForUri(uri));
ProcessAndDisplayImageTask displayTask1 = new ProcessAndDisplayImageTask(this.engine, bmp, imageLoadingInfo, options.getHandler());
this.engine.submit(displayTask1);
} else {
options.getDisplayer().display(bmp, imageView, LoadedFrom.MEMORY_CACHE);
listener.onLoadingComplete(uri, imageView, bmp);
}
} else {
if(options.shouldShowStubImage()) {
imageView.setImageResource(options.getStubImage());
} else if(options.isResetViewBeforeLoading()) {
imageView.setImageDrawable((Drawable)null);
}

imageLoadingInfo = new ImageLoadingInfo(uri, imageView, targetSize, memoryCacheKey, options, listener, this.engine.getLockForUri(uri));
LoadAndDisplayImageTask displayTask = new LoadAndDisplayImageTask(this.engine, imageLoadingInfo, options.getHandler());
this.engine.submit(displayTask);
}

}
}
}





源码分析

1.当传递的imageview为空时抛出“传递参数有误,文件找不到”的异常

if(imageView == null) {
throw new IllegalArgumentException("Wrong arguments were passed to displayImage() method (ImageView reference must not be null)");
} 


2.当传递的ImageLoadingListener接口对象或是SimpleImageLoadingListener(
ImageLoadingListener接口的实现类 常用)为空时 监听为自己类创建的
private final ImageLoadingListener emptyListener = new SimpleImageLoadingListener();


if(listener ==null)
{


    listener = this.emptyListener;
}


3.当传递的DisplayImageOptions 对象为空时 对象为自己类创建

if(options == null) {
options = this.configuration.defaultDisplayImageOptions;
}


4.当图片路径为空时
if(TextUtils.isEmpty(uri)) {
this.engine.cancelDisplayTaskFor(imageView);
listener.onLoadingStarted(uri, imageView);
if(options.shouldShowImageForEmptyUri()) {
imageView.setImageResource(options.getImageForEmptyUri());
} else {
imageView.setImageDrawable((Drawable)null);
}

listener.onLoadingComplete(uri, imageView, (Bitmap)null);
} 
即此时 
如果DisplayImageOptions对象设置了.showImageForEmptyUri(R.drawable.companylogo_default)
及路径为空时显示默认图片 此时就显示设置的图片 
如果没设置图片显示为空
最后调用ImageLoadingListener接口对象或是SimpleImageLoadingListener(ImageLoadingListener接口的实现类 常用)onLoadingComplete方法
5.当路径不为空时
ImageSize targetSize = ImageSizeUtils.defineTargetSizeForView(imageView, this.configuration.maxImageWidthForMemoryCache, this.configuration.maxImageHeightForMemoryCache);
String memoryCacheKey = MemoryCacheUtil.generateKey(uri, targetSize);
this.engine.prepareDisplayTaskFor(imageView, memoryCacheKey);
listener.onLoadingStarted(uri, imageView);
Bitmap bmp = (Bitmap)this.configuration.memoryCache.get(memoryCacheKey);
ImageLoadingInfo imageLoadingInfo;
if(bmp != null && !bmp.isRecycled()) {
if(this.configuration.loggingEnabled) {
L.i("Load image from memory cache [%s]", new Object[]{memoryCacheKey});
}

if(options.shouldPostProcess()) {
imageLoadingInfo = new ImageLoadingInfo(uri, imageView, targetSize, memoryCacheKey, options, listener, this.engine.getLockForUri(uri));
ProcessAndDisplayImageTask displayTask1 = new ProcessAndDisplayImageTask(this.engine, bmp, imageLoadingInfo, options.getHandler());
this.engine.submit(displayTask1);
} else {
options.getDisplayer().display(bmp, imageView, LoadedFrom.MEMORY_CACHE);
listener.onLoadingComplete(uri, imageView, bmp);
}
} else {
if(options.shouldShowStubImage()) {
imageView.setImageResource(options.getStubImage());
} else if(options.isResetViewBeforeLoading()) {
imageView.setImageDrawable((Drawable)null);
}

imageLoadingInfo = new ImageLoadingInfo(uri, imageView, targetSize, memoryCacheKey, options, listener, this.engine.getLockForUri(uri));
LoadAndDisplayImageTask displayTask = new LoadAndDisplayImageTask(this.engine, imageLoadingInfo, options.getHandler());
this.engine.submit(displayTask);
}
即先从缓存中找
ImageSize targetSize = ImageSizeUtils.defineTargetSizeForView(imageView, this.configuration.maxImageWidthForMemoryCache, this.configuration.maxImageHeightForMemoryCache);
String memoryCacheKey = MemoryCacheUtil.generateKey(uri, targetSize);
this.engine.prepareDisplayTaskFor(imageView, memoryCacheKey);
listener.onLoadingStarted(uri, imageView);
Bitmap bmp = (Bitmap)this.configuration.memoryCache.get(memoryCacheKey);
如果缓存中的bitmap不为空且没有被回收
if(options.shouldPostProcess()) {
imageLoadingInfo = new ImageLoadingInfo(uri, imageView, targetSize, memoryCacheKey, options, listener, this.engine.getLockForUri(uri));
ProcessAndDisplayImageTask displayTask1 = new ProcessAndDisplayImageTask(this.engine, bmp, imageLoadingInfo, options.getHandler());
this.engine.submit(displayTask1);
} else {
options.getDisplayer().display(bmp, imageView, LoadedFrom.MEMORY_CACHE);
listener.onLoadingComplete(uri, imageView, bmp);
}
DisplayImageOptions类的display()方法 并调用onLoadingComplete()方法
否则 bitmap为空或是已经被回收
if(options.shouldShowStubImage()) {
imageView.setImageResource(options.getStubImage());
} else if(options.isResetViewBeforeLoading()) {
imageView.setImageDrawable((Drawable)null);
}

imageLoadingInfo = new ImageLoadingInfo(uri, imageView, targetSize, memoryCacheKey, options, listener, this.engine.getLockForUri(uri));
LoadAndDisplayImageTask displayTask = new LoadAndDisplayImageTask(this.engine, imageLoadingInfo, options.getHandler());
this.engine.submit(displayTask);
利用LoadAndDisplayImageTask 下载图片
final class LoadAndDisplayImageTask implements Runnable {
  ...
bmp = (Bitmap)this.configuration.memoryCache.get(this.memoryCacheKey); 下载成功后将图片放入缓存
}
ImageLoaderConfiguration详解
ImageLoaderConfiguration的配置主要是全局性的配置,主要有线程类、缓存大小、磁盘大小、图片下载与解析、日志方面的配置。
获取ImageLoaderConfiguration
1.默认方式
ImageLoaderConfiguration configuration=ImageLoaderConfiguration.createDefault(context);
2.自定义方式
ImageLoaderConfiguration configuration=new ImageLoaderConfiguration.Builder(this)
.memoryCacheExtraOptions(480, 800)//内存缓存文件的最大长宽
.discCacheExtraOptions(480, 800, null)//本地缓存的详细信息
.threadPoolSize(3)//线程池内加载的数量
.threadPriority(Thread.NORM_PRIORITY-2)//设置当前线程的优先级
.tasksProcessingOrder(QueueProcessingType.FIFO)//设置任务的处理顺序
.denyCacheImageMultipleSizesInMemory()//防止内存中图片重复
.memoryCache(new LruMemoryCache(2*1024*1024))//设置自己的内存缓存大小   2M
.memoryCacheSize(2*1024*1024)
.memoryCacheSizePercentage(13)//内存缓存百分比
.discCache(new UnlimitedDiscCache(new File(sdPath+"/huang/image1")))//设置缓存的图片在sdcard中的位置
.discCacheSize(50*1024*1024)//硬盘缓存大小    50M
.discCacheFileCount(100)//硬盘缓存文件个数
.discCacheFileNameGenerator(new Md5FileNameGenerator())//md5加密的方式,或new HashCodeFileNameGenerator()
.imageDownloader(new BaseImageDownloader(this))
.imageDecoder(new BaseImageDecoder(true))//图片解码
.defaultDisplayImageOptions(getOption())//是否使用默认的图片加载配置,null表示不使用
.writeDebugLogs()
.build();


一般使用默认方式即可
ImageLoaderConfiguration configuration=ImageLoaderConfiguration.createDefault(context);


ImageLoaderConfiguration默认内存缓存使用的是LruMemoryCache
缓存的是bitmap的强引用
LruMemoryCache源码
public class LruMemoryCache implements MemoryCacheAware<String, Bitmap> {
private final LinkedHashMap<String, Bitmap> map;
private final int maxSize;
private int size;

public LruMemoryCache(int maxSize) {
if(maxSize <= 0) {
throw new IllegalArgumentException("maxSize <= 0");
} else {
this.maxSize = maxSize;
this.map = new LinkedHashMap(0, 0.75F, true);
}
}

public final Bitmap get(String key) {
if(key == null) {
throw new NullPointerException("key == null");
} else {
synchronized(this) {
return (Bitmap)this.map.get(key);
}
}
}

public final boolean put(String key, Bitmap value) {
if(key != null && value != null) {
synchronized(this) {
this.size += this.sizeOf(key, value);
Bitmap previous = (Bitmap)this.map.put(key, value);
if(previous != null) {
this.size -= this.sizeOf(key, previous);
}
}

this.trimToSize(this.maxSize);
return true;
} else {
throw new NullPointerException("key == null || value == null");
}
}

private void trimToSize(int maxSize) {
while(true) {
synchronized(this) {
if(this.size < 0 || this.map.isEmpty() && this.size != 0) {
throw new IllegalStateException(this.getClass().getName() + ".sizeOf() is reporting inconsistent results!");
}

if(this.size > maxSize && !this.map.isEmpty()) {
Entry toEvict = (Entry)this.map.entrySet().iterator().next();
if(toEvict != null) {
String key = (String)toEvict.getKey();
Bitmap value = (Bitmap)toEvict.getValue();
this.map.remove(key);
this.size -= this.sizeOf(key, value);
continue;
}
}

return;
}
}
}

public final void remove(String key) {
if(key == null) {
throw new NullPointerException("key == null");
} else {
synchronized(this) {
Bitmap previous = (Bitmap)this.map.remove(key);
if(previous != null) {
this.size -= this.sizeOf(key, previous);
}

}
}
}

public Collection<String> keys() {
synchronized(this) {
return new HashSet(this.map.keySet());
}
}

public void clear() {
this.trimToSize(-1);
}

private int sizeOf(String key, Bitmap value) {
return value.getRowBytes() * value.getHeight();
}

public final synchronized String toString() {
return String.format("LruCache[maxSize=%d]", new Object[]{Integer.valueOf(this.maxSize)});
}
}

即使用了
LinkedHashMap
LinkedHashMap(LRU(Least Recently Used)策略,即当内存使用不足时,把最近最少使用的数据从缓存中移除,保留使用最频繁的数据。)

构造方法 new LinkedHashMap(10, 0.75, true);
参数1:缓存的大小一般为10或是5。
参数2:加载因子 经验值为0.75。
参数3:true:按照最近访问量的高低排序,false:按照插入顺序排序。

最后初始化
ImageLoaderConfiguration


Application.imageLoader.init(configuration);

DisplayImageOptions详解

options=new DisplayImageOptions.Builder()
.showImageForEmptyUri(R.drawable.companylogo_default)
.showImageOnFail(R.drawable.companylogo_default)
.cacheInMemory(true)
.cacheOnDisc(true)
.bitmapConfig(Bitmap.Config.RGB_565)
.build();
displayImage源码图片路径为空时用到DisplayImageOptions设置的图片路径为空时的图片。





































内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息