原文地址:http://developer.android.com/training/displaying-bitmaps/cache-bitmap.html html
缓存图片java
加载一个图片到界面中是很简单的,可是若是一次性要加载一堆大的图片就变的复杂多了。在许多状况下(好比使用ListView、GridView或是ViewPager组件)界面上能显示的图片随着滚动会无限制的增长。android
缓存的使用须要被控制当组件在不断的重复使用的时候。若是你不长时间引用加载来的图片的话,垃圾搜集器也会清除他。为了流畅快速的加载界面,不但愿组件每次显示的时候都从新加载一次图片。利用缓存和磁盘缓存就可以快速加载图片。这一课就是介绍若是经过缓存和磁盘缓存快速加载多张图片。
缓存
使用内存缓存app
内存缓存经过占用应用固定的内存达到快速读取缓存。LruCache类能很好的解决图片缓存占用问题,保证最近使用的资源强引用在LinkedHashMap中,删除最近最少使用的资源。
ide
过去一种很是流行的缓存实现是经过SoftReference或是WeakReference存储图片缓存,然而这种方法再也不推荐。从2.3开始垃圾收集器会更加积极的搜集soft/weak的应用这将使得他们无效了。在3.0之后,返回的图片数据存储在一个私有的内存中,以一种不可预见的方式释放,可能致使应用暂时超过其内存限制而奔溃。ui
为了给LruCache选中一个适合的大小,有一些因素须要考虑:
this
一、应用剩下多少的内存spa
二、屏幕一次要展现多少图片,须要多少图片准备用于屏幕展现线程
三、设备的屏幕大小和分辨率是多少,对于高分辨率设别(xhdpi)像Galaxy Nexus比hdpi的设备须要更大的缓存来存储图片
四、图片的分辨率和配置和其占用的缓存
五、图片存储是否频繁,是否有其余更频繁的,若是有要确保其内存,甚至可使用多个LruCache对象处理不一样组的图片
六、可否平衡质量和数量,大多数状况下存储更多低质量的图片比存储高质量的图片来的重要。
对于全部应用来讲没有专门的大小或公式。取决于你使用的解决方案。缓存过小会致使而外开销,缓存太大会致使内存溢出。
下面是一个设置图片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); }
在这个例子中,应用1/8的内存分配给缓存,一个普通的hdpi设备的最小缓存在4MB左右(32/8),一个全屏的GridView在800x480的设备中大概占用1.5MB(800x480x4bytes),所以能缓存的最少页面的2.5个页面的缓存。当加载图片在ImageView中时先从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); } }
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; } ... }
使用磁盘缓存
内存的使用对于控件快速加载图片来讲是很是有用的,然而不能期望全部图片都存储在内存中,像GridView这样的组件加载大量的数据可以很轻松的占满缓存。应用会被其余任务所打断好比电话,当其在后台时,有可能被杀死,缓存被销毁。一旦返回应用时,又必须从新加载。
这种状况下使用磁盘缓存加载再也不内存中的图片能减小加载时间。固然从磁盘获取图片是缓慢的不肯定的,须要在后台线程中执行。
ContentProvider多是一个更合适的地方来存储缓存图片若是他们更频繁地访问,例如在一个图片库应用程序。
下面是使用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; } ... } 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); }
即便是初始化磁盘缓存的任务都不该该占用UI线程。但并不意味着读取缓存会在初始化以前,经过锁来控制其余任务须要在初始化完成后才能执行。
内存的获取在UI线程,磁盘的获取在后台线程,磁盘的操做不能占用UI线程,当图片加载完成后,图片会存储在内存和磁盘中供之后使用。