在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對象。