android圖片緩存(包含ReusableBitmapDrawable和BitmapPool)


  現在做的項目中,有用到一個開源的2D地圖框架osmdroid,但是在項目中,使用還是有一些問題,例如,多個地圖實例,會有獨自的圖片緩存,Activity onPause時,並不會釋放圖片緩存,如果多級界面都有地圖的話,可能會造成很多手機內存溢出(按照每個瓦片256*256,屏幕1280*720來算,顯示一個屏幕的地圖,至少要在內存保存15張圖片,占用內存256*256*4*15=3.75M),所以還是得針對項目做一定的修改調整。今天將項目中使用到的圖片緩存做一個整理。

  首先說下整體的圖片加載流程:

  1、內存中加載。通過LruCache保存,LruCache中移除的圖片,如果沒有引用使用,會緩存到BitmapPool以供重用,避免頻繁申請釋放內存,這樣滑動時加載圖片會更平滑;

  2、從文件加載。內存中沒有,會從瓦片緩存目錄中加載,加載成功保存到內存並通知地圖刷新顯示;

  3、從網絡加載。如果本地文件也沒有,會從網絡下載,然后保存到瓦片緩存目錄,以及內存中,然后再通知地圖刷新顯示。

 

  接下來說一下Bitmap相關的幾個重要的參數:

  BitmapFactory.Options.inBitmap:

  If set, decode methods that take the Options object will attempt to reuse this bitmap when loading content. If the decode operation cannot use this bitmap, the decode method will return null and will throw an IllegalArgumentException. The current implementation necessitates that the reused bitmap be of the same size as the source content and in jpeg or png format (whether as a resource or as a stream). The configuration of the reused bitmap will override the setting of inPreferredConfig, if set.

  You should still always use the returned Bitmap of the decode method and not assume that reusing the bitmap worked, due to the constraints outlined above and failure situations that can occur. Checking whether the return value matches the value of the inBitmap set in the Options structure is a way to see if the bitmap was reused, but in all cases you should use the returned Bitmap to make sure that you are using the bitmap that was used as the decode destination.

  從Android 3.0(API level 11)開始,引入了BitmapFactory.Options.inBitmap字段,如果這個屬性被設置了,擁有這個Options對象的方法在解析圖片的時候會嘗試復用一張已存在的圖片。這意味着圖片緩存被復用了,這意味着更流暢的用戶體驗以及更好的內存分配和回收。然而,要使用inBitmap有這一定的限制:

  1、在Android 4.4(API level 19)之前,只有相同大小的Btiamp才會被復用;

  2、必須配合inMutable=true,inSampleSize=1一起使用;

  3、一定要使用解碼方法BitmapFactory.decodeStream返回的Bitmap,否則重用可能會失敗。

  具體使用:  

BitmapFactory.Options bitmapOptions = new BitmapFactory.Options();

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
            bitmapOptions.inBitmap = obtainSizedBitmapFromPool();
            bitmapOptions.inSampleSize = 1;
            bitmapOptions.inMutable = true;
}

Bitmap bitmap = BitmapFactory.decodeStream(aFileInputStream, null, bitmapOptions);
Drawable drawable = new ReusableBitmapDrawable(bitmap);

 

  如果是在Android 2.3.3 (API level 10),以及更低的版本中,推薦使用Bitmap.recycle方法加快Bitmap內存的回收。

if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB) {
            //BitmapOptions.inBitmap無效,不緩存圖片
            if (b != null && !b.isRecycled()){
                b.recycle();
            }
            return;
}

 

  然后介紹下使用到的幾個比較重要的類:

  ReusableBitmapDrawable:主要是記錄Bitmap的引用數,在從內存中移除時判斷是否可以recycle回收釋放內存或添加到BitmapPool,也可避免"trying to use a recycled bitmap"得異常。

  代碼:

public class ReusableBitmapDrawable extends BitmapDrawable {

    private boolean mBitmapRecycled = false;
    private int mUsageRefCount = 0;

    public ReusableBitmapDrawable(Bitmap pBitmap) {
        super(pBitmap);
    }

    /**
     * 開始使用圖片,引用數+1,只要有地方引用圖片就不能回收
     * 之后還是需要通過isBitmapValid()確保圖片有效能用
     */
    public void beginUsingDrawable() {
        synchronized (this) {
            mUsageRefCount++;
        }
    }

    /**
     * 某個地方使用圖片結束,引用數-1
     */
    public void finishUsingDrawable() {
        synchronized (this) {
            mUsageRefCount--;
            if (mUsageRefCount < 0)
                throw new IllegalStateException("Unbalanced endUsingDrawable() called.");
        }
    }

    /**
     * 如果沒有任何引用了,返回圖片以便recycle釋放內存或者加入BitmapPool以供復用
     */
    public Bitmap tryRecycle() {
        synchronized (this) {
            if (mUsageRefCount == 0) {
                mBitmapRecycled = true;
                return getBitmap();
            }
        }
        return null;
    }

    /**
     * 判定是否已被回收或者加入緩存池
     */
    public boolean isBitmapValid() {
        synchronized (this) {
            return !mBitmapRecycled;
        }
    }
}

 

  使用示例:

Drawable currentMapTile = ......

boolean isReusable = currentMapTile != null
                    && currentMapTile instanceof ReusableBitmapDrawable;
final ReusableBitmapDrawable reusableBitmapDrawable =
                    isReusable ? (ReusableBitmapDrawable) currentMapTile : null;
if (isReusable) {
reusableBitmapDrawable.beginUsingDrawable();
}
try {
if (isReusable && !((ReusableBitmapDrawable) currentMapTile).isBitmapValid()) {
currentMapTile = getLoadingTile(); //已經回收或者緩存,顯示默認的加載圖片
isReusable = false;
}
onTileReadyToDraw(pCanvas, currentMapTile, mTileRect); //繪制瓦片

} finally {
if (isReusable)
reusableBitmapDrawable.finishUsingDrawable();
}

  

  當從內存中移除時:

public void returnDrawableToPool(ReusableBitmapDrawable drawable) {
        Bitmap b = drawable.tryRecycle();

        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB) {
            //BitmapOptions.inBitmap無效,不緩存圖片
            if (b != null && !b.isRecycled()){
                b.recycle();
            }
            return;
        }

        if (b != null && b.isMutable()){
            synchronized (mPool) {
                mPool.addLast(b);
            }
        }
}

 

 

  LruCache:

public class LruCache<K, V> {
    private final LinkedHashMap<K, V> map;
    /** Size of this cache in units. Not necessarily the number of elements. */
    private int size;
    private int maxSize;
    private int putCount;
    private int createCount;
    private int evictionCount;
    private int hitCount;
    private int missCount;
    /**
     * @param maxSize for caches that do not override {@link #sizeOf}, this is
     *     the maximum number of entries in the cache. For all other caches,
     *     this is the maximum sum of the sizes of the entries in this cache.
     */
    public LruCache(int maxSize) {
        if (maxSize <= 0) {
            throw new IllegalArgumentException("maxSize <= 0");
        }
        this.maxSize = maxSize;
        this.map = new LinkedHashMap<K, V>(0, 0.75f, true);
    }
    /**
     * Sets the size of the cache.
     *
     * @param maxSize The new maximum size.
     */
    public void resize(int maxSize) {
        if (maxSize <= 0) {
            throw new IllegalArgumentException("maxSize <= 0");
        }
        synchronized (this) {
            this.maxSize = maxSize;
        }
        trimToSize(maxSize);
    }
    /**
     * Returns the value for {@code key} if it exists in the cache or can be
     * created by {@code #create}. If a value was returned, it is moved to the
     * head of the queue. This returns null if a value is not cached and cannot
     * be created.
     */
    public final V get(K key) {
        if (key == null) {
            throw new NullPointerException("key == null");
        }
        V mapValue;
        synchronized (this) {
            mapValue = map.get(key);
            if (mapValue != null) {
                hitCount++;
                return mapValue;
            }
            missCount++;
        }
        /*
         * Attempt to create a value. This may take a long time, and the map
         * may be different when create() returns. If a conflicting value was
         * added to the map while create() was working, we leave that value in
         * the map and release the created value.
         */
        V createdValue = create(key);
        if (createdValue == null) {
            return null;
        }
        synchronized (this) {
            createCount++;
            mapValue = map.put(key, createdValue);
            if (mapValue != null) {
                // There was a conflict so undo that last put
                map.put(key, mapValue);
            } else {
                size += safeSizeOf(key, createdValue);
            }
        }
        if (mapValue != null) {
            entryRemoved(false, key, createdValue, mapValue);
            return mapValue;
        } else {
            trimToSize(maxSize);
            return createdValue;
        }
    }
    /**
     * Caches {@code value} for {@code key}. The value is moved to the head of
     * the queue.
     *
     * @return the previous value mapped by {@code key}.
     */
    public final V put(K key, V value) {
        if (key == null || value == null) {
            throw new NullPointerException("key == null || value == null");
        }
        V previous;
        synchronized (this) {
            putCount++;
            size += safeSizeOf(key, value);
            previous = map.put(key, value);
            if (previous != null) {
                size -= safeSizeOf(key, previous);
            }
        }
        if (previous != null) {
            entryRemoved(false, key, previous, value);
        }
        trimToSize(maxSize);
        return previous;
    }
    /**
     * Remove the eldest entries until the total of remaining entries is at or
     * below the requested size.
     *
     * @param maxSize the maximum size of the cache before returning. May be -1
     *            to evict even 0-sized elements.
     */
    public void trimToSize(int maxSize) {
        while (true) {
            K key;
            V value;
            synchronized (this) {
                if (size < 0 || (map.isEmpty() && size != 0)) {
                    throw new IllegalStateException(getClass().getName()
                            + ".sizeOf() is reporting inconsistent results!");
                }
                if (size <= maxSize || map.isEmpty()) {
                    break;
                }
                Map.Entry<K, V> toEvict = map.entrySet().iterator().next();
                key = toEvict.getKey();
                value = toEvict.getValue();
                map.remove(key);
                size -= safeSizeOf(key, value);
                evictionCount++;
            }
            entryRemoved(true, key, value, null);
        }
    }
    /**
     * Removes the entry for {@code key} if it exists.
     *
     * @return the previous value mapped by {@code key}.
     */
    public final V remove(K key) {
        if (key == null) {
            throw new NullPointerException("key == null");
        }
        V previous;
        synchronized (this) {
            previous = map.remove(key);
            if (previous != null) {
                size -= safeSizeOf(key, previous);
            }
        }
        if (previous != null) {
            entryRemoved(false, key, previous, null);
        }
        return previous;
    }
    /**
     * Called for entries that have been evicted or removed. This method is
     * invoked when a value is evicted to make space, removed by a call to
     * {@link #remove}, or replaced by a call to {@link #put}. The default
     * implementation does nothing.
     *
     * <p>The method is called without synchronization: other threads may
     * access the cache while this method is executing.
     *
     * @param evicted true if the entry is being removed to make space, false
     *     if the removal was caused by a {@link #put} or {@link #remove}.
     * @param newValue the new value for {@code key}, if it exists. If non-null,
     *     this removal was caused by a {@link #put}. Otherwise it was caused by
     *     an eviction or a {@link #remove}.
     */
    protected void entryRemoved(boolean evicted, K key, V oldValue, V newValue) {}
    /**
     * Called after a cache miss to compute a value for the corresponding key.
     * Returns the computed value or null if no value can be computed. The
     * default implementation returns null.
     *
     * <p>The method is called without synchronization: other threads may
     * access the cache while this method is executing.
     *
     * <p>If a value for {@code key} exists in the cache when this method
     * returns, the created value will be released with {@link #entryRemoved}
     * and discarded. This can occur when multiple threads request the same key
     * at the same time (causing multiple values to be created), or when one
     * thread calls {@link #put} while another is creating a value for the same
     * key.
     */
    protected V create(K key) {
        return null;
    }
    private int safeSizeOf(K key, V value) {
        int result = sizeOf(key, value);
        if (result < 0) {
            throw new IllegalStateException("Negative size: " + key + "=" + value);
        }
        return result;
    }
    /**
     * Returns the size of the entry for {@code key} and {@code value} in
     * user-defined units.  The default implementation returns 1 so that size
     * is the number of entries and max size is the maximum number of entries.
     *
     * <p>An entry's size must not change while it is in the cache.
     */
    protected int sizeOf(K key, V value) {
        return 1;
    }
    /**
     * Clear the cache, calling {@link #entryRemoved} on each removed entry.
     */
    public final void evictAll() {
        trimToSize(-1); // -1 will evict 0-sized elements
    }
    /**
     * For caches that do not override {@link #sizeOf}, this returns the number
     * of entries in the cache. For all other caches, this returns the sum of
     * the sizes of the entries in this cache.
     */
    public synchronized final int size() {
        return size;
    }
    /**
     * For caches that do not override {@link #sizeOf}, this returns the maximum
     * number of entries in the cache. For all other caches, this returns the
     * maximum sum of the sizes of the entries in this cache.
     */
    public synchronized final int maxSize() {
        return maxSize;
    }
    /**
     * Returns the number of times {@link #get} returned a value that was
     * already present in the cache.
     */
    public synchronized final int hitCount() {
        return hitCount;
    }
    /**
     * Returns the number of times {@link #get} returned null or required a new
     * value to be created.
     */
    public synchronized final int missCount() {
        return missCount;
    }
    /**
     * Returns the number of times {@link #create(Object)} returned a value.
     */
    public synchronized final int createCount() {
        return createCount;
    }
    /**
     * Returns the number of times {@link #put} was called.
     */
    public synchronized final int putCount() {
        return putCount;
    }
    /**
     * Returns the number of values that have been evicted.
     */
    public synchronized final int evictionCount() {
        return evictionCount;
    }
    /**
     * Returns a copy of the current contents of the cache, ordered from least
     * recently accessed to most recently accessed.
     */
    public synchronized final Map<K, V> snapshot() {
        return new LinkedHashMap<K, V>(map);
    }
    @Override public synchronized final String toString() {
        int accesses = hitCount + missCount;
        int hitPercent = accesses != 0 ? (100 * hitCount / accesses) : 0;
        return String.format("LruCache[maxSize=%d,hits=%d,misses=%d,hitRate=%d%%]",
                maxSize, hitCount, missCount, hitPercent);
    }
}
View Code

 

  BitmapPool: 緩存Bitmap以便重用,避免頻繁申請回收內存,使圖片加載更加平滑。

public class BitmapPool {
    final LinkedList<Bitmap> mPool = new LinkedList<Bitmap>();

    private static BitmapPool sInstance;

    public static BitmapPool getInstance() {
        if (sInstance == null)
            sInstance = new BitmapPool();

        return sInstance;
    }

    public void returnDrawableToPool(ReusableBitmapDrawable drawable) {
     Bitmap b = drawable.tryRecycle();

      if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB) {
        //BitmapOptions.inBitmap無效,不緩存圖片
        if (b != null && !b.isRecycled()){
          b.recycle();
        }
        return;
      }

      if (b != null && b.isMutable()){

        synchronized (mPool) {
          mPool.addLast(b);
        }
      }

    }

public void applyReusableOptions(final BitmapFactory.Options aBitmapOptions) {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
            aBitmapOptions.inBitmap = obtainBitmapFromPool();
            aBitmapOptions.inSampleSize = 1;
            aBitmapOptions.inMutable = true;
        }
    }

    public Bitmap obtainBitmapFromPool() {
        synchronized (mPool) {
            if (mPool.isEmpty()) {
                return null;
            } else {
                final Bitmap bitmap = mPool.removeFirst();
                if (bitmap.isRecycled()) {
                    return obtainBitmapFromPool(); // recurse
                } else {
                    return bitmap;
                }
            }
        }
    }

    public Bitmap obtainSizedBitmapFromPool(final int aWidth, final int aHeight) {
        synchronized (mPool) {
            if (mPool.isEmpty()) {
                return null;
            } else {
                for (final Bitmap bitmap : mPool) {
                    if (bitmap.isRecycled()) {
                        mPool.remove(bitmap);
                        return obtainSizedBitmapFromPool(aWidth, aHeight); // recurse to prevent ConcurrentModificationException
                    } else if (bitmap.getWidth() == aWidth && bitmap.getHeight() == aHeight) {
                        mPool.remove(bitmap);
                        return bitmap;
                    }
                }
            }
        }

        return null;
    }

    public void clearBitmapPool() {
        synchronized (sInstance.mPool) {
            while (!sInstance.mPool.isEmpty()) {
                Bitmap bitmap = sInstance.mPool.remove();
                bitmap.recycle();
            }
        }
    }
}

 

  如果文章中又寫的有問題的地方,歡迎反饋。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM