LruCache 的文档描述
A cache that holds strong references to a limited number of values. Each time a value is accessed, it is moved to the head of a queue. When a value is added to a full cache,
the value at the end of that queue is evicted and may become eligible for garbage collection.
1 一个包含有限数量强引用的缓存,一定数量的map。
2 每次访问一个值,它都会被移动到队列的头部,
3 将一个新的值添加到已经满了的缓存队列时,该队列末尾的值将会被逐出,并且可能会被垃圾回收机制进行回收。
LruCache 构造函数
创建了一个 LinkedHashMap,三个参数分别为 初始容量、加载因子和访问顺序,当 accessOrder 为 true 时,这个集合的元素顺序就会是访问顺序,也就是访问了之后就会将这个元素放到集合的最后面。
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);
}
int cacheSize = 1024 * 1024 * memClass;
int mCacheSize = maxMemory / 8;//设置图片内存缓存占用八分之一
mMemoryCache = new LruCache<String, BitmapItem>(cacheSize) {
@Override //必须重写此方法,来测量Bitmap的大小
protected int sizeOf(String key, BitmapItem bitmap) {
return bitmap.mBitmap.getByteCount();
}
};
public void getBitmap(BitmapWorkerOptions options, final BitmapCallback callback) {
cancelDownload(callback);
final boolean hasAccountImageUri = UriUtils.isAccountImageUri(options.getResourceUri());
final Bitmap bitmap = hasAccountImageUri ? null : getBitmapFromMemCache(options);
if (hasAccountImageUri) {
AccountImageChangeObserver.getInstance().registerChangeUriIfPresent(options);
}
BitmapWorkerTask task = new BitmapWorkerTask(null) {
@Override
protected Bitmap doInBackground(BitmapWorkerOptions... params) {
if (bitmap != null) {
return bitmap;
}
final Bitmap bitmap = super.doInBackground(params);
if (bitmap != null && !hasAccountImageUri) {
addBitmapToMemoryCache(params[0], bitmap, isScaled());
}
return bitmap;
}
@Override
protected void onPostExecute(Bitmap bitmap) {
callback.onBitmapRetrieved(bitmap);//获取图片回调。
}
};
callback.mTask = new SoftReference<>(task); 生成下载线程的弱引用
task.executeOnExecutor(BITMAP_DOWNLOADER_THREAD_POOL_EXECUTOR, options); 把线程放到线程池执行。
}
//发起任务下载完bitmap:加入LruCache
private void addBitmapToMemoryCache(BitmapWorkerOptions key, Bitmap bitmap, boolean isScaled) {
if (!key.isMemCacheEnabled()) {
return;
}
String bucketKey = getBucketKey(
key.getCacheKey(), key.getBitmapConfig(), bitmap.getHeight());
BitmapItem bitmapItem = mMemoryCache.get(bucketKey);
if (bitmapItem != null) {
Bitmap currentBitmap = bitmapItem.mBitmap;
// If somebody else happened to get a larger one in the bucket, discard our bitmap.
// TODO: need a better way to prevent current downloading for the same Bitmap
if (currentBitmap.getWidth() >= bitmap.getWidth() && currentBitmap.getHeight()
>= bitmap.getHeight()) {
return;
}
}
if (DEBUG) {
Log.d(TAG, "add cache "+bucketKey+" isScaled = "+isScaled);
}
bitmapItem = new BitmapItem(bitmap, isScaled);
mMemoryCache.put(bucketKey, bitmapItem);加到缓存
}
private Bitmap getBitmapFromMemCache(BitmapWorkerOptions key) {
if (key.getHeight() != BitmapWorkerOptions.MAX_IMAGE_DIMENSION_PX) {
// 1. find the bitmap in the size bucket
String bucketKey =
getBucketKey(key.getCacheKey(), key.getBitmapConfig(), key.getHeight());
BitmapItem bitmapItem = mMemoryCache.get(bucketKey);获取
if (bitmapItem != null) {
Bitmap bitmap = bitmapItem.mBitmap;
// now we have the bitmap in the bucket, use it when the bitmap is not scaled or
// if the size is larger than or equals to the output size
if (!bitmapItem.mScaled) {
return bitmap;
}
if (bitmap.getHeight() >= key.getHeight()) {
return bitmap;
}
}
// 2. find un-scaled bitmap in smaller buckets. If the un-scaled bitmap exists
// in higher buckets, we still need to scale it down. Right now we just
// return null and let the BitmapWorkerTask to do the same job again.
// TODO: use the existing unscaled bitmap and we don't need to load it from resource
// or network again.
for (int i = SIZE_BUCKET.length - 1; i >= 0; i--) {
if (SIZE_BUCKET[i] >= key.getHeight()) {
continue;
}
bucketKey = getBucketKey(key.getCacheKey(), key.getBitmapConfig(), SIZE_BUCKET[i]);
bitmapItem = mMemoryCache.get(bucketKey);
if (bitmapItem != null && !bitmapItem.mScaled) {
return bitmapItem.mBitmap;
}
}
return null;
}
// 3. find un-scaled bitmap if size is not specified
for (int i = SIZE_BUCKET.length - 1; i >= 0; i--) {
String bucketKey =
getBucketKey(key.getCacheKey(), key.getBitmapConfig(), SIZE_BUCKET[i]);
BitmapItem bitmapItem = mMemoryCache.get(bucketKey);
if (bitmapItem != null && !bitmapItem.mScaled) {
return bitmapItem.mBitmap;
}
}
return null;
}
applicationContext.registerComponentCallbacks(new ComponentCallbacks2() {
@Override
public void onTrimMemory(int level) {
mMemoryCache.evictAll(); 内存不足 清理内存缓存。
}
@Override
public void onConfigurationChanged(Configuration newConfig) {
int changes = mConfiguration.updateFrom(newConfig);
if (Configuration.needNewResources(changes, ActivityInfo.CONFIG_LAYOUT_DIRECTION)) {
invalidateCachedResources(); 旋转屏幕时清理指定缓存
}
}
@Override
public void onLowMemory() {}
});
public void invalidateCachedResources() {
Map<String, BitmapItem> snapshot = mMemoryCache.snapshot();
for (String uri: snapshot.keySet()) {
Log.d(TAG, "remove cached image: " + uri);
if (uri.startsWith(ContentResolver.SCHEME_ANDROID_RESOURCE)) {
mMemoryCache.remove(uri);从缓存中清理。
}
}
}
重要的部分:如何调用:
Uri iconUri = getIconResourceUri();
if (iconUri != null) {
iconImageView.setVisibility(View.INVISIBLE);
if (mActivity != null) {
BitmapDownloader bitmapDownloader = BitmapDownloader.getInstance(mActivity);
mBitmapCallBack = new BitmapCallback() {
@Override
public void onBitmapRetrieved(Bitmap bitmap) {//回调
if (bitmap != null) {
mIconBitmap = bitmap;
iconImageView.setVisibility(View.VISIBLE);
iconImageView.setImageBitmap(bitmap);
addShadow(iconImageView, view);
updateViewSize(iconImageView);
}
}
};
bitmapDownloader.getBitmap(new BitmapWorkerOptions.Builder(
mActivity).resource(iconUri)
.width(iconImageView.getLayoutParams().width).build(),
mBitmapCallBack);
}
public static abstract class BitmapCallback {
SoftReference<BitmapWorkerTask> mTask;
public abstract void onBitmapRetrieved(Bitmap bitmap);
}
getBitmap中
callback.mTask = new SoftReference<>(task);生成下载线程的弱引用
task.executeOnExecutor(BITMAP_DOWNLOADER_THREAD_POOL_EXECUTOR, options);
imageView.setTag(R.id.imageDownloadTask, new SoftReference<>(task));
public boolean cancelDownload(Object key) {
BitmapWorkerTask task = null;
if (key instanceof ImageView) {
ImageView imageView = (ImageView)key;
SoftReference<BitmapWorkerTask> softReference =
(SoftReference<BitmapWorkerTask>) imageView.getTag(R.id.imageDownloadTask);
if (softReference != null) {
task = softReference.get();
softReference.clear();
}
} else if (key instanceof BitmapCallback) {
BitmapCallback callback = (BitmapCallback)key;
if (callback.mTask != null) {
task = callback.mTask.get();
callback.mTask = null;
}
}
if (task != null) {
return task.cancel(true);
}
return false;
}