getResources().getDrawable(id)
会执行
public Drawable getDrawable(int id) throws NotFoundException { synchronized (mTmpValue) { TypedValue value = mTmpValue; getValue(id, value, true); return loadDrawable(value, id); } }
1 /*package*/ Drawable loadDrawable(TypedValue value, int id) 2 throws NotFoundException { 3 4 if (TRACE_FOR_PRELOAD) { 5 // Log only framework resources 6 if ((id >>> 24) == 0x1) { 7 final String name = getResourceName(id); 8 if (name != null) android.util.Log.d("PreloadDrawable", name); 9 } 10 } 11 12 final long key = (((long) value.assetCookie) << 32) | value.data; 13 boolean isColorDrawable = false; 14 if (value.type >= TypedValue.TYPE_FIRST_COLOR_INT && 15 value.type <= TypedValue.TYPE_LAST_COLOR_INT) { 16 isColorDrawable = true; 17 } 18 Drawable dr = getCachedDrawable(isColorDrawable ? mColorDrawableCache : mDrawableCache, key); 19 20 if (dr != null) { 21 return dr; 22 } 23 24 Drawable.ConstantState cs = isColorDrawable ? 25 sPreloadedColorDrawables.get(key) : sPreloadedDrawables.get(key); 26 if (cs != null) { 27 dr = cs.newDrawable(this); 28 } else { 29 if (isColorDrawable) { 30 dr = new ColorDrawable(value.data); 31 } 32 33 if (dr == null) { 34 if (value.string == null) { 35 throw new NotFoundException( 36 "Resource is not a Drawable (color or path): " + value); 37 } 38 39 String file = value.string.toString(); 40 41 if (TRACE_FOR_MISS_PRELOAD) { 42 // Log only framework resources 43 if ((id >>> 24) == 0x1) { 44 final String name = getResourceName(id); 45 if (name != null) android.util.Log.d(TAG, "Loading framework drawable #" 46 + Integer.toHexString(id) + ": " + name 47 + " at " + file); 48 } 49 } 50 51 if (DEBUG_LOAD) Log.v(TAG, "Loading drawable for cookie " 52 + value.assetCookie + ": " + file); 53 54 if (file.endsWith(".xml")) { 55 try { 56 XmlResourceParser rp = loadXmlResourceParser( 57 file, id, value.assetCookie, "drawable"); 58 dr = Drawable.createFromXml(this, rp); 59 rp.close(); 60 } catch (Exception e) { 61 NotFoundException rnf = new NotFoundException( 62 "File " + file + " from drawable resource ID #0x" 63 + Integer.toHexString(id)); 64 rnf.initCause(e); 65 throw rnf; 66 } 67 68 } else { 69 try { 70 InputStream is = mAssets.openNonAsset( 71 value.assetCookie, file, AssetManager.ACCESS_STREAMING); 72 // System.out.println("Opened file " + file + ": " + is); 73 dr = Drawable.createFromResourceStream(this, value, is, 74 file, null); 75 is.close(); 76 // System.out.println("Created stream: " + dr); 77 } catch (Exception e) { 78 NotFoundException rnf = new NotFoundException( 79 "File " + file + " from drawable resource ID #0x" 80 + Integer.toHexString(id)); 81 rnf.initCause(e); 82 throw rnf; 83 } 84 } 85 } 86 } 87 88 if (dr != null) { 89 dr.setChangingConfigurations(value.changingConfigurations); 90 cs = dr.getConstantState(); 91 if (cs != null) { 92 if (mPreloading) { 93 if (isColorDrawable) { 94 sPreloadedColorDrawables.put(key, cs); 95 } else { 96 sPreloadedDrawables.put(key, cs); 97 } 98 } else { 99 synchronized (mTmpValue) { 100 //Log.i(TAG, "Saving cached drawable @ #" + 101 // Integer.toHexString(key.intValue()) 102 // + " in " + this + ": " + cs); 103 if (isColorDrawable) { 104 mColorDrawableCache.put(key, new WeakReference<Drawable.ConstantState>(cs)); 105 } else { 106 mDrawableCache.put(key, new WeakReference<Drawable.ConstantState>(cs)); 107 } 108 } 109 } 110 } 111 } 112 113 return dr; 114 }
请注意18行,这里我们会去尝试读取CachedDrawable,看看这里是否有我们需要读取的Drawable。显然,第一次读取的时候,是不会存在缓存。所以我们继续往下看。
1 private Drawable getCachedDrawable(long key) { 2 synchronized (mTmpValue) { 3 WeakReference<Drawable.ConstantState> wr = mDrawableCache.get(key); 4 if (wr != null) { // we have the key 5 Drawable.ConstantState entry = wr.get(); 6 if (entry != null) { 7 //Log.i(TAG, "Returning cached drawable @ #" + 8 // Integer.toHexString(((Integer)key).intValue()) 9 // + " in " + this + ": " + entry); 10 return entry.newDrawable(this); 11 } 12 else { // our entry has been purged 13 mDrawableCache.delete(key); 14 } 15 } 16 } 17 return null; 18 }
关键是73行,这里会从资源中创建一个Drawable对象。这个函数最终会调用到drawableFromBitmap(),其中会new BitmapDrawable(res, bm):
1 public BitmapDrawable(Resources res, Bitmap bitmap) { 2 this(new BitmapState(bitmap), res); 3 mBitmapState.mTargetDensity = mTargetDensity; 4 }
请注意,我们创建了一个BitmapState对象。
继续看loadDrawable()函数,来到90行,这里,我们会把新的Drawable的ConstantState加入到缓存中。这个ConstantState对于BitmapDrawable来说,实际就是一个BitmapState对象
final static class BitmapState extends ConstantState { }
然后,我们就完成第一次读取BitmapDrawable对象。
如果我们再次读取这个BitmapDrawable对象,回头看loadDrawable()的第18行,我们会从CachedDrawable中拿:
1 private Drawable getCachedDrawable( 2 LongSparseArray<WeakReference<ConstantState>> drawableCache, 3 long key) { 4 synchronized (mTmpValue) { 5 WeakReference<Drawable.ConstantState> wr = drawableCache.get(key); 6 if (wr != null) { // we have the key 7 Drawable.ConstantState entry = wr.get(); 8 if (entry != null) { 9 //Log.i(TAG, "Returning cached drawable @ #" + 10 // Integer.toHexString(((Integer)key).intValue()) 11 // + " in " + this + ": " + entry); 12 return entry.newDrawable(this); 13 } 14 else { // our entry has been purged 15 drawableCache.delete(key); 16 } 17 } 18 } 19 return null; 20 }
请看12行,对于BitmapDrawable的BitmapState来说,newDrawable()就是:
1 @Override 2 public Drawable newDrawable(Resources res) { 3 return new BitmapDrawable(this, res); 4 }
看到什么了?对了,两次读取到的Drawable共享了一个BitmapState。其实,所有的ConstantState都是共享的。NinePatchDrawable中的ConstantState,即NinePatchState,也是共享的。
那问题出在哪儿?请注意这个名字ConstantState。实际上,Drawable的设计者希望,能够共享给所有Drawable使用的state都必须是Constant的。但是,不幸的是BitmapDrawable的设计者却不打算遵循这个规范。请回头看看本文开始的地方写的BitmapDrawable的alpha是通过BitmapState的Paint来实现的。而BitmapState是共享的。也就是说同一个BitmapDrawable的alpha值是共享的!!而NinePatchDrawable就没有这个问题,其alpha设置并不共享。
会造成什么问题?
假设你有两个一样的Button,使用了BitmapDrawable作为背景,当你对某一个Button做alpha动画的时候,实际上另外一个Button的alpha值也在变化!!!但是因为另外一个Button没有重绘,所以你看不到,如果这个时候某一个事件导致界面重绘,比如锁屏再解锁,就会发现另外一个Button的alpha值也变化了。
所以,请慎用BitmapDrawable,最好不要用他来做alpha动画。当然,也不仅是alpha值才有这个问题,所有BitmapState中共享的值都会存在这个问题。请看:
1 Bitmap mBitmap; 2 int mChangingConfigurations; 3 int mGravity = Gravity.FILL; 4 Paint mPaint = new Paint(DEFAULT_PAINT_FLAGS); 5 Shader.TileMode mTileModeX = null; 6 Shader.TileMode mTileModeY = null; 7 int mTargetDensity = DisplayMetrics.DENSITY_DEFAULT; 8 boolean mRebuildShader;