Android 缓存Bitmaps

return mMemoryCache.get(key);

}

注意:在这个例子中,1/8的应用内存被分配给缓存。在一个普通的/hdpi设备上最低也在4M左右(32/8)。一个分辨率为800*480的设备上,全屏的填满图片的GridView占用的内存约1.5M(800*480*4字节),因此这个大小的内存可以缓存2.5页左右的图片。

当加载一个Bitmap到ImageView中,先要检查LruCache。如果有相应的数据,则立即用来更新ImageView,否则将启动后台线程来处理这个图片。

public void loadBitmap(int resId, ImageView imageView) {

final String imageKey = String.valueOf(resId);

final Bitmap bitmap = getBitmapFromMemCache(imageKey);

if (bitmap != null) {

mImageView.setImageBitmap(bitmap);

} else {

mImageView.setImageResource(R.drawable.image_placeholder);

BitmapWorkerTask task = new BitmapWorkerTask(mImageView);

task.execute(resId);

}

}

BitmapWorkerTask也需要更新内存中的数据:

class BitmapWorkerTask extends AsyncTask<Integer, Void, Bitmap> {

// Decode image in background.

@Override

protected Bitmap doInBackground(Integer… params) {

final Bitmap bitmap = decodeSampledBitmapFromResource(

getResources(), params[0], 100, 100));

addBitmapToMemoryCache(String.valueOf(params[0]), bitmap);

return bitmap;

}

}

使用硬盘缓存

一个内存缓存对加速访问最近浏览过的Bitmap非常有帮助,但是你不能局限于内存中的可用图片。GridView这样有着更大的数据集的组件可以很轻易消耗掉内存缓存。你的应用有可能在执行其他任务(如打电话)的时候被打断,并且在后台的任务有可能被杀死或者缓存被释放。一旦用户重新聚焦(resume)到你的应用,你得再次处理每一张图片。

在这种情况下,硬盘缓存可以用来存储Bitmap并在图片被内存缓存释放后减小图片加载的时间(次数)。当然,从硬盘加载图片比内存要慢,并且应该在后台线程进行,因为硬盘读取的时间是不可预知的。

注意:如果访问图片的次数非常频繁,那么ContentProvider可能更适合用来存储缓存图片,例如Image Gallery这样的应用程序。

这个类中的示例代码使用DiskLruCache(来自Android源码)实现。在示例代码中,除了已有的内存缓存,还添加了硬盘缓存。

private DiskLruCache mDiskLruCache;

private final Object mDiskCacheLock = new Object();

private boolean mDiskCacheStarting = true;

private static final int DISK_CACHE_SIZE = 1024 * 1024 * 10; // 10MB

private static final String DISK_CACHE_SUBDIR = “thumbnails”;

@Override

protected void onCreate(Bundle savedInstanceState) {

// Initialize memory cache

// Initialize disk cache on background thread

File cacheDir = getDiskCacheDir(this, DISK_CACHE_SUBDIR);

new InitDiskCacheTask().execute(cacheDir);

}

class InitDiskCacheTask extends AsyncTask<File, Void, Void> {

@Override

protected Void doInBackground(File… params) {

synchronized (mDiskCacheLock) {

File cacheDir = params[0];

mDiskLruCache = DiskLruCache.open(cacheDir, DISK_CACHE_SIZE);

mDiskCacheStarting = false; // Finished initialization

mDiskCacheLock.notifyAll(); // Wake any waiting threads

}

return null;

}

}

class BitmapWorkerTask extends AsyncTask<Integer, Void, Bitmap> {

// Decode image in background.

@Override

protected Bitmap doInBackground(Integer… params) {

final String imageKey = String.valueOf(params[0]);

// Check disk cache in background thread

Bitmap bitmap = getBitmapFromDiskCache(imageKey);

if (bitmap == null) { // Not found in disk cache

// Process as normal

final Bitmap bitmap = decodeSampledBitmapFromResource(

getResources(), params[0], 100, 100));

}

// Add final bitmap to caches

addBitmapToCache(imageKey, bitmap);

return bitmap;

}

}

public void addBitmapToCache(String key, Bitmap bitmap) {

// Add to memory cache as before

if (getBitmapFromMemCache(key) == null) {

mMemoryCache.put(key, bitmap);

}

// Also add to disk cache

synchronized (mDiskCacheLock) {

if (mDiskLruCache != null && mDiskLruCache.get(key) == null) {

mDiskLruCache.put(key, bitmap);

}

}

}

public Bitmap getBitmapFromDiskCache(String key) {

synchronized (mDiskCacheLock) {

// Wait while disk cache is started from background thread

while (mDiskCacheStarting) {

try {

mDiskCacheLock.wait();

} catch (InterruptedException e) {}

}

if (mDiskLruCache != null) {

return mDiskLruCache.get(key);

}

}

return null;

}

// Creates a unique subdirectory of the designated app cache directory. Tries to use external

// but if not mounted, falls back on internal storage.

public static File getDiskCacheDir(Context context, String uniqueName) {

// Check if media is mounted or storage is built-in, if so, try and use external cache dir

// otherwise use internal cache dir

final String cachePath =

Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState()) ||

!isExternalStorageRemovable() ? getExternalCacheDir(context).getPath() :

context.getCacheDir().getPath();

return new File(cachePath + File.separator + uniqueName);

}

注意:即便是硬盘缓存初始化也需要硬盘操作,因此不应该在主线程执行。但是,这意味着硬盘缓存在初始化前就能被访问到。为了解决这个问题,在上面的实现中添加了一个锁对象(lock object),以确保在缓存被初始化之前应用无法访问硬盘缓存。

在UI线程中检查内存缓存,相应的硬盘缓存检查应在后台线程中进行。硬盘操作永远不要在UI线程中发生。当图片处理完成后,最终的Bitmap要被添加到内存缓存和硬盘缓存中,以便后续的使用。

处理配置更改

运行时的配置会发生变化,例如屏幕方向的改变,会导致Android销毁并以新的配置重新启动Activity(关于此问题的更多信息,请参阅Handling Runtime Changes)。为了让用户有着流畅而快速的体验,你需要在配置发生改变的时候避免再次处理所有的图片。

幸运的是,你在“使用内存缓存”一节中为Bitmap构造了很好的内存缓存。这些内存可以通过使用Fragment传递到新的Activity(活动)实例,这个Fragment可以调用setRetainInstance(true)方法保留下来。在Activity(活动)被重新创建后,你可以在上面的Fragment中访问到已经存在的缓存对象,使得图片能快加载并重新填充到ImageView对象中。

下面是一个使用Fragment将LruCache对象保留在配置更改中的示例:

private LruCache<String, Bitmap> mMemoryCache;

@Override

protected void onCreate(Bundle savedInstanceState) {

RetainFragment mRetainFragment =

RetainFragment.findOrCreateRetainFragment(getFragmentManager());

mMemoryCache = RetainFragment.mRetainedCache;

if (mMemoryCache == null) {

mMemoryCache = new LruCache<String, Bitmap>(cacheSize) {

… // Initialize cache here as usual

}

mRetainFragment.mRetainedCache = mMemoryCache;

}

}

class RetainFragment extends Fragment {

private static final String TAG = “RetainFragment”;

public LruCache<String, Bitmap> mRetainedCache;

public RetainFragment() {}

public static RetainFragment findOrCreateRetainFragment(FragmentManager fm) {

RetainFragment fragment = (RetainFragment) fm.findFragmentByTag(TAG);

if (fragment == null) {

fragment = new RetainFragment();

}

return fragment;

}

@Override

public void onCreate(Bundle savedInstanceSta外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

资料获取→专栏
te) {

super.onCreate(savedInstanceState);

setRetainInstance(true);

}

}

RetainFragment fragment = (RetainFragment) fm.findFragmentByTag(TAG);

if (fragment == null) {

fragment = new RetainFragment();

}

return fragment;

}

@Override

public void onCreate(Bundle savedInstanceSta[外链图片转存中…(img-Gm69rPvl-1718727960543)]

资料获取→专栏
te) {

super.onCreate(savedInstanceState);

setRetainInstance(true);

}

}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值