Android的Drawable緩衝機制源碼分析
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.javaprivate 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池中。