Caching Bitmaps缓存bitmaps(Android官方文档翻译三)

时间:2021-01-16 07:10:28


原文地址: http://developer.android.com/training/displaying-bitmaps/cache-bitmap.html


注:我的英文水平确实有限,希望大家能够指出不足之处:——),建议多阅读官方文档,市场上很多书将思想讲的比较少,一来就是组件和熟悉。。。简直无语

我接下来写的关于Android的文档基本以翻译为主,原因有:1、个人能力有限,不想耽误童鞋们。。

我把我个人觉得相对重要的用红色标注了



Caching Bitmaps

缓存bitmaps

Loading a single bitmap into your userinterface (UI) is straightforward, however things get more complicated if youneed to load a larger set of images at once. In many cases (such as withcomponents like ListViewGridView or ViewPager), the total number of imageson-screen combined with images that might soon scroll onto the screen areessentially unlimited.

Memory usage is kept down withcomponents like this by recycling the child views as they move off-screen. Thegarbage collector also frees up your loaded bitmaps, assuming you don't keepany long lived references. This is all good and well, but in order to keep afluid and fast-loading UI you want to avoid continually processing these imageseach time they come back on-screen. A memory and disk cache can often helphere, allowing components to quickly reload processed images.

This lesson walks you through using amemory and disk bitmap cache to improve the responsiveness and fluidity of yourUI when loading multiple bitmaps.

 

加载一个bitmap到UI是很简单的,但是如果你需要一次加载大量的图片事情就变得复杂了。在很多情况下(一些组件比如ListViewGridView or ViewPager),将要滚动到屏幕上的图片组成的屏幕上的图片,其的总量原则上是不受限制的。

对于一些组件,当child view离开屏幕的时候,他们通过循环使用child View从而降低内存的使用。如果你没有保持任何持久的引用的话,gc就会释放你加载的bitmaps。这样做很好,但是当这些图片又回到屏幕上来的时候,如果你想避免每次都要去处理这些图片从而让UI能够流畅快速的加载图片的话,memory 和 disk 缓存就有用了,他们能够让组件快速的重新加载处理过的图片。

这节课将引导你使用memorydisk缓存来提高当你加载大量图片时UI的响应和流畅度

 

Use aMemory Cache

使用内存缓存

A memory cache offers fast access tobitmaps at the cost of taking up valuable application memory. TheLruCache class (also availablein the Support Library for use back to APILevel 4) is particularly well suited to the task of caching bitmaps, keepingrecently referenced objects in a strong referenced LinkedHashMap and evicting theleast recently used member before the cache exceeds its designated size.

Note: In thepast, a popular memory cache implementation was a SoftReference or WeakReference bitmap cache, howeverthis is not recommended. Starting from Android 2.3 (API Level 9) the garbagecollector is more aggressive with collecting soft/weak references which makesthem fairly ineffective. In addition, prior to Android 3.0 (API Level 11), thebacking data of a bitmap was stored in native memory which is not released in apredictable manner, potentially causing an application to briefly exceed itsmemory limits and crash.

 

在占用了宝贵的应用内存的代价下,memorycache能够提供对图片的快速访问。LruCache class(API Level 4之前的能够在Support Library 中使用)对于缓存图片的任务来讲是很合适的,保持最近引用的对象在一个强引用LinkedHashMap中,在cache超过设计大小之前移除最近最少使用的成员(LRU算法)

注意:在过去,比较流行的内存缓存的实现是通过软引用或者是弱引用bitmapcache来实现的.但是这种方法从Android 2.3(API Level9)开始就不再推荐了。因为gc对收集软/弱引用变得更加积极了,这使得它们变得相对没效果。另外,在Android 3.0(API Level11)之前,bitmap数据是存储在native memory中的,不可预见的方式来释放内存,这就可能会造成应用程序超过了内存的限制从而造成崩溃。

 

In order tochoose a suitable size for a LruCache, a number offactors should be taken into consideration, for example:

·        How memory intensive is the rest of youractivity and/or application?

·        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 thedevice? An extra high density screen (xhdpi) device like Galaxy Nexus will needa larger cache to hold the same number of images in memory compared to a devicelikeNexusS (hdpi).

·        What dimensions and configuration are thebitmaps 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 wantto keep certain items always in memory or even have multiple LruCache objectsfor different groups of bitmaps.

·        Can you balance quality against quantity? Sometimesit can be more useful to store a larger number of lower quality bitmaps,potentially loading a higher quality version in another background task.

 

为了给LruCache选择一个合适的大小,你需要考虑很多因素,比如:

你其余的activity/应用对内存的使用情况

一次有多少图片会在屏幕上?在准备来到屏幕上的图片中要有多少是可用的。

设备的屏幕大小和密度是多少?一个超高密度屏幕(xhdpi)的设备,比如Galaxy Nexus就比Nexus shdpi)需要一个更大的cache来保存相同数量的图片在内存中

图片的尺寸和配置是什么样的,每个将占用多大的memory

图片访问的频率怎么样?一些图片的访问是不是要比其它的要更加频繁?如果是这样的话,那么你可能会让某些items总是在内存中或者是对于不同组的bitmaps有不同的LruCache

你能够平衡质量和数量吗?有时候,存储大量低质量的bitmaps是很有用的,而在另一个后台任务中加载一个高质量的版本。

 

There is nospecific size or formula that suits all applications, it's up to you to analyzeyour usage and come up with a suitable solution. A cache that is too smallcauses additional overhead with no benefit, a cache that is too large can onceagain cause java.lang.OutOfMemory exceptionsand leave the rest of your app little memory to work with.

Here’s anexample of setting up a LruCache forbitmaps:

这里没有特定的大小或者是公式来适应所有的应用,它取决于你去分析你的用法,然后剔除一个合适的解决方案。cache太小了没啥用,还要造成额外的开销,cache太大了可能又要造成OOM异常并且给你的应用的其他部分留下很少的内存

 

这里有一个为bitmaps设置LruCache的例子

 

private LruCache<String,Bitmap> mMemoryCache;

@Override
protected void onCreate(Bundle savedInstanceState){
    ...
    // Get maxavailable VM memory, exceeding this amount will throw an
    // OutOfMemoryexception. Stored in kilobytes as LruCache takes an
    // int in itsconstructor.
    final int maxMemory=(int)(Runtime.getRuntime().maxMemory()/1024);

    // Use 1/8th ofthe 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 inkilobytes 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 thisexample, one eighth of the application memory is allocated for our cache. On anormal/hdpi device this is a minimum of around 4MB (32/8). A full screen GridView filledwith images on a device with 800x480 resolution would use around 1.5MB (800*480*4bytes), so this would cache a minimum of around 2.5 pages of images in memory.

When loading abitmap into an ImageView, the LruCache is checkedfirst. If an entry is found, it is used immediately to update the ImageView, otherwise abackground thread is spawned to process the image:

注意:在这个例子中,应用内存的1/8分给了cache,在一个普通/hdpi设备,最低大约是4MB(32/8).在一个800*480分辨率的设备上,一个全屏的填满了图片的GridView,将会使用大约1.5MB(800*480*4Byte),因此,这个cache能够缓存大约2.5页的图片在内存中。

 

当加载一个图片到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也需要进行修改,添加条目(entry)到memorycache

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 DiskCache

使用磁盘cache


A memory cache is useful in speedingup access to recently viewed bitmaps, however you cannot rely on images beingavailable in this cache. Components like GridView with larger datasetscan easily fill up a memory cache. Your application could be interrupted byanother task like a phone call, and while in the background it might be killedand the memory cache destroyed. Once the user resumes, your application has toprocess each image again.

A disk cache can be used in thesecases to persist processed bitmaps and help decrease loading times where imagesare no longer available in a memory cache. Of course, fetching images from diskis slower than loading from memory and should be done in a background thread,as disk read times can be unpredictable.

 

memory cache在加快对最近看过的bitmaps的访问速度上是很有用的,然而你不能依赖cache中的图片总是可用的。像拥有大量数据集的GridView很容易填满memory cache。你的应用可能会被另一个任务比如来电给中断了,在后台它可能被killed,memory cache也会被销毁,一旦用户继续,你的应用不得不再处理这些图片一次。

在这些方面磁盘cache能够持久保留处理过的bitmaps,并且在memorycache中的图片不在可用的地方帮助减少加载的时间。当然,从磁盘获取图片的比从内存中获取要慢,而且也需要在后台线程中做,因为磁盘读取的时间不可预测的。

 

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

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

 

注意:如果图片访问非常频繁的话ContentProvider可能更合适去存储cached images,比如imagegallery应用。

这个类的示例代码使用了Android source.中的DiskLruCache实现。除了存在的memorycache外,再添加一个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);
}

Note: Eveninitializing the disk cache requires disk operations and therefore should nottake place on the main thread. However, this does mean there's a chance thecache is accessed before initialization. To address this, in the aboveimplementation, a lock object ensures that the app does not read from the diskcache until the cache has been initialized.

While the memorycache is checked in the UI thread, the disk cache is checked in the backgroundthread. Disk operations should never take place on the UI thread. When imageprocessing is complete, the final bitmap is added to both the memory and diskcache for future use.

注意:初始化磁盘缓存需要磁盘操作,因此不应该占用主线程,然而,这确实意味着在初始化之前,缓存就有呗访问的可能。为了解决这个问题,在上面的视线中,锁对象能够确保在cache初始化之前app不会从磁盘cache中读取数据

 

memory cache是在UI线程中检查,而磁盘cache是在后台线程中检查。磁盘操作应该永远不要占用UI线程,当图片处理完成后,最后的bitmap将同时加载到memory和disk cache for future use

 

HandleConfiguration Changes

处理配置改变


Runtime configuration changes, such asa screen orientation change, cause Android to destroy and restart the runningactivity with the new configuration (For more information about this behavior,see Handling RuntimeChanges). You want to avoid having to process all your imagesagain so the user has a smooth and fast experience when a configuration changeoccurs.

运行时配置改变,比如屏幕的方向发生了改变,造成Android销毁并且使用新配置(获取更多关于这种现象的信息,看Handling RuntimeChangesrestart运行的activity。你想避免重新处理所有的图片。以便当配置发生改变的时候,用户能够有流畅快速的体验

Luckily, you have a nice memory cacheof bitmaps that you built in the Use a Memory Cache section. This cachecan be passed through to the new activity instance using a Fragment which is preserved bycallingsetRetainInstance(true)). Afterthe activity has been recreated, this retained Fragment is reattached and yougain access to the existing cache object, allowing images to be quickly fetchedand re-populated into theImageView objects.

 

幸运的是,在 Use a Memory Cache这一节中,你有一个很好的bitmapsmemorycache通过使用Fragment,并调用setRetainInstance(true),这个cache能够传递到新的activity实例中。在activity 重建后,这个保留的Fragment重新附在activity上,你能够获取现存的cache对象,允许图片快速获取并重新填充到ImageView对象中。

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

这里有一个例子,当配置改变时使用Fragment保留Lrucache对象

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

To test this out,try rotating a device both with and without retaining the Fragment. You should notice littleto no lag as the images populate the activity almost instantly from memory whenyou retain the cache. Any images not found in the memory cache are hopefullyavailable in the disk cache, if not, they are processed as usual.

要测试它是否有效,尝试在有/没有保持Fragment的情况下旋转设备。当cache保存了的情况下,你会注意到图片立即从内测填充到Activity中,没有什么延迟。图片如果没有从内存中找到,就希望能够在磁盘cache找到,如果都找不到,那么就得像平时一样来处理它们



转载请注明出处:http://blog.csdn.net/storyofliu_yu