一.問題背景描述
測試妹子:我發(fā)現(xiàn)我一打開X界面,Y界面的分隔線消失了O粱辍7E省!
我:這么神奇雌澄?斋泄??
二.初找原因
經過一頓操作后發(fā)現(xiàn)镐牺,這兩個界面用到同一個顏色值炫掐,而X界面對用了這個顏色值的當背景的view改變了透明度,所以再打開Y界面的時候睬涧,用同一個顏色的分割線就消失了募胃。
三.臨時解決
考慮到項目進度沛厨,我先用這個顏色寫了一個shape來當背景,五分鐘解決問題摔认。
四.尋找真相
對于真相的好奇逆皮,打算看看究竟咋回事,之前對于資源加載這塊也沒好好了解過参袱,趁這次正好看一下电谣。
猜測
一定是哪里對這顏色做了緩存,如果這個猜測是對的抹蚀,那么打印這兩個drawable應該是同一個對象
I bbbb : ====background=android.graphics.drawable.ColorDrawable@f74fed8
I bbbb : ====getDrawable=android.graphics.drawable.ColorDrawable@d868231
涼涼,不是同一個對象剿牺,猜想錯誤
分析
那么接下來只能看源碼了。通過setbackground不斷的深入环壤,最后來到ResourcesImpl.loadDrawable晒来。
仔細看一下這個drawable的加載流程。
Drawable loadDrawable(@NonNull Resources wrapper, @NonNull TypedValue value, int id,
int density, @Nullable Resources.Theme theme)
throws NotFoundException {
...
}
1. 先判斷是不是ColorDrawable,然后去取相應類型的緩存郑现。
final boolean isColorDrawable;
final DrawableCache caches;
final long key;
if (value.type >= TypedValue.TYPE_FIRST_COLOR_INT
&& value.type <= TypedValue.TYPE_LAST_COLOR_INT) {
isColorDrawable = true;
caches = mColorDrawableCache;
key = value.data;
} else {
isColorDrawable = false;
caches = mDrawableCache;
key = (((long) value.assetCookie) << 32) | value.data;
}
ColorDrawable和其他drawable分別是兩個緩存
private final DrawableCache mDrawableCache = new DrawableCache();
private final DrawableCache mColorDrawableCache = new DrawableCache();
至于其他drawable都有些啥湃崩,這就有點多了,如下圖接箫,
接下來其他Drawable都以BitmapDrawable為代表攒读。
2. 緩存里是否能取到Drawable,如果有就直接返回辛友。這里代碼請先忽略!mPreloading && useCache這個條件
if (!mPreloading && useCache) {
//注意這里getInstance方法薄扁,等一下會分析到
final Drawable cachedDrawable = caches.getInstance(key, wrapper, theme);
if (cachedDrawable != null) {
return cachedDrawable;
}
}
3. 如果緩存里沒有,就加載新的drawable废累。(中間略過了跟預加載相關的部分)
Drawable dr;
if (cs != null) {
//這里是對預加載來的判斷邓梅,暫時先跳過
dr = cs.newDrawable(wrapper);
} else if (isColorDrawable) {
dr = new ColorDrawable(value.data);
} else {
dr = loadDrawableForCookie(wrapper, value, id, null);
}
4. 緩存這次生成的drawable,并返回邑滨,然后看一下緩存的代碼
if (dr != null && useCache) {
dr.setChangingConfigurations(value.changingConfigurations);
cacheDrawable(value, isColorDrawable, caches, theme, canApplyTheme, key, dr);
}
return dr;
然后看一下緩存的代碼,就是把當前的ConstantState緩存起來日缨,ConstantState是從Drawable獲取的,具體ConstantState是啥驼修,接著往下殿遂。
private void cacheDrawable(TypedValue value, boolean isColorDrawable, DrawableCache caches,
Resources.Theme theme, boolean usesTheme, long key, Drawable dr) {
final Drawable.ConstantState cs = dr.getConstantState();
if (cs == null) {
return;
}
...
synchronized (mAccessLock) {
caches.put(key, theme, cs, usesTheme);
}
}
}
目前的結論诈铛,加載drawable這個過程乙各,針對ColorDrawable和非ColorDrawable分別加了兩個緩存mColorDrawableCache和mDrawableCache,這兩個cache都是DrawableCache幢竹,都繼承于ThemedResourceCache耳峦。里面都存放著Drawable.ConstantState。
class DrawableCache extends ThemedResourceCache<Drawable.ConstantState> {
...
public Drawable getInstance(long key, Resources resources, Resources.Theme theme) {
final Drawable.ConstantState entry = get(key, theme);
if (entry != null) {
return entry.newDrawable(resources, theme);
}
return null;
}
...
}
其中ThemedResourceCache有mThemedEntries焕毫,mUnthemedEntries蹲坷,mNullThemedEntries存放著各種資源驶乾,主要是對不同主題的支持。這里的存儲數據結構是ArrayMap和LongSparseArray循签。
abstract class ThemedResourceCache<T> {
private ArrayMap<ThemeKey, LongSparseArray<WeakReference<T>>> mThemedEntries;
private LongSparseArray<WeakReference<T>> mUnthemedEntries;
private LongSparseArray<WeakReference<T>> mNullThemedEntries;
...
}
mColorDrawableCache和ColorDrawable具體緩存的東西不一樣级乐,mColorDrawableCache是存放ColorDrawable.ColorState,mDrawableCache是存放BItmapDrawable.BItmapState。ColorDrawable.ColorState和BItmapDrawable.BItmapState都繼承于Drawable.ConstantState县匠。
5. 接著分析风科,從剛才的第2步里面看到,如果緩存里面取到Drawable.ConstantState乞旦,就會調用getInstance贼穆,然后返回Drawable。
那么這個getInstance是怎么得到相應的Drawable的呢兰粉?
在剛才的DrawableCache的代碼看到是通過Drawable.ConstantState.newDrawable得到的故痊。再貼一下這段代碼
class DrawableCache extends ThemedResourceCache<Drawable.ConstantState> {
...
public Drawable getInstance(long key, Resources resources, Resources.Theme theme) {
final Drawable.ConstantState entry = get(key, theme);
if (entry != null) {
return entry.newDrawable(resources, theme);
}
return null;
}
...
}
那么接下來看看Drawable.ConstantState.newDrawable,這里我們選取ColorDrawable.ColorState作為代表玖姑,比較簡單愕秫,易于理解。
public class ColorDrawable extends Drawable {
private ColorState mColorState;
private boolean mMutated;
public ColorDrawable() {
mColorState = new ColorState();
}
public ColorDrawable(@ColorInt int color) {
mColorState = new ColorState();
setColor(color);
}
@Override
public Drawable mutate() {
if (!mMutated && super.mutate() == this) {
mColorState = new ColorState(mColorState);
mMutated = true;
}
return this;
}
@ColorInt
public int getColor() {
return mColorState.mUseColor;
}
@Override
public ConstantState getConstantState() {
return mColorState;
}
final static class ColorState extends ConstantState {
int[] mThemeAttrs;
int mBaseColor; // base color, independent of setAlpha()
@ViewDebug.ExportedProperty
int mUseColor; // basecolor modulated by setAlpha()
@Config int mChangingConfigurations;
ColorStateList mTint = null;
Mode mTintMode = DEFAULT_TINT_MODE;
ColorState() {
// Empty constructor.
}
ColorState(ColorState state) {
mThemeAttrs = state.mThemeAttrs;
mBaseColor = state.mBaseColor;
mUseColor = state.mUseColor;
mChangingConfigurations = state.mChangingConfigurations;
mTint = state.mTint;
mTintMode = state.mTintMode;
}
@Override
public Drawable newDrawable() {
return new ColorDrawable(this, null);
}
@Override
public Drawable newDrawable(Resources res) {
return new ColorDrawable(this, res);
}
}
private ColorDrawable(ColorState state, Resources res) {
mColorState = state;
updateLocalState(res);
}
}
這里我刪除了很多于這里無關的代碼焰络。從newDrawable的方法看出豫领,即使剛才我們從緩存里面取到了緩存的ColorState,但還是會new ColorDrawable舔琅,所以就出現(xiàn)了等恐,剛開始打臉的一幕,Drawable并不是同一個對象备蚓。但是课蔬,new drawable這個過程中,會把ColorState也傳到新的Drawable中郊尝,所以ColorState中的屬性都會傳到新的Drawable二跋。
那么現(xiàn)在可以大膽的猜想:
1. 剛開始的猜想,再進一步獲取ConstantState,這兩個的ConstantState肯定就是同一個對象了流昏。驗證如下
I bbbb : ====background cs=android.graphics.drawable.ColorDrawable$ColorState@dfe7bd1
I bbbb : ====getDrawable cs=android.graphics.drawable.ColorDrawable$ColorState@dfe7bd1
2. ConstantState里面一定存儲著改變后的Drawable扎即,要不然也不會獲取到,透明度改變后的顏色了况凉。
這個可以從ColorState里面的屬性可以容易的發(fā)現(xiàn)mUseColor谚鄙,這個就是改變后的顏色。
同理BitmapDrawable等一些其他的Drawable也是類似的刁绒,只是相應的ConstantState存儲東西不一樣而已闷营。
至此我們,大概縷清楚了這個緩存的過程。
五.正經的解決辦法
那么這個getDrawable互相影響的問題傻盟,到底怎么解決呢速蕊?難道只能想我剛開始用的方法一樣,新建另外一個資源嗎娘赴。那可能有點低估了Google爸爸了规哲。
在剛才ColorDrawable里面的代碼中有個mutate方法注意到沒,這個方法里面有new ColorState(mColorState)诽表,會把原來的ColorState傳進去大刊,再new一個ColorState眷蜓。
這樣我們只要在設置獨有的屬性前,調用mutate,就不會影響到其他的Drawable了舟奠。
六.總結
問題: drawable在不同的地方胆剧,修改屬性會相互影響骇塘,
原因: android緩存了調用過的drawable的ConstantState汛闸,ConstantState里面保存了drawable的一些屬性
解決方法: 設置獨有的屬性前,調用mutate平痰,就不會影響到其他的Drawable了