一、定義資源
Aandroid 中的資源從類型的角度來看包括:drawable券腔、layout、字符串拘泞、顏色值纷纫、menu、animation 等陪腌。
資源的定義可以分為兩類:
- 屬性的定義
- 值的定義
二辱魁、儲(chǔ)存資源
Android 和資源有關(guān)的“屬性”包括:
- attr
- styleable
- style
- theme
2.1、styleable 和 attr
attr 表示屬性诗鸭,風(fēng)格樣式的最小單元染簇。
styleable 一般和 attr 聯(lián)合使用,用于定義一些屬性强岸,理論上可以不使用 styleable 而只是使用 attr锻弓,示例代碼如下:
<element
attr name="flower_name" format="string"
attr name="flower_color" format="color|reference"
attr name="flower_types" format="string"
...
</element>
對(duì)于以上定義,當(dāng)解析 XML 時(shí)蝌箍,會(huì)返回一個(gè) AtrributeSet 對(duì)象青灼,該對(duì)象包括了 element 中包含的所有屬性和值暴心。通過使用 R.id.flower_name 作為參數(shù)可以得到相應(yīng)的取值,但是這種方式并不在能體現(xiàn)以上幾個(gè)屬性聯(lián)合在一起的意義杂拨,于是引入了 styleable 的概念酷勺,示例代碼如下:
<declare-styleable name="Flower">
<attr name="flower_name" format="string"/>
<attr name="flower_color" format="color|reference"/>
<attr name="flower_types" format="string">
<enum name="rose" value="Rose"/>
<enum name="tulips" value="Tulips"/>
</attr>
</declare-styleable>
這樣從 AtrributeSet 中獲取這三個(gè)屬性值時(shí),就可以把 R.styleale.Flower 作為參數(shù)扳躬,該參數(shù)實(shí)際會(huì)被 aapt 編譯成為一個(gè) int[] 數(shù)組脆诉,數(shù)組的內(nèi)容就是所包含的 attr 的 id。
2.2贷币、Style
Style 直譯為“風(fēng)格”击胜,它是一系列 Attr 的集合用來定義一個(gè) View 的樣式,比如 height役纹、width偶摔、padding 等,可以理解為是 Attr 屬性集合的具體組合實(shí)現(xiàn)促脉,示例代碼如下:
<style name="Netherlands">
<item name="flower_name">yahoo</item>
<item name="flower_color">@color/Amber</item>
<item name="flower_types">Rose</item>
</style>
以上 XML 文件就實(shí)現(xiàn)了 Flower 中定義的屬性辰斋,并將這些具體的屬性這定義為一個(gè) Style。
2.3瘸味、theme
Theme 直譯為“主題”明肮,而主題本身是一個(gè)抽象化的概念震束,比如 Window 系統(tǒng)中的主題包括了桌面背景、系統(tǒng)圖標(biāo)、字體大小以及按鈕風(fēng)格等嫂粟,在 Android 中酥郭,從代碼角度來看茵瀑,主題包括三個(gè)方面昆咽,其出處和用法如下:
-
在 frameworks/base/core/res/res/values/attrs.xml 中定義了一個(gè)名稱為 Theme 的 styleable:
<declare-styleable name="Theme">
其中包括了幾十項(xiàng)屬性,包括窗口樣式尘奏、字體顏色滩褥、大小、按鈕樣式炫加、進(jìn)度條等瑰煎。這些屬性大部分只能用在 AndroidManifest 文件中的 Application 和 Activity 中,而不能用于普通的 View/ViewGroup 中琢感。
原因很簡(jiǎn)單丢间,attrs 已經(jīng)為 View/ViewGroup 定義了可用的屬性探熔,而 styleable 只是為了方便訪問一些特定的 attr 集合驹针,而把這些 attr 放在一個(gè) styleable 中,而 theme 只是把適用于 Application 和 Activity 的屬性單獨(dú)放到了一起诀艰。
-
在 frameworks/base/core/res/res/values/attrs_manifest.xml 中定義了一個(gè)名稱為 theme 的屬性:
<attr name="theme" format="reference"/>
該屬性只有定義到 AndroidManifest 中才有意義柬甥。進(jìn)一步講饮六,只有定義到 Application 和 Activity 元素內(nèi)部才是有意義的,因?yàn)橹挥性谶@兩個(gè)類中才能讀取 theme 屬性的值苛蒲。theme 的屬性賦值是一個(gè) reference卤橄,其引用類型是 style,即 theme 應(yīng)該賦值到某個(gè) style臂外,而 該 style 中所包含的屬性應(yīng)該是 Application 和 Activity 中可以識(shí)別的屬性值窟扑,也就是名稱為 theme 的 styleable 中定義的屬性。
-
在 frameworks/base/core/res/res/values/themes.xml 中定義了一系列名稱為 theme 的 style:
<style name="Theme"> <item name="isLightTheme">false</item> <item name="colorForeground">@color/bright_foreground_dark</item> <item name="colorForegroundInverse">@color/bright_foreground_dark_inverse</item> <item name="colorBackground">@color/background_dark</item> ... </Style>
這些 style 將作為 Application 和 Activity 中 theme 中的值漏健,而這些 theme 中所包含的屬性屬性名稱全部取自與 styleable name = “Theme” 處嚎货。
三、AttributeSet 與 TypedArray
3.1蔫浆、AttributeSet
AttributeSet 類的位置在 android.util 包中殖属,從該類的位置看,該類與 Framework 內(nèi)核沒有任何直接的聯(lián)系瓦盛,而純粹是一個(gè)輔助類洗显。該類僅僅是為了解析 XML 文件用的。AttributeSet 類的代碼如下(只列出部分方法):
public interface AttributeSet {
public int getAttributeCount();
public String getAttributeName(int index);
public String getAttributeValue(int index);
...
public String getClassAttribute();
public int getIdAttributeResourceValue(int defaultValue);
public int getStyleAttribute();
}
一般 XML 文件有以下形式:
<ElementName
attr_name1="value1"
attr_name2="value2"
attr_name3="value3"
...
</ElementName>
如果使用一般的 XML 解析工具原环,則可以通過類似 getElementById() 等方法獲得屬性的名稱和屬性值挠唆,然而卻沒有在屬性名稱和 attrs.xml 定義的屬性名稱建立任何聯(lián)系。而 AttributeSet 類正是在這之間建立了某中聯(lián)系嘱吗,并提供了一些新的 API 接口损搬,從而可以方便根據(jù) attrs.xml 已有的名稱獲得相應(yīng)的屬性值。
在開發(fā)中柜与,通常并不關(guān)心 AttributeSet 接口如何實(shí)現(xiàn)巧勤,只需要通過該該對(duì)象獲取相應(yīng)的屬性值即可,比如在 TextView 的構(gòu)造函數(shù)中:
public TextView(Context context, AttributeSet attrs) {
super((Context)null, (AttributeSet)null, 0, 0);
throw new RuntimeException("Stub!");
}
AttributeSet 中的 API 可按功能劃分為以下幾類弄匕,假定該 XML 的格式為:
<View class="android.widget.TextView"
android:layout_width="@dimem/general_width"
android:layout_height="wrap_content"
android:text="@string/title"
android:id="@+id/output"
style="@style/Test"
/>
-
第一類颅悉,操作特定屬性,包括以下幾類:
- public String getIdAttribute(): 獲取 id 屬性對(duì)應(yīng)的字符串迁匠,返回值為“@+id/output”剩瓶。
- public String getClassAttribute(): 獲取 class 對(duì)應(yīng)的字符串,返回值為“android.widget.TextView”城丧。
- public String getStyleAttribute(): 獲取 style 對(duì)應(yīng)的字符串延曙,返回值“@style/Test”。
- public int getIdAttributeResourceValue(int defaultValue): 返回 id 屬性對(duì)應(yīng)的 int 值亡哄,此處應(yīng)該是 R.id.output 的值枝缔。
-
第二類,操作通用屬性,包括以下幾類:
public int getAttributeCount(): 獲取屬性的數(shù)目愿卸。
-
public String getAttributeName(int index): 根據(jù)屬性所在的位置返回相應(yīng)的屬性名稱灵临。本例中,相應(yīng)的屬性位置如下:
class=0 layout_width=1 layout_height=2 text=3 id=4 style=5
如果 index 為 1趴荸,則返回 android:layout_width儒溉。
public String getAttributeValue(int index): 根據(jù)位置返回屬性值。本例中发钝,如果 index 為 1顿涣,則返回“@dimem/general_width”。
public String getAttributeValue(String nameSpase, String name):返回值定命名空間酝豪、指定名稱的屬性值园骆,該函數(shù)說明 AttributeSet 允許給一個(gè) XML Element 的屬性中添加多個(gè)命名空間的屬性值。
public int getAttributeNameResource(int index):返回指定位置的屬性 id 值寓调。本例中锌唾,如果 index 為 1,則返回 R.id.layout_width夺英,系統(tǒng)為每一個(gè)屬性(attr)分配了惟一的 id 值晌涕。
-
第三類,獲取特定類型的值痛悯。
-
public XXXType getAttributeXXXTypeValue(int index余黎,XXXType defaultValue);
其中 XXXType 包括 int、unsigned int载萌、boolean惧财、以及 float 類型,使用該方法時(shí)扭仁,必須明確知道某個(gè)位置(index)對(duì)應(yīng)的數(shù)據(jù)類型垮衷。而且該方法僅適用于特定的類型,如果某個(gè)屬性的值為 style 類型乖坠,或是一個(gè) layout 類型搀突,那么返回值將無效。
-
3.2熊泵、TypedArray
TypedArray 是對(duì) AttributeSet 的某種抽象仰迁。
在上面的例子中,對(duì)于 android:layout_width="@dimem/general_width"顽分,如果使用 AttributeSet 只能獲取“@dimem/general_width”字符串徐许,而實(shí)際上該字符串對(duì)應(yīng)了一個(gè) dimem 類型的數(shù)據(jù),因此還要去解析 id 為 general_width 對(duì)應(yīng)的具體的 dimen 的值卒蘸。TypedArray 正是免去了這個(gè)過程雌隅,可以將 AttributeSet 作為參數(shù)來構(gòu)造 TypedArray,TypedArray 提供更加方便的方法來直接獲取該 dimen 的值。
從一個(gè) AttributeSet 構(gòu)造 TypedArray 對(duì)象方法代碼如下:
TypedArray a = context.obtainStyleAttribute(attrs, com.android.internal.R.styleable.XXX, defStyle, 0);
函數(shù) obtainStyleAttribute 的第一個(gè)參數(shù)為一個(gè) AttributeSet 對(duì)象澄步,它包含了一個(gè) XML 元素中所定義的所有屬性冰蘑。第二個(gè)參數(shù)就是前面定義的 styleable和泌,
aapt 會(huì)把 TypedArray 編譯為一個(gè) int[] 數(shù)組村缸,該數(shù)組所包含的內(nèi)容正是通過遍歷 AttributeSet 中的每一個(gè)屬性,然后把值和屬性經(jīng)過重定位武氓,返回一個(gè) TypedArray 對(duì)象梯皿。
下面分析 TypedArray 類的內(nèi)部接口和重要成員變量。
該類的重要成員變量包括:
int[] mData县恕;
-
/package/ TypedValue mValue = new TypedValue(): TypedValue 是一個(gè)數(shù)據(jù)類东羹,其意義是為了保存一個(gè)屬性值,比如 layout_width忠烛、textSize属提、textColor 等,該類中有四個(gè)重要成員變量:
- int type:類型包括 int美尸、boolean冤议、float、String师坎、reference 等恕酸;
- int data:如果 type 是一個(gè) int、boolean胯陋、float蕊温、類型,則 data 包含了具體的數(shù)據(jù)遏乔;
- int referenceId:如果 type 是一個(gè) reference 類型义矛,那么該值為對(duì)應(yīng)的 reference id;
- CharSequence string:如果 type 是一個(gè) String 類型盟萨,則該值為具體的 String症革。
mValue 起到了一個(gè)內(nèi)部緩存的作用。mData 則包含了指定 styleable 中的所有屬性值鸯旁,mData 的長(zhǎng)度為 styleable 中屬性的的個(gè)數(shù) × AssetManager.STYLE_NUM_ENTRIES
(該值為 6)噪矛。也就是說需要 6 個(gè) int 來表示一個(gè)屬性值,以下為 AssetManager 中這 6 個(gè)值的定義:
/*package*/ static final int STYLE_NUM_ENTRIES = 6;
/*package*/ static final int STYLE_TYPE = 0;
/*package*/ static final int STYLE_DATA = 1;
/*package*/ static final int STYLE_ASSET_COOKIE = 2;
/*package*/ static final int STYLE_RESOURCE_ID = 3;
/* Offset within typed data array for native changingConfigurations. */
static final int STYLE_CHANGING_CONFIGURATIONS = 4;
/*package*/ static final int STYLE_DENSITY = 5;
在以上常用值中铺罢,常用的包含了三個(gè):
- STYLE_TYPE(0):包含了值得類型艇挨;
- STYLE_DATA(1):包含了特定的值;
- STYLE_RESOURCE_ID(3):如果 STYLE_TYPE 為一個(gè) reference 類型韭赘,該值對(duì)應(yīng)了相應(yīng)的 resource id缩滨;
下面來看 TypedArray 如何使用以上幾個(gè)成員變量。當(dāng)在 XML 中引用某個(gè)資源時(shí),比如:
android:background=“@drawable/bkg”
該引用對(duì)應(yīng)的元素一般是某個(gè) View/ViewGroup脉漏,View/ViewGroup 的構(gòu)造函數(shù)中一般會(huì)通過函數(shù) obtainStyleAttributes() 方法返回一個(gè) TypedArray 對(duì)象苞冯,然后再調(diào)用該對(duì)象中的相應(yīng) getDrawable() 方法。
下面以 getString() 和 getDrawable() 為例說明 TypedArray 內(nèi)部接口的工作原理侧巨。getString() 代碼如下:
@Nullable
public String getString(@StyleableRes int index) {
if (mRecycled) {
throw new RuntimeException("Cannot make calls to a recycled instance!");
}
index *= AssetManager.STYLE_NUM_ENTRIES;
final int[] data = mData;
final int type = data[index+AssetManager.STYLE_TYPE];
if (type == TypedValue.TYPE_NULL) {
return null;
} else if (type == TypedValue.TYPE_STRING) {
return loadStringValueAt(index).toString();
}
final TypedValue v = mValue;
if (getValueAt(index, v)) {
final CharSequence cs = v.coerceToString();
return cs != null ? cs.toString() : null;
}
// We already checked for TYPE_NULL. This should never happen.
throw new RuntimeException("getString of bad type: 0x" + Integer.toHexString(type));
}
首先傳遞進(jìn)來的 index 必須先乘以 6舅锄,因?yàn)槊恳粋€(gè)屬性值都站用連續(xù)的 6 個(gè) int 值。每一個(gè) styleable 都將被 aapt 編譯為一個(gè) int[] 數(shù)組司忱,數(shù)組中的內(nèi)容為 styleable 所包含的每一個(gè)屬性(attr)對(duì)應(yīng)的 id 的值皇忿,在調(diào)用 getString() 時(shí),其參數(shù) index 是該屬性在 styleable 中的位置坦仍。當(dāng)定義了一個(gè) styleable 時(shí)鳍烁, aapt 同時(shí)生成了 attr 在 styleable 中的位置,比如 TextView 是一個(gè) styleable繁扎,其中包含的 attr 有 textSize幔荒,aapt 會(huì)自動(dòng)生成一個(gè) TextView_textSize 常量。該常量的名稱格式是固定的梳玫,其形式為“styleable 名稱 _attr 名稱”爹梁。
接著從 mData 中取出值得類型,即 index+AssetManager.STYLE_TYPE 處汽纠,然后判斷類型卫键,如果為 STYLE_NULL,說明無該屬性值虱朵,返回 null莉炉,如果類型為 STYLE_STRING,則調(diào)用 loadStringValueAt() 方法找到 String 并返回碴犬。
接著看 loadStringValueAt()絮宁,代碼如下:
private CharSequence loadStringValueAt(int index) {
final int[] data = mData;
final int cookie = data[index+AssetManager.STYLE_ASSET_COOKIE];
if (cookie < 0) {
if (mXml != null) {
return mXml.getPooledString(
data[index+AssetManager.STYLE_DATA]);
}
return null;
}
return mAssets.getPooledStringForCookie(cookie, data[index+AssetManager.STYLE_DATA]);
}
該方法先從 mData 中取出 cookie,如果 cookie 小于 0 并且 mXml 存在服协,則會(huì)從 mXml(用于解析 XML 文件) 中得到 String 的值绍昂。mXml 內(nèi)部有一個(gè) String 池,通過 mData 的 STYLE_DATA 為索引可以得到哦相應(yīng)的 String 值偿荷。如果 cookie 大于 0窘游,那么 cookie 將作為 mResource.mAssets的內(nèi)部方法 getPooledString() 的參數(shù)。cookie 將作為 mAssets 內(nèi)部 mXml 的索引跳纳,而 Data 的 STYLE_DATA 將作為字符串索引忍饰。
下面看 getDrawable() 的流程:
@Nullable
public Drawable getDrawable(@StyleableRes int index) {
return getDrawableForDensity(index, 0);
}
/**
* Version of {@link #getDrawable(int)} that accepts an override density.
* @hide
*/
@Nullable
public Drawable getDrawableForDensity(@StyleableRes int index, int density) {
if (mRecycled) {
throw new RuntimeException("Cannot make calls to a recycled instance!");
}
final TypedValue value = mValue;
if (getValueAt(index*AssetManager.STYLE_NUM_ENTRIES, value)) {
if (value.type == TypedValue.TYPE_ATTRIBUTE) {
throw new UnsupportedOperationException(
"Failed to resolve attribute at index " + index + ": " + value);
}
if (density > 0) {
// If the density is overridden, the value in the TypedArray will not reflect this.
// Do a separate lookup of the resourceId with the density override.
mResources.getValueForDensity(value.resourceId, density, value, true);
}
return mResources.loadDrawable(value, value.resourceId, density, mTheme);
}
return null;
}
四、獲取 Resources 的過程
獲取 Resources 有兩種方式寺庄,一是通過 Context艾蓝,而是通過 PackageManager力崇。
4.1、通過 Context 獲取
在開發(fā)中赢织,通常是通過 getResources().getXXX() 方法來獲取 XML 中的指定資源亮靴,比如 getDrawable()、getString()于置、getBoolean() 等茧吊。
首先看 getResources() 方法,該方法是 Context 的成員函數(shù)俱两,一般在 Activity 或是 Service 中調(diào)用饱狂,因?yàn)?Activity 和 Service 本質(zhì)上是一個(gè) Context曹步,而真正實(shí)現(xiàn) Context 接口的是 ContextImpl 類宪彩。
ContextImpl 是在 ActivityThread 中創(chuàng)建的,它的 getResources() 方法就是返回其內(nèi)部的 mResources 變量讲婚,對(duì)該變量的賦值是在創(chuàng)建 ContextImpl 對(duì)象時(shí)進(jìn)行初始化的尿孔,代碼如下(API 27):
private ContextImpl(ContextImpl container, ActivityThread mainThread,
LoadedApk packageInfo, IBinder activityToken, UserHandle user, int flags,
Display display, Configuration overrideConfiguration, int createDisplayWithId) {
mOuterContext = this;
...
mPackageInfo = packageInfo;
mResourcesManager = ResourcesManager.getInstance();
...
Resources resources = packageInfo.getResources(mainThread);
if (resources != null) {
if (displayId != Display.DEFAULT_DISPLAY
|| overrideConfiguration != null
|| (compatInfo != null && compatInfo.applicationScale
!= resources.getCompatibilityInfo().applicationScale)) {
if (container != null) {
// This is a nested Context, so it can't be a base Activity context.
// Just create a regular Resources object associated with the Activity.
resources = mResourcesManager.getResources(
activityToken,
packageInfo.getResDir(),
packageInfo.getSplitResDirs(),
packageInfo.getOverlayDirs(),
packageInfo.getApplicationInfo().sharedLibraryFiles,
displayId,
overrideConfiguration,
compatInfo,
packageInfo.getClassLoader());
} else {
// This is not a nested Context, so it must be the root Activity context.
// All other nested Contexts will inherit the configuration set here.
resources = mResourcesManager.createBaseActivityResources(
activityToken,
packageInfo.getResDir(),
packageInfo.getSplitResDirs(),
packageInfo.getOverlayDirs(),
packageInfo.getApplicationInfo().sharedLibraryFiles,
displayId,
overrideConfiguration,
compatInfo,
packageInfo.getClassLoader());
}
}
}
mResources = resources;
...
可以看出 mResources 是調(diào)用 mPackageInfo 的 getResources() 方法進(jìn)行賦值的。一個(gè)用應(yīng)用中的多個(gè) ContextImpl 對(duì)象實(shí)際上共享了同一個(gè) PackageInfo 對(duì)象筹麸,這就意味著活合,多個(gè) ContextImpl 對(duì)象中的 mResources 變量實(shí)際上是同一個(gè)。
packageInfo(類型為:LoadedApk)的 getResources() 方法如下:
public Resources getResources(ActivityThread mainThread) {
if(this.mResources == null) {
this.mResources = mainThread.getTopLevelResources(this.mResDir, this.mSplitResDirs, this.mOverlayDirs, this.mApplicationInfo.sharedLibraryFiles, 0, (Configuration)null, this);
}
return this.mResources;
}
ActivityThread 的 getTopLevelResources 的邏輯是得到本應(yīng)用的對(duì)應(yīng)的資源對(duì)象物赶。代碼如下:
Resources getTopLevelResources(String resDir, String[] splitResDirs, String[] overlayDirs, String[] libDirs, int displayId, Configuration overrideConfiguration, LoadedApk pkgInfo) {
return this.mResourcesManager.getTopLevelResources(resDir, splitResDirs, overlayDirs, libDirs, displayId, overrideConfiguration, pkgInfo.getCompatibilityInfo(), (IBinder)null);
// ResourcesManager#getTopLevelResources
public Resources getTopLevelResources(String resDir, String[] splitResDirs, String[] overlayDirs, String[] libDirs, int displayId, Configuration overrideConfiguration, CompatibilityInfo compatInfo, IBinder token) {
float scale = compatInfo.applicationScale;
ResourcesKey key = new ResourcesKey(resDir, displayId, overrideConfiguration, scale, token);
Resources r;
synchronized(this) {
WeakReference<Resources> wr = (WeakReference)this.mActiveResources.get(key);
r = wr != null?(Resources)wr.get():null;
if(r != null && r.getAssets().isUpToDate()) {
return r;
}
}
AssetManager assets = new AssetManager();
if(resDir != null && assets.addAssetPath(resDir) == 0) {
return null;
} else {
int len$;
int i$;
String libDir;
String[] arr$;
if(splitResDirs != null) {
arr$ = splitResDirs;
len$ = splitResDirs.length;
for(i$ = 0; i$ < len$; ++i$) {
libDir = arr$[i$];
if(assets.addAssetPath(libDir) == 0) {
return null;
}
}
}
if(overlayDirs != null) {
arr$ = overlayDirs;
len$ = overlayDirs.length;
for(i$ = 0; i$ < len$; ++i$) {
libDir = arr$[i$];
assets.addOverlayPath(libDir);
}
}
if(libDirs != null) {
arr$ = libDirs;
len$ = libDirs.length;
for(i$ = 0; i$ < len$; ++i$) {
libDir = arr$[i$];
if(assets.addAssetPath(libDir) == 0) {
Slog.w("ResourcesManager", "Asset path '" + libDir + "' does not exist or contains no resources.");
}
}
}
DisplayMetrics dm = this.getDisplayMetricsLocked(displayId);
boolean isDefaultDisplay = displayId == 0;
boolean hasOverrideConfig = key.hasOverrideConfiguration();
Configuration config;
if(isDefaultDisplay && !hasOverrideConfig) {
config = this.getConfiguration();
} else {
config = new Configuration(this.getConfiguration());
if(!isDefaultDisplay) {
this.applyNonDefaultDisplayMetricsToConfigurationLocked(dm, config);
}
if(hasOverrideConfig) {
config.updateFrom(key.mOverrideConfiguration);
}
}
// 重點(diǎn)白指,使用構(gòu)造方法創(chuàng)建 Resources
r = new Resources(assets, dm, config, compatInfo, token);
synchronized(this) {
WeakReference<Resources> wr = (WeakReference)this.mActiveResources.get(key);
Resources existing = wr != null?(Resources)wr.get():null;
if(existing != null && existing.getAssets().isUpToDate()) {
r.getAssets().close();
return existing;
} else {
this.mActiveResources.put(key, new WeakReference(r));
return r;
}
}
}
}
// Resources
@Deprecated
public Resources(AssetManager assets, DisplayMetrics metrics, Configuration config) {
this(null);
mResourcesImpl = new ResourcesImpl(assets, metrics, config, new DisplayAdjustments());
}
// ResourcesImpl
public ResourcesImpl(@NonNull AssetManager assets, @Nullable DisplayMetrics metrics,
@Nullable Configuration config, @NonNull DisplayAdjustments displayAdjustments) {
mAssets = assets;
mMetrics.setToDefaults();
mDisplayAdjustments = displayAdjustments;
updateConfiguration(config, metrics, displayAdjustments.getCompatibilityInfo());
mAssets.ensureStringBlocks();
}
變量 mActiveResources 對(duì)象內(nèi)部保存了該應(yīng)用所使用到的所有 Resources 對(duì)象,其類型為:
ArrayMap<ResourcesKey, WeakReference<Resources>>
參數(shù) ResourcesKey 是一個(gè)數(shù)據(jù)類酵紫,其構(gòu)造方式如下:
ResourcesKey key = new ResourcesKey(resDir, displayId, overrideConfiguration, scale, token);
重點(diǎn)看第一個(gè)參數(shù)告嘲,resDir 變量為源文件路徑,實(shí)際就是 APK 程序所在路徑奖地,比如可以是:/data/app/com.serah.android.Kaiyan.apk橄唬,該 APK 會(huì)對(duì)應(yīng) /data/dalvik-cache 目錄下的:data@app@com.serah.android.Kaiyan.apk@classes.dex 文件。
所以参歹,如果一個(gè)應(yīng)用沒有訪問該程序外的其他資源仰楚,那么 mActiveResources 中只含有一個(gè) Resources 對(duì)象。如果 mActiveResources 中沒有包含所要的 Resources 那么犬庇,就重新建立一個(gè) Resources 并添加到 mActiveResources 中僧界。
可以發(fā)現(xiàn) Resources 構(gòu)造函數(shù)需要一個(gè) AssetManager 對(duì)象,AssetManager 負(fù)責(zé)訪問 res 下的所有資源(不只是 res/assets 目錄下資源)臭挽,AssetManager 中幾個(gè)關(guān)鍵函數(shù)都是 native 的捂襟。以上代碼 assets.addAssetPath(resDir) 函數(shù)非常關(guān)鍵,它為所創(chuàng)建的 AssetManager 對(duì)象添加資源路徑埋哟,剩下的事就 AssetManager 內(nèi)部完成笆豁,內(nèi)部會(huì)從指定的路徑下加載資源文件郎汪,AssetManager 構(gòu)造函數(shù)如下:
public AssetManager() {
synchronized(this) {
this.init(false);
ensureSystemAssets();
}
}
構(gòu)造方法中的來兩個(gè)關(guān)鍵函數(shù) init() 和 ensureSystemAssets() 都是 native 實(shí)現(xiàn)的。
init() 用于初始化 AssetManager 內(nèi)部的環(huán)境變量闯狱,初始化過程的一個(gè)關(guān)鍵任務(wù)就是把 Framework 中的資源路徑添加到 AssetManager 中煞赢,該 native 代碼如下(android_content_AssetManager_init.cpp):
static void android_content_AssetManager_init(JNIEnv* env, jobject clazz, jboolean isSystem)
{
if (isSystem) {
verifySystemIdmaps();
}
AssetManager* am = new AssetManager();
if (am == NULL) {
jniThrowException(env, "java/lang/OutOfMemoryError", "");
return;
}
am->addDefaultAssets();
ALOGV("Created AssetManager %p for Java object %p\n", am, clazz);
env->SetLongField(clazz, gAssetManagerOffsets.mObject, reinterpret_cast<jlong>(am));
}
以上代碼首先創(chuàng)建一個(gè) AssetManager 類,這是一個(gè) C++ 類哄孤,然后調(diào)用 am->addDefaultAssets() 將 Framework 的資源文件添加到這個(gè) AssetManager 對(duì)象的路徑中照筑。最后調(diào)用 SetLongField() 方法將 C++ 創(chuàng)建的 AssetManager 對(duì)象引用保存到 java 端的 mObject 變量中,該變量可以在 java 端的 AssetManager 類中找到瘦陈, 其類型為 int凝危。
addDefaultAssets() 代碼如下:
bool AssetManager::addDefaultAssets()
{
const char* root = getenv("ANDROID_ROOT");
LOG_ALWAYS_FATAL_IF(root == NULL, "ANDROID_ROOT not set");
String8 path(root);
path.appendPath(kSystemAssets);
return addAssetPath(path, NULL, false /* appAsLib */, true /* isSystemAsset */);
}
該函數(shù)首先獲取 Android 根目錄, getenv() 是一個(gè) Linux 系統(tǒng)調(diào)用晨逝,用戶同樣可以使用以下終端命令來獲榷昴:
keyid:tools keyid$ ./adb shell
root@android:/ # echo $ANDROID_ROOT
/system
root@android:/ #
獲取根目錄后,在與 kSystemAssets 路徑進(jìn)行組合捉貌,該變量定義如下:
static const char* kSystemAssets = "framework/framework-res.apk";
所以最中獲得的文件路徑為:/system/framework/framework-res.apk支鸡, 這正是 Framework 對(duì)應(yīng)的資源文件。
分析完 init() 后趁窃,接著看 ensureSystemAssets() 方法牧挣,該方法實(shí)際在 Framework 啟動(dòng)時(shí)調(diào)用,因?yàn)?mSystem 是一個(gè) static 變量醒陆,該變量在 Zygote 啟動(dòng)時(shí)已經(jīng)被賦值瀑构。
private static void ensureSystemAssets() {
Object var0 = sSync;
synchronized(sSync) {
if(sSystem == null) {
AssetManager system = new AssetManager(true);
system.makeStringBlocks((StringBlock[])null);
sSystem = system;
}
}
}
因?yàn)閼?yīng)用程序中 Resources 對(duì)象內(nèi)部的 AssetManager 對(duì)象除了包含應(yīng)用程序本身的資源文件路徑外,還包含了 Framework 的資源路徑刨摩,這就是為什么僅使用本地 Resources 就能訪問系統(tǒng)的資源的原因寺晌。
在 AssetManager.cpp 文件中,當(dāng)使用 getXXX(int id) 訪問資源時(shí)码邻,如果 id 小于 0x1000 0000 時(shí)折剃,AssetManager 會(huì)認(rèn)為是訪問系統(tǒng)資源。因?yàn)?aapt 在對(duì)系統(tǒng)資源進(jìn)行編譯時(shí)像屋,所有資源 id 都被編譯為小于該值的一個(gè) int 值怕犁, 而當(dāng)訪問應(yīng)用程序資源時(shí),id 值都會(huì)大于 0x7000 0000己莺。
創(chuàng)建好 Resources 對(duì)象后奏甫,就把該對(duì)象緩存到 mActiveResources 中,方便以后繼續(xù)使用凌受。以上就是訪問 Resources 的整個(gè)流程阵子。
4.2、通過 PackageManager 獲取
該方法用于訪問其他程序中的資源胜蛉,使用 PackageManager 獲取資源的代碼如下:
try {
PackageManager pm = mContext.getPackageManager();
pm.getResourcesForApplication("com.serah.android.Kaiyan");
} catch (PackageManager.NameNotFoundException e) {
e.printStackTrace();
}
和其他 Manager 一樣挠进,PackageManager 負(fù)責(zé)和遠(yuǎn)程 PackageManagerService 進(jìn)行通信色乾,獲取PackageManager 代碼如下(ContextImpl中實(shí)現(xiàn)):
@Override
public PackageManager getPackageManager() {
if (mPackageManager != null) {
return mPackageManager;
}
IPackageManager pm = ActivityThread.getPackageManager();
if (pm != null) {
// Doesn't matter if we make more than one instance.
return (mPackageManager = new ApplicationPackageManager(this, pm));
}
return null;
}
PackageManager 本身是一個(gè)抽象類,其實(shí)現(xiàn)類是 ApplicationPackageManager领突,該類的構(gòu)造函數(shù)包含了遠(yuǎn)程服務(wù)的一個(gè)引用暖璧,即 IPackageManager,該對(duì)象是通過 getPackageManager() 靜態(tài)方法的到的君旦,這種獲取遠(yuǎn)程服務(wù)的方法和大多數(shù)獲取遠(yuǎn)程服務(wù)的方法類似澎办,代碼如下(ActivityThread#getPackageManager()):
public static IPackageManager getPackageManager() {
if (sPackageManager != null) {
//Slog.v("PackageManager", "returning cur default = " + sPackageManager);
return sPackageManager;
}
IBinder b = ServiceManager.getService("package");
//Slog.v("PackageManager", "default service binder = " + b);
sPackageManager = IPackageManager.Stub.asInterface(b);
//Slog.v("PackageManager", "default service = " + sPackageManager);
return sPackageManager;
}
即得到一個(gè)本地代理,獲得代理后調(diào)用 getResourcesForApplication() 方法金砍,該方法代碼在 ApplicationPackageManager 中實(shí)現(xiàn)局蚀,代碼如下:
@Override
public Resources getResourcesForApplication(@NonNull ApplicationInfo app)
throws NameNotFoundException {
if (app.packageName.equals("system")) {
return mContext.mMainThread.getSystemUiContext().getResources();
}
final boolean sameUid = (app.uid == Process.myUid());
final Resources r = mContext.mMainThread.getTopLevelResources(
sameUid ? app.sourceDir : app.publicSourceDir,
sameUid ? app.splitSourceDirs : app.splitPublicSourceDirs,
app.resourceDirs, app.sharedLibraryFiles, Display.DEFAULT_DISPLAY,
mContext.mPackageInfo);
if (r != null) {
return r;
}
throw new NameNotFoundException("Unable to open " + app.publicSourceDir);
}
以上代碼調(diào)用了 mMainThread.getTopLevelResources,這和從 Contex 中獲取 Resource 過程一致恕稠。
需要注意的是這里的參數(shù)琅绅,其含義是:如果目標(biāo)資源程序和當(dāng)前程序是同一個(gè) uid 那么就使用目標(biāo)程序的 sourceDir 作為路徑,否則就使用目標(biāo)程序的publicSourceDir 目錄谱俭,該目錄可以在 AndroidManifest.xml 中指定奉件。在多數(shù)情況下宵蛀,目標(biāo)程序和當(dāng)前程序都不屬于一個(gè) uid昆著,因此,多為 publicSourceDir术陶,而該值在默認(rèn)情況下和 sourceDir 的值相同凑懂。
當(dāng)進(jìn)入 mMainThread.getTopLevelResources() 方法后,全局 ActivityThread 對(duì)象就會(huì)在 mActiveResources 中保存一個(gè)新的 Resources 對(duì)象梧宫,其鍵值對(duì)應(yīng)目標(biāo)應(yīng)用程序的包名接谨。
五、Framework 資源
了解了 Resources 的獲取流程后塘匣,本節(jié)將介紹系統(tǒng)資源的加載脓豪、讀取、添加過程忌卤,至于系統(tǒng)資源是如何被編譯的扫夜,將在后續(xù)文章中進(jìn)行分析(aapt 編譯
framework/base/core/res/res 目錄下資源,并生成 framework-res.apk)驰徊。
5.1笤闯、加載和讀取
系統(tǒng)資源是在 Zygote 進(jìn)程啟動(dòng)時(shí)被加載的,并且只有當(dāng)加載了系統(tǒng)資源之后才開始啟動(dòng)其他應(yīng)用進(jìn)程棍厂,從而實(shí)現(xiàn)其他應(yīng)用進(jìn)程共享系統(tǒng)資的目標(biāo)颗味。該過程源碼在 com/android/internal/os/ZygoteInit.java 的 main() 函數(shù),核心代碼如下:
public static void main(String argv[]) {
ZygoteHooks.startZygoteNoThreadCreation();
try {
...
// 加載系統(tǒng)資源
preload();
...
// 啟動(dòng)系統(tǒng)進(jìn)程
if (startSystemServer) {
startSystemServer(abiList, socketName);
}
runSelectLoop(abiList);
closeServerSocket();
} catch (MethodAndArgsCaller caller) {
caller.run();
} catch (Throwable ex) {
Log.e(TAG, "Zygote died with exception", ex);
closeServerSocket();
throw ex;
}
}
以上函數(shù)內(nèi)部過程可以分為三步:
- 加載系統(tǒng)資源牺弹;
- 調(diào)用 startSystemServer() 啟動(dòng)系統(tǒng)進(jìn)程浦马;
- 調(diào)用 runSelectLoop() 開始監(jiān)聽 Socket时呀,并啟動(dòng)指定的應(yīng)用進(jìn)程。
本文主要分析第一步晶默,即加載系統(tǒng)資源退唠,該過程具體是通過 preloadResources() 實(shí)現(xiàn)的,該函數(shù)代碼如下:
private static void preloadResources() {
final VMRuntime runtime = VMRuntime.getRuntime();
try {
mResources = Resources.getSystem();
mResources.startPreloading();
if (PRELOAD_RESOURCES) {
Log.i(TAG, "Preloading resources...");
long startTime = SystemClock.uptimeMillis();
TypedArray ar = mResources.obtainTypedArray(
com.android.internal.R.array.preloaded_drawables);
int N = preloadDrawables(ar);
ar.recycle();
Log.i(TAG, "...preloaded " + N + " resources in "
+ (SystemClock.uptimeMillis()-startTime) + "ms.");
startTime = SystemClock.uptimeMillis();
ar = mResources.obtainTypedArray(
com.android.internal.R.array.preloaded_color_state_lists);
N = preloadColorStateLists(ar);
ar.recycle();
Log.i(TAG, "...preloaded " + N + " resources in "
+ (SystemClock.uptimeMillis()-startTime) + "ms.");
if (mResources.getBoolean(
com.android.internal.R.bool.config_freeformWindowManagement)) {
startTime = SystemClock.uptimeMillis();
ar = mResources.obtainTypedArray(
com.android.internal.R.array.preloaded_freeform_multi_window_drawables);
N = preloadDrawables(ar);
ar.recycle();
Log.i(TAG, "...preloaded " + N + " resource in "
+ (SystemClock.uptimeMillis() - startTime) + "ms.");
}
}
mResources.finishPreloading();
} catch (RuntimeException e) {
Log.w(TAG, "Failure preloading resources", e);
}
}
以上代碼有兩個(gè)關(guān)鍵點(diǎn):
- 第一點(diǎn)荤胁,創(chuàng)建 Resources 對(duì)象 mResources 是通過 Resources.getSystem() 函數(shù)瞧预。該函數(shù)返回的 Resources 對(duì)象只能訪問 Framework 中定義的系統(tǒng)資源。
getSystem() 函數(shù)內(nèi)部會(huì)調(diào)用一個(gè) private 類型的 Resources 構(gòu)造函數(shù)仅政,該函數(shù)內(nèi)部調(diào)用 AssetManager 類的靜態(tài)方法 AssetManager.getSystem() 為變量 mAssets 賦值垢油,從而保證了 Resources 類內(nèi)部 mSystem 變量對(duì)應(yīng)為系統(tǒng)資源,mSystem 是 static 類型的圆丹。
從這里可以看出滩愁,zygote 中創(chuàng)建 Resources 對(duì)象和普通應(yīng)用程序的不同,前者使用靜態(tài)的 getSystem()方法辫封,而后者使用帶有參數(shù)的 Resources 構(gòu)造函數(shù)創(chuàng)建硝枉,參數(shù)間接包含了應(yīng)用程序資源文件的路徑信息。
- 第二點(diǎn)倦微,有了包含系統(tǒng)資源的 Resources 后妻味,接下來調(diào)用兩個(gè)重要函數(shù):preloadDrawables 和 preloadColorStateLists,裝載需要的“預(yù)裝載”資源欣福。
首先看 preloadDrawables():
private static int preloadDrawables(TypedArray ar) {
int N = ar.length();
for (int i=0; i<N; i++) {
int id = ar.getResourceId(i, 0);
if (false) {
Log.v(TAG, "Preloading resource #" + Integer.toHexString(id));
}
if (id != 0) {
if (mResources.getDrawable(id, null) == null) {
throw new IllegalArgumentException(
"Unable to find preloaded drawable resource #0x"
+ Integer.toHexString(id)
+ " (" + ar.getString(i) + ")");
}
}
}
return N;
}
該函數(shù)的參數(shù)是一個(gè) TypedArray 對(duì)象责球,其來源是 res/values/arrays.xml 中定義的一個(gè) array 數(shù)組資源,名稱為 preloaded_drawable拓劝,以下是該資源的代碼片段:
<array name="preloaded_drawables">
<item>@drawable/ab_share_pack_material</item>
<item>@drawable/ab_solid_shadow_material</item>
<item>@drawable/action_bar_item_background_material</item>
<item>@drawable/activated_background_material</item>
......
</array>
因此雏逾,需要想要讓所有的應(yīng)用進(jìn)程共享預(yù)裝的資源,則需要在該文件中生命資源的名稱郑临。
接下來看 preloadColorStateLists:
private static int preloadColorStateLists(TypedArray ar) {
int N = ar.length();
for (int i=0; i<N; i++) {
int id = ar.getResourceId(i, 0);
if (false) {
Log.v(TAG, "Preloading resource #" + Integer.toHexString(id));
}
if (id != 0) {
if (mResources.getColorStateList(id, null) == null) {
throw new IllegalArgumentException(
"Unable to find preloaded color resource #0x"
+ Integer.toHexString(id)
+ " (" + ar.getString(i) + ")");
}
}
}
return N;
}
該函數(shù)的參數(shù)同樣是一個(gè) TypedArray, 來源同樣是來自 res/values/arrays.xml 中定義的一個(gè)數(shù)組資源栖博,名稱為 preloaded_color_state_lists,該資源代碼片段如下所示:
<array name="preloaded_color_state_lists">
<item>@color/primary_text_dark</item>
<item>@color/primary_text_dark_disable_only</item>
<item>@color/primary_text_dark_nodisable</item>
.....
</array>
以上便是加載資源的來源厢洞,接著仇让,在 Resources 類中相關(guān)資源讀取函數(shù)中則需要將讀到的資源緩存起來,為了這個(gè)目地犀变,Resources 中定義了四個(gè)靜態(tài)變量妹孙,
如下所示:
private static final LongSparseArray<Drawable.ConstantState>[] sPreloadedDrawables;
private static final LongSparseArray<Drawable.ConstantState> sPreloadedColorDrawables
= new LongSparseArray<>();
private static final LongSparseArray<android.content.res.ConstantState<ComplexColor>>
sPreloadedComplexColors = new LongSparseArray<>();
private static boolean sPreloaded;
前三個(gè)都是類表變量,并且是 static 的获枝,正是由于變量類型為 static 所以導(dǎo)致 Resources 類在被應(yīng)用進(jìn)程創(chuàng)建新的對(duì)象時(shí)蠢正,保存了 zygote 進(jìn)程中所預(yù)裝載的資源。
由于 zygote 進(jìn)程在從 framework-res.apk 中裝載資源的實(shí)現(xiàn)方式和普通進(jìn)程基本相同省店,可以通過 sPreloaded 變量區(qū)分是從 zygote 中還是普通進(jìn)程調(diào)用嚣崭,該變量在 startPreloading() 中被設(shè)置為 true笨触,在 finishPreloading() 中被設(shè)置為 false,該過程參見 ZygoteInit 的 preloadResources() 函數(shù)雹舀。
5.2芦劣、添加
以上加載的資源僅僅是加載 res/values/arrays.xml 中的資源,而 Framework 中的資源遠(yuǎn)不止于此说榆,以設(shè)計(jì)者角度來看虚吟,對(duì)于那些非“預(yù)裝載”的系統(tǒng)資源不會(huì)被添加到靜態(tài)列表中,在這種情況下签财,多個(gè)應(yīng)用進(jìn)程如果需要一個(gè)非預(yù)裝載的資源串慰,則會(huì)在各自的進(jìn)程中保持一個(gè)資源的緩沖。
至于是否是“預(yù)裝載”的唱蒸,僅僅取決于該資源始否在 res/values/arrays.xml中邦鲫。
系統(tǒng)資源按照被公開的方式分為公開的和私有的,總的來說只要是放在 res 目錄下的資源都會(huì)被 aapt 編譯神汹,而所謂的私有資源是指僅能在 Framework 內(nèi)部訪問的資源庆捺,公開資源是指使用 SDK 的應(yīng)用程序也能訪問的系統(tǒng)資源。
假設(shè)要添加一個(gè)字符串資源:
<string name="cus_str">Custom string</string>
那么屁魏,可以直接把以上代碼添加到 res/values/arrays.xml 中滔以,然后重新編譯 Framework,編譯完畢后蚁堤,在 com.android.internal.R.java 文件中醉者,就會(huì)包含該字符串的聲明,然后在 Framework 的源碼中就可以直接引用該資源披诗。
需要注意的是,cus_str 被存放到了 com.android.internal.R 文件中立磁,而不是 android.R 文件中呈队,這正是公開資源和私有資源的區(qū)別所在,使用 SDK 開發(fā)普通應(yīng)用時(shí)唱歧,當(dāng)要引用系統(tǒng)資源時(shí)宪摧,只能引用 android.R 文件,該文件的內(nèi)容可以被認(rèn)為是 com.android.internal.R 的一個(gè)子集颅崩。
如前所述几于,res 目錄下的資源總會(huì)被 aapt 編譯到一個(gè) R.java 文件中,這個(gè)文件就是 com.android.internal.R 文件沿后,而 android.R 文件則是來源于
res/values/public.xml 中定義的資源沿彭。
res/values/public.xml 為所有需要公開到 SDK 中的資源進(jìn)行 id 的預(yù)先定義,這些 id 值在不同的 Android 版本中保持一致尖滚,從而保證了 Android 版本的資源兼容性喉刘。
想要將前面的 cus_str 公開到 SDK 中瞧柔,需要在 public.xml 文件中聲明該字符串,為新資源指定 id 時(shí)必須要考慮來兩個(gè)個(gè)問題:
- 不能與已有的 id 沖突;
- 盡量避免與未來的 id 沖突睦裳。
本例中造锅,就可以給 public.xml 文件添加以下代碼:
<public type="string" name="cus_str" id="0x0104f000" />
id 的含義是,01 代表這是一個(gè) Framework 資源廉邑,04 代表著是一個(gè) string 類型的資源哥蔚,f000 是該資源的編號(hào),之所以從 f000 開始蛛蒙,是因?yàn)?Framework 內(nèi)部的資源是從 0 開始的肺素,防止以后遞增時(shí)與我們自己定義的資源值沖突。
六宇驾、android 生成資源 id
先看一下 apk 的打包流程倍靡,Android Developer 官方流程,下面對(duì)官方流程做了系統(tǒng)的總結(jié)课舍。
下圖的是官網(wǎng)對(duì)于 Android 編譯打包流程的介紹:
虛線方框是打包 APK 的操作塌西,現(xiàn)在開發(fā) Android 都是使用的 Android Studio 基于 gradle 來構(gòu)建項(xiàng)目,所有打包操作都是執(zhí)行 gradle 腳本來完成筝尾,gradle 編譯腳本具有強(qiáng)大的功能捡需,可以在里面完成多渠道,多版本筹淫,不同版本使用不同代碼站辉,不同的資源,編譯后的文件重命名损姜,混淆簽名驗(yàn)證等等配置饰剥,雖然都是基于AndroidSdk 的 platform-tools 的文件夾下面的工具來完成的,但是有了 gradle 這個(gè)配置文件摧阅,這樣就便捷了汰蓉。
以下是一張 APK 打包詳細(xì)步驟流程圖:
具體步驟:
- 打包資源文件,生成 R.java 文件棒卷;
- 處理 aidl 文件顾孽,生成相應(yīng)的 .java 文件;
- 編譯工程源碼比规,生成相應(yīng)的 class 文件若厚;
- 轉(zhuǎn)換所有的 class 文件,生成 classes.dex 文件蜒什;
- 打包生成 apk测秸;
- 對(duì) apk 文件進(jìn)行簽名;
- 對(duì)簽名后的 apk 進(jìn)行對(duì)齊處理。
本文只分析 aapt 對(duì)資源文件的編譯過程乞封。
6.1做裙、aapt
資源 id 的生成過程主要是調(diào)用 aapt 源碼目錄下的 Resouce.cpp 的 buildResources() 函數(shù),該函數(shù)首先檢查 AndroidManifest.xml 的合法性肃晚,然后對(duì) res 目錄下的資源目錄進(jìn)行處理锚贱,處理函數(shù)為 makeFileResource(),處理的內(nèi)容包括資源文件名的合法性檢查关串,向資源表 table 添加條目等拧廊。處理完后調(diào)用 compileResourceFile() 函數(shù)編譯 res 與 asserts 目錄下的資源并生 resource.arsc 文件,compileResourceFile() 函數(shù)位于 appt 源碼目錄的 ResourceTable.cpp 文件中晋修,該函數(shù)最后會(huì)調(diào)用 parseAndAddEntry() 函數(shù)生成 R.java 文件吧碾,完成資源編譯后,接下來調(diào)用 compileXmlfile() 函數(shù)對(duì) res 目錄的子目錄下的 xml 文件進(jìn)行編譯墓卦,這樣處理過的 xml 文件就簡(jiǎn)單的被"加密"了倦春,最后將所有資源與編譯生成的 resource.arsc 文件以及"加密"過的 AndroidManifest.xml 打包壓縮成 resources.ap_ 文件。
上面涉及的源碼代碼位置在:
- buildResources() 函數(shù)在:Resouce.cpp 1144 行落剪;
- makeFileResource() 函數(shù)在:Resouce.cpp 297 行睁本;
- compileResourceFile() 函數(shù)在:ResourceTable.cpp 782 行;
- parseAndAddEntry() 函數(shù)在:ResourceTable.cpp 690 行忠怖;
- compileXmlfile() 函數(shù)在:ResourceTable.cpp 42/57/73 行呢堰;
aapt 編譯后的輸出文件包括:
- resources.ap_ 文件
- R.java 文件
打包資源的工具 aapt,大部分文本格式的 XML 資源文件會(huì)被編譯成二進(jìn)制格式的 XML 資源文件凡泣,除了 assets 和 res/raw 資源被原封不動(dòng)地打包進(jìn) APK 之外枉疼,其他資源都會(huì)被編譯或者處理。
注意鞋拟,除了 assets 和 res/raw 資源被原封不動(dòng)地打包進(jìn) APK 之外骂维,其它的資源都會(huì)被編譯或者處理。除了 assets 資源之外严卖,其他的資源都會(huì)被賦予一個(gè)資源 id席舍。
resources.arsc 是清單文件,但是 resources.arsc 跟 R.java 區(qū)別還是非常大的哮笆,R.java 里面的只是 id 列表,并且里面的 id 值不重復(fù)融求。但是 drawable-xdpi 或者 drawable-xxdpi 這些不同分辨率的文件夾存放的圖片和名稱和 id 是一樣的呕诉,在運(yùn)行的時(shí)候就需要 resources.arsc 這個(gè)文件了斥黑,resources.arsc 里面會(huì)對(duì)所有的資源 id 進(jìn)行組裝,在 apk 運(yùn)行是會(huì)根據(jù)設(shè)備的情況來采用不同的資源项阴。resource.arsc 文件的作用就是通過一樣的 id,根據(jù)不同的配置索引到最佳的資源現(xiàn)在 UI 中。
可以這樣理解:R.java 是我們?cè)趯懘a時(shí)候引用的 res 資源的 id 表环揽,resources.arsc 是程序在運(yùn)行時(shí)候用到的資源表略荡。R.java 是給程序員讀的,resources.arsc 是給機(jī)器讀的歉胶。
大體情況如下:
6.2汛兜、資源 id 生成規(guī)則
資源 id 是一個(gè) 32bit的數(shù)字,格式是 PPTTNNNN通今,其中 PP 代表資源所屬的包(package)粥谬,TT 代表資源的類型(type),NNNN 代表這個(gè)類型下面的資源的名稱辫塌。
對(duì)于應(yīng)用程序的資源來說漏策,PP 的取值是 0×7f。TT 和 NNNN 的取值是由 aapt 工具隨意指定的–基本上每一種新的資源類型的數(shù)字都是從上一個(gè)數(shù)字累加的(從1開始)臼氨;而每一個(gè)新的資源條目也是從數(shù)字 1 開始向上累加的掺喻。
所以如果我們的這幾個(gè)資源文件按照下面的順序排列,aapt 會(huì)依次處理:
<code>layout/main.xml </code>
<code>drawable/icon.xml </code>
<code>layout/listitem.xml</code>
按照順序储矩,第一個(gè)資源的類型是”layout” 所以指定 TT==1感耙,這個(gè)類型下面的第一個(gè)資源是”main”,所以指定 NNNN==1 椰苟,最后這個(gè)資源就是 0x7f010001抑月。
第二個(gè)資源類型是”drawable”,所以指定 TT==2舆蝴,這個(gè)類型下的”icon” 指定 NNNN==1谦絮,所以最終的資源 ID 是 0x7f020001。