DrawableCache遇坑小記

一.問題背景描述

測試妹子:我發(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都有些啥湃崩,這就有點多了,如下圖接箫,

1.jpg

接下來其他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了

?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末汞舱,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子宗雇,更是在濱河造成了極大的恐慌昂芜,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,542評論 6 504
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件赔蒲,死亡現(xiàn)場離奇詭異泌神,居然都是意外死亡,警方通過查閱死者的電腦和手機舞虱,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,822評論 3 394
  • 文/潘曉璐 我一進店門欢际,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人矾兜,你說我怎么就攤上這事损趋。” “怎么了椅寺?”我有些...
    開封第一講書人閱讀 163,912評論 0 354
  • 文/不壞的土叔 我叫張陵浑槽,是天一觀的道長。 經常有香客問我返帕,道長桐玻,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,449評論 1 293
  • 正文 為了忘掉前任溉旋,我火速辦了婚禮畸冲,結果婚禮上,老公的妹妹穿的比我還像新娘观腊。我一直安慰自己邑闲,他們只是感情好,可當我...
    茶點故事閱讀 67,500評論 6 392
  • 文/花漫 我一把揭開白布梧油。 她就那樣靜靜地躺著苫耸,像睡著了一般。 火紅的嫁衣襯著肌膚如雪儡陨。 梳的紋絲不亂的頭發(fā)上褪子,一...
    開封第一講書人閱讀 51,370評論 1 302
  • 那天,我揣著相機與錄音骗村,去河邊找鬼嫌褪。 笑死,一個胖子當著我的面吹牛胚股,可吹牛的內容都是我干的笼痛。 我是一名探鬼主播,決...
    沈念sama閱讀 40,193評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼琅拌,長吁一口氣:“原來是場噩夢啊……” “哼缨伊!你這毒婦竟也來了?” 一聲冷哼從身側響起进宝,我...
    開封第一講書人閱讀 39,074評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎党晋,沒想到半個月后谭胚,有當地人在樹林里發(fā)現(xiàn)了一具尸體,經...
    沈念sama閱讀 45,505評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡未玻,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,722評論 3 335
  • 正文 我和宋清朗相戀三年漏益,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片深胳。...
    茶點故事閱讀 39,841評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡绰疤,死狀恐怖,靈堂內的尸體忽然破棺而出舞终,到底是詐尸還是另有隱情轻庆,我是刑警寧澤,帶...
    沈念sama閱讀 35,569評論 5 345
  • 正文 年R本政府宣布敛劝,位于F島的核電站余爆,受9級特大地震影響,放射性物質發(fā)生泄漏夸盟。R本人自食惡果不足惜蛾方,卻給世界環(huán)境...
    茶點故事閱讀 41,168評論 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧桩砰,春花似錦拓春、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,783評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至煮纵,卻和暖如春懂鸵,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背行疏。 一陣腳步聲響...
    開封第一講書人閱讀 32,918評論 1 269
  • 我被黑心中介騙來泰國打工匆光, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人酿联。 一個月前我還...
    沈念sama閱讀 47,962評論 2 370
  • 正文 我出身青樓终息,卻偏偏與公主長得像,于是被迫代替她去往敵國和親货葬。 傳聞我的和親對象是個殘疾皇子采幌,可洞房花燭夜當晚...
    茶點故事閱讀 44,781評論 2 354

推薦閱讀更多精彩內容

  • 1、Drawable 簡介 Drawable——可簡單理解為可繪制物震桶,表示一些可以繪制在 Canvas 上的對象休傍。...
    牧秦丶閱讀 14,811評論 0 15
  • ¥開啟¥ 【iAPP實現(xiàn)進入界面執(zhí)行逐一顯】 〖2017-08-25 15:22:14〗 《//首先開一個線程,因...
    小菜c閱讀 6,409評論 0 17
  • Drawable在android當中非常常見,每天都會和它打交道柴墩,但很多人卻很少知道Drawable有哪些常用的方...
    想飛的小小小魚閱讀 1,961評論 2 7
  • 7.1 壓縮圖片 一忙厌、基礎知識 1、圖片的格式 jpg:最常見的圖片格式江咳。色彩還原度比較好逢净,可以支持適當壓縮后保持...
    AndroidMaster閱讀 2,515評論 0 13
  • 今天喝了一杯星巴克,在喝的時候就拍不拍照這件事作了一個簡單的思考歼指。 剛上大學那會兒爹土,有個高中同學在qq空間發(fā)了張星...
    柯卡拉閱讀 240評論 0 0