Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> Android的Drawable緩存機制源碼分析

Android的Drawable緩存機制源碼分析

編輯:關於Android編程

Android獲取Drawable的方式一般是Resources.getDrawable(int),Framework會返回給你一個頂層抽象的Drawable對象。而在Framework中,系統使用了享元的方式來節省內存。為了證明這一點,我們來寫一個小demo:

我們在我們的Android項目中引入一個簡單的圖片test.png。由於我們只是為了享元的結論,我們定義一個簡單的Activity,並復寫它的onCreate方法:

 

List list = new ArrayList();

    Bitmap bitmap = null;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        for (int i = 0; i < 10; i ++) {
            bitmap  = BitmapFactory.decodeResource(getResources(), R.drawable.test);
            list.add(bitmap);
        }
        ImageView iv = new ImageView(this);
        iv.setImageBitmap(bitmap);
        this.setContentView(iv);
    }
可能你這裡有疑惑為何要需要一個list把Bitmap存儲起來,這重要是為了避免GC引起的內存釋放。好了我們將我們的內存打印出來會發現我們加入了10個Bitmap占用的實際內存是:26364K。我們在轉化成為Drawable的方式:

 

 

 List list = new ArrayList();

    Drawable bitmap = null;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        for (int i = 0; i < 10; i ++) {
            bitmap  = this.getResources().getDrawable(R.drawable.test);
            list.add(bitmap);
        }
        ImageView iv = new ImageView(this);
        iv.setImageDrawable(bitmap);
        this.setContentView(iv);
    }

我們再打印內存,發現內存已經降到了:7844K,這部分數據基本就證明了我們的結論。那麼有沒有可能是Resources緩存了相同的drawable。當然不是,你可以寫一個簡單代碼測試一下:

 

 

Drawable d1 = this.getResources().getDrawable(R.drawable.test);
        Drawable d2 = this.getResources().getDrawable(R.drawable.test);
        System.out.println(">>>d1 == d2 ? = "+(d1 == d2));
你會發現輸出的是false。實際上,享元這點我們基本達成了共識,關鍵Framwork來包裝Drawable的時候還引入了組合模式,Framework本身緩存的是你這個Drawable的核心元數據。

 

 

Resources.java
 Drawable loadDrawable(TypedValue value, int id)
            throws NotFoundException {
...
Drawable dr = getCachedDrawable(isColorDrawable ? mColorDrawableCache : mDrawableCache, key);
...
}

從代碼可以看出,系統對Drawable主要分成兩大類,實際上還有一類熟悉預加載類的Drawable,不過不作為我們討論的重點,由於我們load的並不屬於color類型的Drawable,因此我們對應的享元池由mDrawableCache對象實現。
Resources.java
private Drawable getCachedDrawable(
            LongSparseArray> drawableCache,
            long key) {
        synchronized (mAccessLock) {
            WeakReference wr = drawableCache.get(key);
            if (wr != null) {   // we have the key
                Drawable.ConstantState entry = wr.get();
                if (entry != null) {
                    //Log.i(TAG, "Returning cached drawable @ #" +
                    //        Integer.toHexString(((Integer)key).intValue())
                    //        + " in " + this + ": " + entry);
                    return entry.newDrawable(this);
                }
                else {  // our entry has been purged
                    drawableCache.delete(key);
                }
            }
        }
        return null;
    }
我們通過調用代碼,會發現我們存儲在數據池中的根本不是我們的Drawable對象,而是一個叫做Drawable.ConstantState類型的對象,而且用了弱引用包裝起來。ConstantState是一個抽象類,有多個子類的實現

 

 

public static abstract class ConstantState {
        /**
         * Create a new drawable without supplying resources the caller
         * is running in.  Note that using this means the density-dependent
         * drawables (like bitmaps) will not be able to update their target
         * density correctly. One should use {@link #newDrawable(Resources)}
         * instead to provide a resource.
         */
        public abstract Drawable newDrawable();
        /**
         * Create a new Drawable instance from its constant state.  This
         * must be implemented for drawables that change based on the target
         * density of their caller (that is depending on whether it is
         * in compatibility mode).
         */
        public Drawable newDrawable(Resources res) {
            return newDrawable();
        }
        /**
         * Return a bit mask of configuration changes that will impact
         * this drawable (and thus require completely reloading it).
         */
        public abstract int getChangingConfigurations();

        /**
         * @hide
         */
        public Bitmap getBitmap() {
            return null;
        }
    }
由於我們使用的是BitmapDrawable,而BitmapDrawable對應的ConstantState是BitmapState

 

 

final static class BitmapState extends ConstantState {
        Bitmap mBitmap;
        int mChangingConfigurations;
        int mGravity = Gravity.FILL;
        Paint mPaint = new Paint(DEFAULT_PAINT_FLAGS);
        Shader.TileMode mTileModeX = null;
        Shader.TileMode mTileModeY = null;
        int mTargetDensity = DisplayMetrics.DENSITY_DEFAULT;
        boolean mRebuildShader;
        boolean mAutoMirrored;

        BitmapState(Bitmap bitmap) {
            mBitmap = bitmap;
        }

        BitmapState(BitmapState bitmapState) {
            this(bitmapState.mBitmap);
            mChangingConfigurations = bitmapState.mChangingConfigurations;
            mGravity = bitmapState.mGravity;
            mTileModeX = bitmapState.mTileModeX;
            mTileModeY = bitmapState.mTileModeY;
            mTargetDensity = bitmapState.mTargetDensity;
            mPaint = new Paint(bitmapState.mPaint);
            mRebuildShader = bitmapState.mRebuildShader;
            mAutoMirrored = bitmapState.mAutoMirrored;
        }

        @Override
        public Bitmap getBitmap() {
            return mBitmap;
        }

        @Override
        public Drawable newDrawable() {
            return new BitmapDrawable(this, null);
        }

        @Override
        public Drawable newDrawable(Resources res) {
            return new BitmapDrawable(this, res);
        }

        @Override
        public int getChangingConfigurations() {
            return mChangingConfigurations;
        }
    }
我們可以看到BitmapState對應的newDrawable方法,它將自己作為參數傳遞給BitmapDrawable對象,也就是說BitmapDrawble組合了同一個的BitmapState。這樣就實現了同一個Bitmap資源的復用。

 

跟到這,相信大家都跟我一樣了解了Bitmap是如何從cache中取出,我們接下來看一下ConstantState是如何存入的。

 

Resources.loadDrawable()
{
...
                        InputStream is = mAssets.openNonAsset(
                                value.assetCookie, file, AssetManager.ACCESS_STREAMING);
        //                System.out.println("Opened file " + file + ": " + is);
                        // MIUI MOD:
                        // dr = Drawable.createFromResourceStream(this, value, is, file, null);
                        dr = createFromResourceStream(this, value, is, file, id);
                        is.close();
...
 if (dr != null) {
            dr.setChangingConfigurations(value.changingConfigurations);
            cs = dr.getConstantState();
            if (cs != null) {
                if (mPreloading) {
                    final int changingConfigs = cs.getChangingConfigurations();
                    if (isColorDrawable) {
                        if (verifyPreloadConfig(changingConfigs, 0, value.resourceId,
                                "drawable")) {
                            sPreloadedColorDrawables.put(key, cs);
                        }
                    } else {
                        if (verifyPreloadConfig(changingConfigs,
                                LAYOUT_DIR_CONFIG, value.resourceId, "drawable")) {
                            if ((changingConfigs&LAYOUT_DIR_CONFIG) == 0) {
                                // If this resource does not vary based on layout direction,
                                // we can put it in all of the preload maps.
                                sPreloadedDrawables[0].put(key, cs);
                                sPreloadedDrawables[1].put(key, cs);
                            } else {
                                // Otherwise, only in the layout dir we loaded it for.
                                final LongSparseArray preloads
                                        = sPreloadedDrawables[mConfiguration.getLayoutDirection()];
                                preloads.put(key, cs);
                            }
                        }
                    }
                } else {
                    synchronized (mAccessLock) {
                        //Log.i(TAG, "Saving cached drawable @ #" +
                        //        Integer.toHexString(key.intValue())
                        //        + " in " + this + ": " + cs);
                        if (isColorDrawable) {
                            mColorDrawableCache.put(key, new WeakReference(cs));
                        } else {
                            mDrawableCache.put(key, new WeakReference(cs));
                        }
                    }
                }
            }
...

}
可以看出,當你新生成一個Drawable的時候,就會將Drawable的ConstantState從Drawable中取出,然後放入你Cache池中。


 

 

  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved