这次做一个图片加载器,里面涉及到线程池,bitmap的高效加载,LruCache,DiskLruCache。接下来我先介绍这四个知识点
优点: (1)重用线程池中的线程,避免因为线程的创建和销毁带来性能上的开销 (2)有效控制线程池的最大并发数,避免大量线程之间因互相抢占系统资源而阻塞 (3)对线程进行简单管理,并提供定时执行和指定间隔循环执行等功能
1.ThreadPoolExecutor介绍 是线程池的真正实现,构造方法
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory)
corePoolSize:核心线程数 maximumPoolSize:最大线程数。超过将阻塞 keepAliveTime:非核心线程超时时长,超过将会被回收 unit:指定keepAliveTime的时间单位。常用TimeUnit.SECONDS(秒),TimeUnit.MINUTES(分钟)等 workQueue:存储线程的队列。通过ThreadPoolExecutor.execute方法提交的Runnable对象会存储在这个线程中 threadFactory:是一个接口,提供创建新线程的功能。只有一个方法:public Thread newThread(Runnable r)
2.ThreadPoolExecutor典型配置
/**
* 线程池,用来管理线程
*/
private static final ThreadFactory sThreadFactory = new ThreadFactory() {
// AtomicInteger,一个提供原子操作的Integer的类。
private final AtomicInteger mCount = new AtomicInteger(1);
@Override
public Thread newThread(Runnable r) {
return new Thread(r, "ImageLoader#" + mCount.getAndIncrement());
}
};
private static final int CPU_COUNT = Runtime.getRuntime().availableProcessors();
private static final int CORE_POOL_SIZE = CPU_COUNT + 1;
private static final int MAX_POOL_SIZE = CPU_COUNT * 2 + 1;
private static final long KEEP_ALIVE = 10L;
public static final Executor THREAD_POOL_EXECUTOR = new ThreadPoolExecutor(
CORE_POOL_SIZE, MAX_POOL_SIZE, KEEP_ALIVE, TimeUnit.SECONDS,
new LinkedBlockingDeque<Runnable>(), sThreadFactory);
通过BitmapFactory.Options来加载所需尺寸的图片,主要是用到了inSampleSize参数,即采样率 流程如下:
public Bitmap decodeSampleBitmapFromResource(Resources resources,
int resId, int reqWidth, int reqHeight) {
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
// 获取options
BitmapFactory.decodeResource(resources, resId, options);
// 结合目标view所需大小计算采样率
options.inSampleSize = calculateInSampleSize(options, reqWidth,
reqHeight);
options.inJustDecodeBounds = false;
return BitmapFactory.decodeResource(resources, resId, options);
}
/**
* 指定输出图片的缩放比例
*
* @param options
* @param reqWidth
* @param reqHeight
* @return
*/
public static int calculateInSampleSize(BitmapFactory.Options options,
int reqWidth, int reqHeight) {
// 获得原始图片的宽高
int imageHeight = options.outHeight;
int imageWidth = options.outWidth;
int inSimpleSize = 1;
if (imageHeight > reqHeight || imageWidth > reqWidth) {
int halfHeight = imageHeight / 2;
int halfWidth = imageWidth / 2;
while ((halfHeight / inSimpleSize) >= reqHeight
&& (halfWidth / inSimpleSize) >= reqWidth) {
inSimpleSize *= 2;
Log.e("SimpleSize", inSimpleSize + "");
}
}
Log.e("inSimpleSize", inSimpleSize + "");
return inSimpleSize;
}
Lru就是Least Recently Used近期最少使用算法。核心思想:当缓存满时,优先淘汰近期最少使用的缓存对象 先看源码:(推荐使用supprt-v4包中的LruCache,地址在E:\adt\sdk\sources\android-19\android\support\v4\util)
public class LruCache<K, V> {
//map用来存储外界的缓存对象
private final LinkedHashMap<K, V> map;
/**
* @param maxSize for caches that do not override {@link #sizeOf}, this is
* the maximum number of entries in the cache. For all other caches,
* this is the maximum sum of the sizes of the entries in this cache.
*/
public LruCache(int maxSize) {
if (maxSize <= 0) {
throw new IllegalArgumentException("maxSize <= 0");
}
this.maxSize = maxSize;
this.map = new LinkedHashMap<K, V>(0, 0.75f, true);
}
//获取一个缓存对象
public final V get(K key) {
if (key == null) {
throw new NullPointerException("key == null");
}
V mapValue;
synchronized (this) {
mapValue = map.get(key);
if (mapValue != null) {
hitCount++;
return mapValue;
}
missCount++;
}
/*
* Attempt to create a value. This may take a long time, and the map
* may be different when create() returns. If a conflicting value was
* added to the map while create() was working, we leave that value in
* the map and release the created value.
*/
V createdValue = create(key);
if (createdValue == null) {
return null;
}
synchronized (this) {
createCount++;
mapValue = map.put(key, createdValue);
if (mapValue != null) {
// There was a conflict so undo that last put
map.put(key, mapValue);
} else {
size += safeSizeOf(key, createdValue);
}
}
if (mapValue != null) {
entryRemoved(false, key, createdValue, mapValue);
return mapValue;
} else {
trimToSize(maxSize);
return createdValue;
}
}
//添加一个缓存对象
public final V put(K key, V value) {
if (key == null || value == null) {
throw new NullPointerException("key == null || value == null");
}
V previous;
synchronized (this) {
putCount++;
size += safeSizeOf(key, value);
previous = map.put(key, value);
if (previous != null) {
size -= safeSizeOf(key, previous);
}
}
if (previous != null) {
entryRemoved(false, key, previous, value);
}
trimToSize(maxSize);
return previous;
}
/**
* Remove the eldest entries until the total of remaining entries is at or
* below the requested size.
*
* @param maxSize the maximum size of the cache before returning. May be -1
* to evict even 0-sized elements.
*/
public void trimToSize(int maxSize) {
while (true) {
K key;
V value;
synchronized (this) {
if (size < 0 || (map.isEmpty() && size != 0)) {
throw new IllegalStateException(getClass().getName()
+ ".sizeOf() is reporting inconsistent results!");
}
if (size <= maxSize || map.isEmpty()) {
break;
}
Map.Entry<K, V> toEvict = map.entrySet().iterator().next();
key = toEvict.getKey();
value = toEvict.getValue();
map.remove(key);
size -= safeSizeOf(key, value);
evictionCount++;
}
entryRemoved(true, key, value, null);
}
}
//移除一个缓存对象,并且减少该对象对应的size
public final V remove(K key) {
if (key == null) {
throw new NullPointerException("key == null");
}
V previous;
synchronized (this) {
previous = map.remove(key);
if (previous != null) {
size -= safeSizeOf(key, previous);
}
}
if (previous != null) {
entryRemoved(false, key, previous, null);
}
return previous;
}
//生成一个null对象
protected V create(K key) {
return null;
}
//返回一个缓存对象的副本
public synchronized final Map<K, V> snapshot() {
return new LinkedHashMap<K, V>(map);
}
}
研究完了源码,使用起来就方便了
初始化:
final int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);
int cacheSize = maxMemory / 8;
mMemoryCache = new LruCache<String, Bitmap>(cacheSize) {
@Override
protected int sizeOf(String key, Bitmap value) {
return value.getByteCount() / 1024;
}
};
获取缓存:mMemoryCache.get(key)
添加缓存:mMemoryCache.put(key, bitmap)
书上说地址在:https://android.googlesource.com/platform/libcore/+/android-4.1.1_r1/luni/src/main/java/libcore/io/DiskLruCache.java 但是下载下来好像要改好多东西,所以我就在Universal-ImageLoader里面找了相同的文件
1.DiskLruCache的创建
public static DiskLruCache open(File directory, int appVersion, int valueCount, long maxSize)
directory:存储路径 appVersion:通常为1 valueCount:单个节点对应数据个数,通常为1 maxSize:缓存总大小,比如50MB
// 初始化DiskLruCache
File diskCacheDir = getDiskCacheDir(mContext, "bitmap");
if (!diskCacheDir.exists()) {
diskCacheDir.mkdirs();
}
if (getUsableSpace(diskCacheDir) > DISK_CACHE_SIZE) {
try {
mDiskLruCache = DiskLruCache.open(diskCacheDir, 1, 1,
DISK_CACHE_SIZE);
} catch (IOException e) {
e.printStackTrace();
}
}
getDiskCacheDir:获取磁盘缓存目录 getUsableSpace:获取sd卡的大小和剩余空间 这两个函数的实现方法在代码包里面有,就不细说
2.DiskLruCache缓存的添加 缓存的添加是通过Editor来完成的。Editor表示缓存对象的编辑对象
//将uri转化为key
String key = hashKeyForURI(uri);
try {
DiskLruCache.Editor editor = mDiskLruCache.edit(key);
if (editor != null) {
OutputStream outputStream = editor
.newOutputStream(DISK_CACHE_INDEX);
if (downloadURIToStream(uri, outputStream)) {
editor.commit();
} else {
editor.abort();
}
mDiskLruCache.flush();
}
} catch (IOException e) {
return null;
}
上面的代码就是将图片写入文件系统,接下来就可以从文件系统中获取图片 解释几点
1.为什么要将uri转化为hashKey?如果uri中含有特殊字符会影响uri的使用
2.downloadURIToStream实现了“把图片写入到文件系统”的功能,确切的来说,还要配合editor.commit
3.DiskLruCache缓存的查找
String key = hashKeyForURI(uri);
try {
DiskLruCache.Snapshot snapshot = mDiskLruCache.get(key);
if (snapshot != null) {
FileInputStream fileInputStream = (FileInputStream) snapshot
.getInputStream(DISK_CACHE_INDEX);
FileDescriptor fileDescriptor = fileInputStream.getFD();
bitmap = mImageResizer.decodeSampleBitmapFromFileDescriptor(
fileDescriptor, width, height);
if (bitmap != null) {
addBitmapToMemoryCache(key, bitmap);
}
}
} catch (IOException e) {
e.printStackTrace();
}
注意,这里的Snapshot和LruCache的Snapshot不一样。LruCache的Snapshot表示内存缓存的副本,这里的Snapshot仅仅指保存了三个参数的一个对象
至此,ImageLoader已经大体实现。 代码包里面SquareImageView.java是为了得到一个宽高相同的ImageView。 同时,为了优化列表的卡顿现象,我们采用了“仅当列表静止时才加载图片”的策略
public void onScrollStateChanged(AbsListView view, int scrollState) {
if (scrollState == OnScrollListener.SCROLL_STATE_IDLE) {
mIsGridViewIdle = true;
adapter.notifyDataSetChanged();
} else {
mIsGridViewIdle = false;
}
}
在getView里面添加如下代码
if (mIsGridViewIdle) {
imageLoader.bindBitmap(uri, imageView);
}
运行截图 loadBitmapFromHttp和downloadBitmapFromURI都可以实现网络加载。前者是先放到disk中,然后获取,后者是先获取,然后放到memorycache中 我先把downloadBitmapFromURI注释掉
然后把loadBitmapFromHttp注释掉