1. 程式人生 > >Android換膚系列 Resources

Android換膚系列 Resources

    在Android中Resources類用於獲取應用資源(如:圖片、顏色、文字),並自動根據地區、語言、解析度、螢幕方向等獲取對應的資源。以下api doc上對Resources類的介紹:這裡寫圖片描述
從上面介紹中我們可以知道,Resources基於AssetManager,資源請求是通過AssetManager類來完成,而java層的AssetManager最終則是通過C++層AssetManager類來完成arsc檔案解析,資源的載入,機型適配等。深入介紹移步Android資源管理框架(Asset Manager)簡要介紹和學習計劃。Resources類中提供了一些public的api介面用於獲取應用資源,如:getColor、 getDrawable、 getString,通過這些介面我們能獲取指定資源顏色、背景圖片、文字等,接下來將詳細分析getColor和getDrawable的過程。

    在Activity中我們可以使用getResources().getColor()獲取指定資源color,下圖getColor的過程。(基於android 4.4原始碼)
這裡寫圖片描述

Step 1. Resources.getColor

    public int getColor(int id) throws NotFoundException {
        TypedValue value;
        synchronized (mAccessLock) {
            value = mTmpValue;
            if (value == null
) { value = new TypedValue(); } getValue(id, value, true); if (value.type >= TypedValue.TYPE_FIRST_INT && value.type <= TypedValue.TYPE_LAST_INT) { mTmpValue = value; return value.data; } else
if (value.type != TypedValue.TYPE_STRING) { throw new NotFoundException( "Resource ID #0x" + Integer.toHexString(id) + " type #0x" + Integer.toHexString(value.type) + " is not valid"); } mTmpValue = null; } ColorStateList csl = loadColorStateList(value, id); synchronized (mAccessLock) { if (mTmpValue == null) { mTmpValue = value; } } return csl.getDefaultColor(); }

    方法裡面首先判斷快取mTmpValue是否為空,為空的話就創建出一個新的TypedValue,mTmpValue變數的併發訪問則通過mAccessLock來保護。接著呼叫另一個方法getValue。

Step 2. Resources.getValue

   public void getValue(int id, TypedValue outValue, boolean resolveRefs)
            throws NotFoundException {
        boolean found = mAssets.getResourceValue(id, 0, outValue, resolveRefs);
        if (found) {
            return;
        }
        throw new NotFoundException("Resource ID #0x" + Integer.toHexString(id));
   }

    getValue方法內部則是通過呼叫AssetManager的getResourceValue方法來獲取資源id對應的color,返回值資源項的資訊儲存在outValue中。對應getResourceValue的底層實現這裡不再深究,Android資源管理框架(Asset Manager)簡要介紹和學習計劃 有詳細介紹。

Step 3. Resources.loadColorStateList
    根據在上一步驟中取得的color資源進行判斷,如果是普通的color型別(xml檔案中定義的顏色,這裡包括定義在color.xml和在xml中直接使用色值),則直接返回給呼叫者;否則通過loadColorStateList來載入定義在xml檔案中的selector資源選擇器。

/*package*/ ColorStateList loadColorStateList(TypedValue value, int id)
            throws NotFoundException {
        ...
        ColorStateList csl;

        csl = getCachedColorStateList(key);
        if (csl != null) {
            return csl;
        }

        csl = sPreloadedColorStateLists.get(key);
        if (csl != null) {
            return csl;
        }

        String file = value.string.toString();
        if (file.endsWith(".xml")) {
            try {
                XmlResourceParser rp = loadXmlResourceParser(
                        file, id, value.assetCookie, "colorstatelist"); 
                csl = ColorStateList.createFromXml(this, rp);
                rp.close();
            } catch (Exception e) {
                throw rnf;
            }
        } 
     ....
        if (csl != null) {
            if (mPreloading) {
                if (verifyPreloadConfig(value.changingConfigurations, 0, value.resourceId,
                        "color")) {
                    sPreloadedColorStateLists.put(key, csl);
                }
            } else {
                synchronized (mAccessLock) {
                    mColorStateListCache.put(key, new WeakReference<ColorStateList>(csl));
                }
            }
        }
        return csl;
    }

    方法首先從快取中判斷資源是否載入過,若載入過則直接返回。接著判斷索引檔案中資源id對應的資源項的檔名稱是否以.xml結尾,不是的話則直接丟擲NotFoundException異常此次查詢結束;確定是xml檔案後,通過loadXmlResourceParser方法將xml檔案載入進來,接著呼叫ColorStateList.createFromXml解析出ColorStateList物件。

Step 4. ColorStateList.createFromXml

public static ColorStateList createFromXml(Resources r, XmlPullParser parser)
            throws XmlPullParserException, IOException {
        AttributeSet attrs = Xml.asAttributeSet(parser);
        int type;
        while ((type=parser.next()) != XmlPullParser.START_TAG
                   && type != XmlPullParser.END_DOCUMENT) {
        }
        if (type != XmlPullParser.START_TAG) {
            throw new XmlPullParserException("No start tag found");
        }
        return createFromXmlInner(r, parser, attrs);
    }

    createFromXml方法就只是簡單的遍歷獲取第一個xml節點,然後接著呼叫createFromXmlInner方法

    private static ColorStateList createFromXmlInner(Resources r, XmlPullParser parser, AttributeSet attrs) throws XmlPullParserException, IOException {
        ColorStateList colorStateList;
        final String name = parser.getName();
        if (name.equals("selector")) {
            colorStateList = new ColorStateList();
        } else {
            throw new XmlPullParserException(
                parser.getPositionDescription() + ": invalid drawable tag " + name);
        }
        colorStateList.inflate(r, parser, attrs);
        return colorStateList;
    }

    createFromXmlInner方法中首先判斷節點型別是否為”selector”,否則異常結束,接著建立ColorStateList物件,呼叫物件的inflate方法解析xml檔案。

private void inflate(Resources r, XmlPullParser parser, AttributeSet attrs)
        throws XmlPullParserException, IOException {
        ...
        while ((type=parser.next()) != XmlPullParser.END_DOCUMENT
               && ((depth=parser.getDepth()) >= innerDepth
                   || type != XmlPullParser.END_TAG)) {
           ....
            final int numAttrs = attrs.getAttributeCount();
            int[] stateSpec = new int[numAttrs];
            for (i = 0; i < numAttrs; i++) {
                final int stateResId = attrs.getAttributeNameResource(i);
                if (stateResId == 0) break;
                if (stateResId == com.android.internal.R.attr.color) {
                    colorRes = attrs.getAttributeResourceValue(i, 0);
                    if (colorRes == 0) {
                        color = attrs.getAttributeIntValue(i, color);
                        haveColor = true;
                    }
                } 
                ....
            }
            ...
            if (colorRes != 0) {
                color = r.getColor(colorRes);
            } 
            ....
        }
       ....
    }

    inflate首先遍歷所有的item節點, 然後判斷item節點裡面的color屬性是值型別(直接在xml檔案中使用”#RRGGBB”)還是引用型別(通過@color引用color.xm檔案內容)。如果是值型別,通過getAttributeIntValue方法獲取xml檔案中指定的color;否則需要根據xml檔案中引用資源的id再次呼叫Resources.getColor獲取資源。從這裡也可以看出雖然color selector支援巢狀,但是引用xml資源的item項獲取的是預設的color。

    接下來是getDrawable的過程
這裡寫圖片描述
從圖中可以看出getDrawable和getColor前幾個步驟是相似的,都是是先通過資源id從索引檔案中獲取到相應資源項的資訊,步驟1和2參考getColor。

Step 3. Resources.loadDrawable

/*package*/ Drawable loadDrawable(TypedValue value, int id)
            throws NotFoundException {
        boolean isColorDrawable = false;
        if (value.type >= TypedValue.TYPE_FIRST_COLOR_INT &&
                value.type <= TypedValue.TYPE_LAST_COLOR_INT) {
            isColorDrawable = true;
        }
        ....
       Drawable.ConstantState cs;
        if (isColorDrawable) {
            cs = sPreloadedColorDrawables.get(key);
        } else {
            cs = sPreloadedDrawables[mConfiguration.getLayoutDirection()].get(key);
        }
        if (cs != null) {
            dr = cs.newDrawable(this);
        } else {
            if (isColorDrawable) {
                dr = new ColorDrawable(value.data);
            }
            if (dr == null) {
                String file = value.string.toString();
                if (DEBUG_LOAD) Log.v(TAG, "Loading drawable for cookie "
                        + value.assetCookie + ": " + file);

                if (file.endsWith(".xml")) {
                    Trace.traceBegin(Trace.TRACE_TAG_RESOURCES, file);
                    try {
                        XmlResourceParser rp = loadXmlResourceParser(
                                file, id, value.assetCookie, "drawable");
                        dr = Drawable.createFromXml(this, rp);
                        rp.close();
                    } catch (Exception e) {
                    }
                } else {
                    try {
                        InputStream is = mAssets.openNonAsset(
                                value.assetCookie, file, AssetManager.ACCESS_STREAMING);
                        dr = Drawable.createFromResourceStream(this, value, is,
                                file, null);
                        is.close();
                    } catch (Exception e) {
                    }
                }
            }
        }

        if (dr != null) {
            dr.setChangingConfigurations(value.changingConfigurations);
            cs = dr.getConstantState();
            if (cs != null) {
                if (mPreloading) {
                ....
                } else {
                    synchronized (mAccessLock) {
                        if (isColorDrawable) {
                            mColorDrawableCache.put(key, new WeakReference<Drawable.ConstantState>(cs));
                        } else {
                            mDrawableCache.put(key, new WeakReference<Drawable.ConstantState>(cs));
                        }
                    }
                }
            }
        }
        return dr;
    }

    方法裡面首先判斷資源的型別是否為color,程式碼裡面根據isColorDrawable變數來區分兩種資源的快取:color儲存在mColorDrawableCache、drawable儲存在mDrawableCache。這裡假設id對應的資源在快取裡面不存在,如果資源的型別為color則直接建立一個新的ColorDrawable物件,資源內容就是上一個步驟getValue從索引檔案解析得到的儲存在valueType的data中。如果是檔案型別則判斷是否為xml檔案,這裡通過Drawable.createFromXml來解析資源xml檔案。Drawable.createFromXml 方法和geColor的ColorStateList.createFromXml是相同的。

Step 4. Drawable.createFromXmlInner

    public static Drawable createFromXmlInner(Resources r, XmlPullParser parser, AttributeSet attrs)
    throws XmlPullParserException, IOException {
         Drawable drawable;
        final String name = parser.getName();
        if (name.equals("selector")) {
            drawable = new StateListDrawable();
        } else if (name.equals("level-list")) {
          ....
        } else {
            throw new XmlPullParserException(parser.getPositionDescription() +
                    ": invalid drawable tag " + name);
        }
        drawable.inflate(r, parser, attrs);
        return drawable;

    createFromXmlInner方法內通過判斷item的名稱選擇不同的Drawable實現,有StateListDrawable,LevelListDrawable,ColorDrawable,BitmapDrawable等14中資源型別,這裡以StateListDrawable為例。當選擇Drawable的具體實現後,接著呼叫inflate方法來解析xml檔案。

    public void inflate(Resources r, XmlPullParser parser,
            AttributeSet attrs)
            throws XmlPullParserException, IOException {
        TypedArray a = r.obtainAttributes(attrs,
                com.android.internal.R.styleable.StateListDrawable);
        ....
        while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
                && ((depth = parser.getDepth()) >= innerDepth
                || type != XmlPullParser.END_TAG)) {
           ....
            for (i = 0; i < numAttrs; i++) {
                final int stateResId = attrs.getAttributeNameResource(i);
                if (stateResId == 0) break;
                if (stateResId == com.android.internal.R.attr.drawable) {
                    drawableRes = attrs.getAttributeResourceValue(i, 0);
                } 
                ....
            }
            Drawable dr;
            if (drawableRes != 0) {
                dr = r.getDrawable(drawableRes);
            } 
            ....
        }
    }

    inflate首先遍歷所有的item節點,從drawable屬性中讀取引用的資源id存入drawableRes變數,接著再呼叫Resources的getDrawable方法獲取drawableRes資源id對應的drawable資源。

    從上面我們知道不管是getColor還是getDrawable方法,都是先呼叫getValue方法從資源索引檔案中獲取資源id對應的資源項的資訊,接著再根據資源型別呼叫不同的drawable進行資源物件的建立。這裡比較特殊的是color資源,在呼叫getValue方法的後,如果是color資源,color的值是儲存在outValue變數中。如果方法呼叫的是getColor則返回outValue中data的值,如果是getDrawable則通過outValue中data的值建立一個ColorDrawable物件。