Bitmap的cache处理,包括memory cache和disk cache

本文深入探讨了Android应用中内存和磁盘缓存的优化策略,包括如何利用LRU缓存和DiskLruCache有效地管理图片资源,确保应用在不同设备上的高效运行,同时提供了一种跨屏幕方向的缓存保存方法,以及如何通过Fragment实现缓存的跨实例保留。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

翻译至点击打开链接


安卓的控件经常会显示很多图片,如list view or view pager: recycling the child views as they move off-screen.

垃圾回收会释放加载的bitmapgarbage collector also frees up your loaded bitmaps, assuming you don't keep any long lived references. 

下面介绍一直内存cache和disk cache: A memory and disk cache

LruCache class 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.

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. 

举个栗子LRU cache,最近最少用

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的内存用于缓存图片,On a normal/hdpi device this is a minimum of around 4MB (32/8).

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.

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 abackground thread is spawned to process the image:

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是一个异步task

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;

    }

    …

}

存在内存里是不够的,在onPause时,还需要存到disk中,

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

举个栗子

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

}


 a lock object ensures that the app does not read from the disk cache until the cache has been initialized.

切换屏幕方向

用Fragment举个栗子,设置fragment为retain instance

This cache can be passed through to the new activity instance using a Fragment which is preserved by calling 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 theImageView objects.

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;

    }

    ...

}

其中setRetainInstance的源码如下,不会去调用onDestroy和onCreate,而是调用onDetach和onAttach

 /**
     * Control whether a fragment instance is retained across Activity
     * re-creation (such as from a configuration change).  This can only
     * be used with fragments not in the back stack.  If set, the fragment
     * lifecycle will be slightly different when an activity is recreated:
     * <ul>
     * <li> {@link #onDestroy()} will not be called (but {@link #onDetach()} still
     * will be, because the fragment is being detached from its current activity).
     * <li> {@link #onCreate(Bundle)} will not be called since the fragment
     * is not being re-created.
     * <li> {@link #onAttach(Activity)} and {@link #onActivityCreated(Bundle)} <b>will</b>
     * still be called.
     * </ul>
     */
    public void setRetainInstance(boolean retain) {
        if (retain && mParentFragment != null) {
            throw new IllegalStateException(
                    "Can't retain fragements that are nested in other fragments");
        }
        mRetainInstance = retain;
    }


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

    }

}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值