Caching Bitmaps(缓存位图)

来源:互联网 发布:mac 图片尺寸修改 编辑:程序博客网 时间:2024/05/23 05:08

Loading a single bitmap into your user interface (UI) is straightforward, however things get more complicated if you need to load a larger set of images at once.

加载一个单独的位图到你的UI中是容易的,但是更复杂的是你需要一次加载更多设置图像。

In many cases (such as with components like ListViewGridView or ViewPager), the total number of images on-screen combined with images that might soon scroll onto the screen are essentially unlimited.

在许多cases里(例如类似ListView,GridView和ViewPager),屏幕显示的加上将要可能无限在屏幕上滚动的图像总数。

Memory usage is kept down with components like this by recycling the child views as they move off-screen.

像循环子视图的控件当他们移动到焦点外,浪费内存的使用。

The garbage collector also frees up your loaded bitmaps, assuming you don't keep any long lived references.

如果你不保留任何长时间存活的标记,垃圾回收器通常释放你加载的位图。

This is all good and well, but in order to keep a fluid and fast-loading UI you want to avoid continually processing these images each time they come back on-screen.

这是很好的,但是为了保证流畅快速的UI你想要避免重复处理这些图像每当他们又被显示的时候。

A memory and disk cache can often help here, allowing components to quickly reload processed images.

一个内存和磁盘缓存经常帮助这,允许控件快速重新加载处理过的图像。

This lesson walks you through using a memory and disk bitmap cache to improve the responsiveness and fluidity of your UI when loading multiple bitmaps.

这节课教你使用磁盘和内存缓存位图来提高你的UI响应和流畅度当你加载多个位图的时候。

Use a Memory Cache(使用内存缓存)


A memory cache offers fast access to bitmaps at the cost of taking up valuable application memory.

内存缓存以应用程序宝贵的内存为代价提供快速使用位图。

The LruCache class (also available in the Support Library for use back to API Level 4) is particularly well suited to the task of caching bitmaps, keeping recently referenced objects in a strong referenced LinkedHashMap and evicting the least recently used member before the cache exceeds its designated size.

LruCache类(V4兼容包,可兼容到版本4)特别适合缓存位图,保证最近引用的对象在一个强引用的LinkedHashMap和驱除最近最少使用的成员在内存超过指定的大小之前。

Note: In the past, a popular memory cache implementation was a SoftReference or WeakReference bitmap cache, however this is not recommended.

注意:在以前通常内存缓存工具是一个弱引用或软引用位图缓存,然而这是不推荐的。

Starting from Android 2.3 (API Level 9) the garbage collector is more aggressive with collecting soft/weak references which makes them fairly ineffective.

android2.3开始垃圾回收器更加积极的回收弱/软引用,使他们完全不起作用。

In addition, prior to Android 3.0 (API Level 11), the backing data of a bitmap was stored in native memory which is not released in a predictable manner, potentially causing an application to briefly exceed its memory limits and crash.

另外在android 3.0之前,位图的支持数据被储存在本地内存在已知的方法中无法被释放,可能导致应用短暂的超出内存限制并且崩溃。

In order to choose a suitable size for a LruCache, a number of factors should be taken into consideration, for example:

为了选择LruCache合适的大小,一些因数要考虑比如:

  • How memory intensive is the rest of your activity and/or application?
  • 其余的activity和应用是否需要大量的内存。
  • How many images will be on-screen at once? How many need to be available ready to come on-screen?
  • 多少图像将会一次被显示?多少需要随时要显示?
  • What is the screen size and density of the device? An extra high density screen (xhdpi) device like Galaxy Nexus will need a larger cache to hold the same number of images in memory compared to a device like Nexus S (hdpi).
  • 设备的屏幕尺寸和密度是多少?一个超高密度(xhdpi)屏幕的设备像Galaxy Nexus将会需要一个更大的缓存去保持同样数量的图像在内存相比像Nexus S设备(hdpi)。
  • What dimensions and configuration are the bitmaps and therefore how much memory will each take up?
  • 位图的尺寸和结构是什么,为此每一个位图要消耗多少内存?
  • How frequently will the images be accessed? Will some be accessed more frequently than others? If so, perhaps you may want to keep certain items always in memory or even have multiple LruCache objects for different groups of bitmaps.
  • 图像的访问频率是多少?是否将会比其他的访问更频繁?如果是,也许你可能想要保持一些成员一直在内存,以至复杂的LruCache对象给不同的位图组。
  • Can you balance quality against quantity? Sometimes it can be more useful to store a larger number of lower quality bitmaps, potentially loading a higher quality version in another background task.
  • 你能在质量前先平衡数量吗?有时候存储大量低质量的位图是更有用的,在其他的隐藏后台事务加载高质量的版本。

There is no specific size or formula that suits all applications, it's up to you to analyze your usage and come up with a suitable solution.

这没有明确的尺寸和准则适合所有的应用,这事由你去分析你的使用想出一个解决方案。

A cache that is too small causes additional overhead with no benefit, a cache that is too large can once again cause java.lang.OutOfMemory exceptions and leave the rest of your app little memory to work with.

一个过小的缓存导致无用的额外开销,一个过大的缓存能再次导致内存溢出异常而且退出崩溃较小的内存工作应用。

Here’s an example of setting up a LruCache for bitmaps:

这有一个设置LruCache给位图的例子:

private LruCache<String, Bitmap> 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.    // 获取虚拟机最大的可用内存,超出这个数量将导致内存溢出异常.    // 在LruCache构造方法储存int字节    final int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);    // Use 1/8th of the available memory for this memory cache.    // 使用可用内存的8分之一来做为内存缓存    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);}

Note: In this example, one eighth of the application memory is allocated for our cache. On a normal/hdpi device this is a minimum of around 4MB (32/8).

注意:在这个例子中,应用的1/8内存被分配到我们的缓存,在一个hdpi的设备上最小是4MB。

A full screen GridView filled with images on a device with 800x480 resolution would use around 1.5MB (800*480*4 bytes), so this would cache a minimum of around 2.5 pages of images in memory.

整个屏幕用GridView充满图像在一个800X480分辨率设备上将会使用大约1.5MB,所以这个缓存最小大约缓存2.5页图像在内存里。

When loading a bitmap into an ImageView, the LruCache is checked first. If an entry is found, it is used immediately to update the ImageView, otherwise a background thread is spawned to process the image:

当加载一个位图到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);    }}

The BitmapWorkerTask also needs to be updated to add entries to the memory cache:

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;    }    ...}

Use a Disk Cache(使用硬盘缓存)


A memory cache is useful in speeding up access to recently viewed bitmaps, however you cannot rely on images being available in this cache.

内存缓存是有益于快速获取最近的视图位图,但是你不能保证图像存在在这个内存里。

Components like GridView with larger datasets can easily fill up a memory cache.

像GridView这样的大数据的组件容易占满整个内存缓存。

Your application could be interrupted by another task like a phone call, and while in the background it might be killed and the memory cache destroyed.

你的应用会被像来电这样的另一个事务打断,而且在后台它肯呢个被杀死或者内存缓存被破坏。

Once the user resumes, your application has to process each image again.

一旦用户重来,你的应用要再次处理每一个图像。

A disk cache can be used in these cases to persist processed bitmaps and help decrease loading times where images are no longer available in a memory cache. 

一个磁盘缓存被使用在这些cases里去保留处理后的位图而且帮助减少加载时间当图像没有长时间存在内存缓存中。

Of course, fetching images from disk is slower than loading from memory and should be done in a background thread, as disk read times can be unpredictable.

当然,从磁盘获取图像比比内存加载图像要慢而且必须在后台线程中获取,磁盘读取时间是不可预料的。

Note: A ContentProvider might be a more appropriate place to store cached images if they are accessed more frequently, for example in an image gallery application.

注意:一个ContextProvider可能更多可用的控件去储存图像吐过他们访问更频繁,例如画廊应用。

The sample code of this class uses a DiskLruCache implementation that is pulled from the Android source. Here’s updated example code that adds a disk cache in addition to the existing memory cache:

示例代码使用android源码中的DiskLruCache工具,更新示例代码除了已有的内存缓存添加磁盘缓存。

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);}

Note: Even initializing the disk cache requires disk operations and therefore should not take place on the main thread.

注意:当初始化磁盘缓存的时候需要操作磁盘所以不能在主线程里这么做。

However, this does mean there's a chance the cache is accessed before initialization.

然而,这意味着一个机会在缓存被访问在初始化之前。

To address this, in the above implementation, a lock object ensures that the app does not read from the disk cache until the cache has been initialized.

为了解决这个,在上面的方法里,锁定对象确保应用不能读取磁盘缓存直到缓存已经初始化。

While the memory cache is checked in the UI thread, the disk cache is checked in the background thread. Disk operations should never take place on the UI thread.

虽然内存缓存可以在UI线程中被检查,但是磁盘缓存只能在后台线程中被检查,磁盘操作永远不能在UI线程。

When image processing is complete, the final bitmap is added to both the memory and disk cache for future use.

当图像处理完成,唯一的位图被添加到内存缓存和磁盘缓存供将来使用。

Handle Configuration Changes(处理布局变化)


Runtime configuration changes, such as a screen orientation change, cause Android to destroy and restart the running activity with the new configuration (For more information about this behavior, see Handling Runtime Changes).

当布局发生改变时,例如屏幕方向发生改变,因为android破坏并重新运行新布局的activity(这个行为的更多信息看 Handling Runtime Changes)

You want to avoid having to process all your images again so the user has a smooth and fast experience when a configuration change occurs.

你想要避免再次处理你的所有图像,这样当布局出现改变用户有一个流程快速的体验。


Luckily, you have a nice memory cache of bitmaps that you built in the Use a Memory Cache section.

幸运的,你有一个好的位图内存缓存在Use a Memory Cache这节构造。

This cache can be passed through to the new activity instance using a Fragment which is preserved by calling setRetainInstance(true)).

这个缓存透过新的使用Fragment的activity实例被处理调用setRetainInstance(true).

After the activity has been recreated, this retained Fragment is reattached and you gain access to the existing cache object, allowing images to be quickly fetched and re-populated into the ImageView objects.

在activity再次创建后,这个保存的Fragment被复位而且你获得访问持续存在的缓存对象,允许视图被快速取得并且重新填充到ImageView对象。

Here’s an example of retaining a LruCache object across configuration changes using a Fragment:

这是一个LruCache对象通过配置改变使用一个fragment的例子。

private LruCache<String, Bitmap> mMemoryCache;@Overrideprotected 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 savedInstanceState) {        super.onCreate(savedInstanceState);        setRetainInstance(true);    }}

To test this out, try rotating a device both with and without retaining the Fragment.

为了测试这个,试着旋转设备保留或不保留Fragment.

You should notice little to no lag as the images populate the activity almost instantly from memory when you retain the cache.

你应该注意到当你保存内存时图像会几乎没有延迟填满actitity几乎立即从内存中。

Any images not found in the memory cache are hopefully available in the disk cache, if not, they are processed as usual.

任何图像没有在内存缓存中找到的话那么有可能在磁盘缓存中获得,如果没有那么像平常一样处理。














原创粉丝点击