用户在使用ListView或GridView时,控件会自动把用户滑过的已不在当前显示区域的ChildView回收掉,当然也会把该子视图上的bitmap回收掉以释放内存,因此,为了保证一个流畅,快速的操作体验,我们应当避免反复的对同一张图片进行加载,比如说用户在往下看图的过程中又向上滑回去看图,这时对于已经上面已经加载过的图片我们就没有必要让它再加载一遍了,应该能很快的把图片显示出来,这里我们要使用缓存来达到这一目的。
一,使用Memory Cache:
内存缓存速度快,同时为了更加适应实际应用的场景,我们使用LruCache来达到按使用频率缓存的目的,把最近使用的加入缓存,较长时间不用的则会剔除掉释放出空间。
缓存的代码如下:
- 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;
- }
- };
- ...
- }
- public void addBitmapToMemoryCache(String key, Bitmap bitmap) {
- if (getBitmapFromMemCache(key) == null) {
- mMemoryCache.put(key, bitmap);
- }
- }
- public Bitmap getBitmapFromMemCache(String key) {
- return mMemoryCache.get(key);
- }
private LruCache那么我们在loadBitmap的时候就可以先检查下缓存中保存的是否有该图片,有则直接取出使用,不再进行加载。mMemoryCache;@Overrideprotected 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 (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; } }; ...}public void addBitmapToMemoryCache(String key, Bitmap bitmap) { if (getBitmapFromMemCache(key) == null) { mMemoryCache.put(key, bitmap); }}public Bitmap getBitmapFromMemCache(String key) { return mMemoryCache.get(key);}
新的代码如下:
- 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);
- }
- }
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); }}当然,我们也要在加载图片是及时的维护缓存,把刚使用到的图片add进缓存中去。
新的代码如下:
- 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;
- }
- ...
- }
class BitmapWorkerTask extends AsyncTask{ ... // 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; } ...}
在使用内存做缓存的基础上,我们还可以使用Disk控件做为缓存,构成一种二级缓存的结构,设想这种情况,如果App在使用的过程被突然来电打断,那么此时有可能就会引起系统内存的回收,当用户再次切换到App时,App就要进行次很明显的图片再次加载的过程。这个时候,我们就需要用到Disk了,因为足够持久。
下面是是原来的基础上增加使用Disk Cache 的例子:
- 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);
- }