一、使用内存缓存,通过使用LruCache类,需要确定缓存的大小,如使用app运行内存的1/8:
private LruCache<String, Bitmap> mMemoryCache;
@Override
protected void onCreate(Bundle savedInstanceState) {
...
// Get max available VM memory, exceeding this amount will throw an
// OutOfMemory exception. Stored in kilobytes as LruCache takes an
// int in its constructor.
final int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);
// Use 1/8th of the available memory for this memory cache.
final int cacheSize = maxMemory / 8;
mMemoryCache = new LruCache<String, Bitmap>(cacheSize) {
@Override
protected int sizeOf(String key, Bitmap bitmap) {
// The cache size will be measured in kilobytes rather than
// number of items.
return bitmap.getByteCount() / 1024;
}
};
...
}
Bitmap存入缓存(调用put()方法):
public void addBitmapToMemoryCache(String key, Bitmap bitmap) {
if (getBitmapFromMemCache(key) == null) {
mMemoryCache.put(key, bitmap);
}
}
从缓存中读出Bitmap(调用get()方法):
public Bitmap getBitmapFromMemCache(String key) {
return mMemoryCache.get(key);
}
注:在常见的hpdi设备上,程序的运行内存的1/8通常为4MB,一幅全屏800*480分辨率的图片大约要1.5MB空间,
所以该缓存可以存储大约2.5张图片。在加载图片时,先检测LruCache中是否有该图片,如果没有则开启子线程去加载:
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);
}
}
二、使用磁盘缓存
使用内存缓存有不小的局限性,如当你的进程因为系统运行内存不够被杀死时,缓存的文件就被破坏了,
这样下次又需要重新加载,可以通过磁盘进行缓存。以下实例使用了开源项目DiskLruCache:
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; } ... }
private void addBitmapToDiskCache(String key, Bitmap bitmap) { synchronized (mDiskCacheLock) { if (mDiskCache != null) { DiskLruCache.Editor editor = null; try { editor = mDiskCache.edit(key); if (editor != null) { OutputStream stream = editor.newOutputStream(0); ByteArrayOutputStream baos = new ByteArrayOutputStream(); bitmap.compress(Bitmap.CompressFormat.PNG, 100, stream); byte[] byteArray = baos.toByteArray(); stream.write(byteArray); editor.commit(); } } catch (IOException e) { e.printStackTrace(); } } } }private Bitmap getBitmapFromDiskCache(String key) { synchronized (mDiskCacheLock) { //如果磁盘缓存初始化未结束,则等待结束 while (mDiskCacheStarting) { try { mDiskCacheLock.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } if (mDiskCache != null) { try { DiskLruCache.Snapshot snapshot = mDiskCache.get(key); if (snapshot != null) { InputStream inputStream = snapshot.getInputStream(0); return BitmapFactory.decodeStream(inputStream); } } catch (IOException e) { e.printStackTrace(); } } } 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);}注:由于初始化缓存是在子线程中进行,这样有可能用户读取缓存文件时初始化还没完成,因此给初始化过程加锁;
磁盘操作不应该在主线程中进行。
三、处理程序运行时配置改变:
当用户转动屏幕时,界面会被重新创建,因此缓存对象被销毁,此时需要重新处理缓存文件。因此为了给用户一个流畅的使用体验,
可以给该界面保留一个Fragment,通过该Fragment把缓存对象传递到新的界面实例,这样你的界面就可以重新访问内存中的缓存:
注:记得在Fragment调用setRetainInstance()方法,如上述的onCreate()方法。private LruCache<String, Bitmap> mMemoryCache; @Override protected void onCreate(Bundle savedInstanceState) { ... RetainFragment retainFragment = RetainFragment.findOrCreateRetainFragment(getFragmentManager()); mMemoryCache = retainFragment.mRetainedCache; if (mMemoryCache == null) { mMemoryCache = new LruCache<String, Bitmap>(cacheSize) { ... // Initialize cache here as usual } retainFragment.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(); fm.beginTransaction().add(fragment, TAG).commit(); } return fragment; } @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setRetainInstance(true); } }