Android的優點之一是它幾乎能運行在任何尺寸的設備上,爲了能讓同一個apk在不一樣設備上正常運行,Android設計了一套資源管理系統來完成目標。java
Android
並非簡單地將UI佈局和圖片進行擴大和縮小來匹配不一樣配置的設備,而是經過複雜的資源定義方式來保證每種設備均可以有對應的資源文件,從而讓用戶體驗最佳。android
Android
應用適應不一樣設備的方法是儘可能爲每種類型的設備提供一套資源。理論上雖然能夠這樣作,但實際上卻行不通,咱們只能爲常見的幾種設備類型提供完整的資源,不然應用的佔用空間會膨脹到沒法接受的程度算法
大部分來自官網,應用資源概覽數組
Android
中經常使用的單位:緩存
dpi
:屏幕密度,即每英寸的像素點數。160 dpi
表示屏幕每英寸包含160個像素點px
:像素,1 px
表示一個物理的像素點。px
不被推薦使用,可是若是須要經過像素點來控制UI
,也可使用。dp
:dp
是一個虛擬像素單位,1 dp
約等於中密度
屏幕(160dpi
;「基準」密度)上的1像素
。對於其餘每一個密度,Android
會將此值轉換爲相應的實際像素數。sp
:sp
多用於表示字體大小上。sp
和dp
概念類似。區別是Android
在系統配置中定義一個scale
值,sp
和dp
的換算關係是sp=dp*scale
,一般scale
值爲1
。(官方說法:默認狀況下,sp
單位與dp
大小相同,但它會根據用戶的首選文本大小來調整大小。)Android
把屏幕尺寸歸爲4類:markdown
small
:尺寸相似於低密度 VGA 屏幕的屏幕。小屏幕的最小布局尺寸約爲 320x426 dp
。例如,QVGA 低密度屏幕和 VGA 高密度屏幕。normal
:尺寸相似於中等密度 HVGA 屏幕的屏幕。標準屏幕的最小布局尺寸約爲 320x470 dp。例如,WQVGA 低密度屏幕、HVGA 中等密度屏幕、WVGA 高密度屏幕。large
:尺寸相似於中等密度 VGA 屏幕的屏幕。大屏幕的最小布局尺寸約爲 480x640 dp。例如,VGA 和 WVGA 中等密度屏幕。xlarge
:明顯大於傳統中等密度 HVGA 屏幕的屏幕。超大屏幕的最小布局尺寸約爲 720x960 dp。在大多數狀況下,屏幕超大的設備體積太大,不能放進口袋,最多見的是平板式設備。此項爲 API 級別 9 中的新增配置。Android
把屏幕密度(dpi
)分爲:cookie
ldpi
:低密度屏幕;約爲 120dpi。mdpi
:中等密度(傳統 HVGA)屏幕;約爲 160dpi。hdpi
:高密度屏幕;約爲 240dpi。xhdpi
:超高密度屏幕;約爲 320dpi。此項爲 API 級別 8 中的新增配置xxhdpi
:絕高密度屏幕;約爲 480dpi。此項爲 API 級別 16 中的新增配置xxxhdpi
:極高密度屏幕使用(僅限啓動器圖標);約爲 640dpi。此項爲 API 級別 18 中的新增配置nodpi
:可用於您不但願爲匹配設備密度而進行縮放的位圖資源。tvdpi
:密度介於 mdpi 和 hdpi 之間的屏幕;約爲 213dpi。此限定符並不是指「基本」密度的屏幕。它主要用於電視,且大多數應用都不使用該密度 — 大多數應用只會使用 mdpi 和 hdpi 資源,並且系統將根據須要對這些資源進行縮放。anydpi
:此限定符適合全部屏幕密度,其優先級高於其餘限定符。這很是適用於矢量可繪製對象
。此項爲 API 級別 21 中的新增配置對於下面的Layout
資源定義:數據結構
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical">
</LinearLayout>
複製代碼
上面的定義中有各類各樣的符號,例如LinearLayout
、layout_width
、orientation
等,這些符號在哪裏定義的呢?語法規則優點什麼呢?app
Android
的資源系統並不想看上去那麼簡單,Android
利用了xml
定義了一套完整的資源語言,咱們來具體看下框架
在Framework
資源目錄frameworks/base/core/res/res/values
的目錄下,有一個attrs.xml
文件,在這個文件裏,Android
定義了資源的屬性值,以LinearLayout
爲例,相關的定義以下:
<declare-styleable name="LinearLayout">
<attr name="orientation" />
<attr name="gravity" />
<attr name="baselineAligned" format="boolean" />
<attr name="baselineAlignedChildIndex" format="integer" min="0"/>
<attr name="weightSum" format="float" />
<attr name="measureWithLargestChild" format="boolean" />
<attr name="divider" />
<attr name="showDividers">
<flag name="none" value="0" />
<flag name="beginning" value="1" />
<flag name="middle" value="2" />
<flag name="end" value="4" />
</attr>
<attr name="dividerPadding" format="dimension" />
</declare-styleable>
複製代碼
從文件中不難看出,LinearLayout
中所使用的各類屬性正是在這裏定義的。不但定義了屬性的名稱,還有屬性值的格式也作了定義
對於UI的基本元素widget而言,最重要的是得到預先定義好的各類屬性值。因而在attrs.xml
文件中,經過declare-styleable
的方式定義了一套屬性的集合。咱們再看下LinearLayout
類的構造方法:
public LinearLayout(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
......
final TypedArray a = context.obtainStyledAttributes(
attrs, com.android.internal.R.styleable.LinearLayout, defStyleAttr, defStyleRes);
int index = a.getInt(com.android.internal.R.styleable.LinearLayout_orientation, -1);
if (index >= 0) {
setOrientation(index);
}
......
a.recycle();
}
複製代碼
LinearLayout
調用方法obtainStyledAttributes()
來建立一個屬性集合a.getInt()
用來獲取屬性
com.android.internal.R.styleable.LinearLayout
,正是attrs.xml
文件中定義的屬性集合名稱-1
,若是在編寫layout文件時必須給每一個widget的全部屬性都賦值,這會變得很繁瑣,所以在讀取每一個屬性時,代碼中都會給出一個缺省值LinearLayout
的構造方法中有一個defStyleAttr
和defStyleRes
用來指定使用的style
,style
就是一些預約義的屬性值,例如:
<style name="Widget.Button"> <item name="background">@drawable/btn_default</item> <item name="focusable">true</item> <item name="clickable">true</item> <item name="textAppearance">?attr/textAppearanceSmallInverse</item> <item name="textColor">@color/primary_text_light</item> <item name="gravity">center_vertical|center_horizontal</item> </style>
複製代碼
這段xml
定義了一個名爲Widget.Button
的style
。使用style
的方式是在layout定義中加入下面的語句:
style="@style/Widget.Button"
複製代碼
對於style
的命名,爲何要用.
分割呢?Android
用這種方式表示一種繼承關係。Widget.Button
的含義是當前style
繼承了Widget
的全部屬性值
除了經過名字來表示繼承關係外,還能夠經過<style/>
標籤的parent
屬性來指定,如:
<style name="Widget.PopupMenu" parent="Widget.ListPopupWindow">
</style>
複製代碼
theme
就是全部UI
屬性的集合。做爲基礎定義的Theme
自己是一個很龐大的style
,而其餘theme
基本上都是經過Theme
派生出來的,咱們看下frameworks/base/core/res/res/values/themes.xml
文件定義:
<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> ...... </style>
<!-- Variant of {@link #Theme} with no title bar -->
<style name="Theme.NoTitleBar"> <item name="windowNoTitle">true</item> </style>
複製代碼
在Android應用的源碼目錄下,一般有兩個和資源相關的目錄:
assets
和res
目錄。這兩個目錄下的文件都會被打包進APK
文件中
Android
規定各種資源存放在res/
目錄,目錄中支持的資源目錄表以下:
目錄 | 資源類型 |
---|---|
animator |
用於定義屬性動畫的 XML 文件。 |
anim |
用於定義漸變更畫的 XML 文件。(屬性動畫也可保存在此目錄中,但爲了區分這兩種類型,屬性動畫首選 animator/ 目錄。) |
color | 用於定義顏色狀態列表的 XML 文件 |
drawable |
位圖文件(.png、.9.png、.jpg、.gif)或編譯爲如下可繪製對象資源子類型的 XML 文件:位圖文件、九宮格(可調整大小的位圖)、狀態列表、形狀、動畫可繪製對象、其餘可繪製對象 |
mipmap |
適用於不一樣啓動器圖標密度的可繪製對象文件 |
layout |
用於定義用戶界面佈局的 XML 文件 |
menu |
用於定義應用菜單(如選項菜單、上下文菜單或子菜單)的 XML 文件 |
raw |
需以原始形式保存的任意文件。如要使用原始 InputStream 打開這些資源,請使用資源 ID (即 R.raw.filename )調用 Resources.openRawResource() 。可是,如需訪問原始文件名和文件層次結構,則能夠考慮將某些資源保存在 assets/ 目錄(而非 res/raw/ )下。assets/ 中的文件沒有資源 ID ,所以您只能使用 AssetManager 讀取這些文件。 |
values |
包含字符串、整型數和顏色等簡單值的 XML 文件。 |
xml |
可在運行時經過調用 Resources.getXML() 讀取的任意 XML 文件。各類 XML 配置文件(如可搜索配置)都必須保存在此處。 |
font |
帶有擴展名的字體文件(如 .ttf 、.otf 或 .ttc ),或包含 <font-family> 元素的 XML 文件。 |
對於上面的animator
、anim
、color
、drawable
、layout
、menu
、raw
、values
、xml
的9種目錄下存放的是缺省資源
,每種資源均可以有候選資源
,候選資源
的存放格式相似<resources_name>-<qualifier>
的目錄下:
<resources_name>
就是前面的這9種缺省資源
的目錄名<qualifier>
是一些限定符的組合,用來區分候選資源類型。assets
目錄下保存的文件不能經過存取資源的方式在代碼中訪問到。訪問assets
目錄下文件的方式更像是打開一個文件,例如:
AssetManager am = Context.getAssets();
InputStream is = am.open(filePath);
複製代碼
assets
目錄下能夠再建目錄,沒有限制,目錄下存放的文件在編譯過程當中不會被改動,會被原封不動的打包進APK
中。
res
目錄下有個raw
目錄,放置在該目錄下的文件也不會被Android
改動,可是raw
目錄下不能在建立子目錄。
訪問raw
目錄下資源的方式和訪問其餘資源是一致的:
InputStream is = getResources().openRawResource(R.id.filename);
複製代碼
同時,raw
目錄下的資源也能像其它資源同樣有備選資源,可以被overlay
目錄中的資源覆蓋掉,這也是assets
目錄下的文件不具有的特性。
上面咱們瞭解到Android
中的資源是如此的紛繁複雜,那麼
Android
是如何加載、管理資源的呢?Android
應用可使用的資源有3中來源:本apk、系統Framework、其餘apk,Android
如何統一管理控制呢?apk
間的資源包如何共享呢?關於這幾個問題的答案,咱們須要從原理上來尋找!
Resources
類的做用
Android
資源的裝載是經過Resources
類來完成的。
還記的
Zygote
進程的預加載
麼?
就是經過Resources.getSystem().startPreloading();
來實現的,咱們來看看Resources
這個類到底作了什麼
咱們先看下核心的成員變量:
class Resources{
// Resources的實例對象
static Resources mSystem = null;
// 真正實現類ResourcesImpl的實例對象
private ResourcesImpl mResourcesImpl;
}
class ResourcesImpl{
// 預加載相關資源
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<>();
// Drawable 和 ColorStateList 的緩存
private final DrawableCache mDrawableCache = new DrawableCache();
private final DrawableCache mColorDrawableCache = new DrawableCache();
// XML文件的緩存 XML_BLOCK_CACHE_SIZE=4,寫死的
private int mLastCachedXmlBlockIndex = -1;
private final int[] mCachedXmlBlockCookies = new int[XML_BLOCK_CACHE_SIZE];
private final String[] mCachedXmlBlockFiles = new String[XML_BLOCK_CACHE_SIZE];
private final XmlBlock[] mCachedXmlBlocks = new XmlBlock[XML_BLOCK_CACHE_SIZE];
// AssetManager實例的
final AssetManager mAssets;
}
複製代碼
上面的代碼中,Resources
類有兩個重要的變量:
Resources mSystem
:
Resources
類的實例對象,用來管理系統資源ResourcesImpl mResourcesImpl
9.0
版本的源碼中,Resources
類具體業務交給了ResourcesImpl
來實現咱們下面具體來看下
mSystem
的初始化mSystem
是一個引用Resources
類自身實例的靜態變量。咱們看下mSystem
變量是如何初始化的:
public static Resources getSystem() {
synchronized (sSync) {
Resources ret = mSystem;
if (ret == null) {
ret = new Resources();
mSystem = ret;
}
return ret;
}
}
複製代碼
靜態方法getSystem
在mSystem
爲空的狀況下會建立一個Resources
對象的實例。咱們看下這個無參構造:
/** * class:Resources.java * Only for creating the System resources. */
private Resources() {
mClassLoader = ClassLoader.getSystemClassLoader();
final DisplayMetrics metrics = new DisplayMetrics();
metrics.setToDefaults();
final Configuration config = new Configuration();
config.setToDefaults();
mResourcesImpl = new ResourcesImpl(AssetManager.getSystem(), metrics, config,
new DisplayAdjustments());
}
/** * class:ResourcesImpl.java */
public ResourcesImpl(@NonNull AssetManager assets, @Nullable DisplayMetrics metrics, @Nullable Configuration config, @NonNull DisplayAdjustments displayAdjustments) {
mAssets = assets;
mMetrics.setToDefaults();
mDisplayAdjustments = displayAdjustments;
mConfiguration.setToDefaults();
updateConfiguration(config, metrics, displayAdjustments.getCompatibilityInfo());
}
複製代碼
這個兩個構造方法比較簡單,咱們須要重點關注的是
Resource
類私有構造方法的註釋:Only for creating the System resources.
ResourcesImpl
實例時傳入的AssetManager
對象,是經過靜態方法AssetManager.getSystem()
來獲取的
AssetManager
類哈咱們接下來就來找找Resource.getSystem()
方法最先是在什麼地方調用的?
你們在frameworks
目錄下grep
一下就會發現,最先的函數調用其實也就是在Zygote
進程初始化時:
/** * preloadResources 函數調用關係展現 */
class ZygoteInit{
public static void main(String argv[]) {
preload();
}
static void preload(...) {
preloadResources();
}
private static void preloadResources() {
mResources = Resources.getSystem();
mResources.startPreloading();
......
preloadDrawables(ar);
......
preloadColorStateLists(ar);
......
mResources.finishPreloading();
}
private static int preloadDrawables() {
......
mResources.getDrawable(id, null);
......
}
private static int preloadColorStateLists() {
......
mResources.getColorStateList(id, null);
......
}
}
複製代碼
由此,在Zygote
進程初始化時mSystem
就被賦值了。咱們知道,全部的應用程序都是從Zygote
進程fork
而來的,這意味着全部進程中的mSystem
引用的Resource
實例在整個Android
系統中是共享的,這部分其實也就是Framework
的資源
getDrawable
的過程在Zygote
的preloadResources
方法中獲得系統的Resources
實例後,會調用這個實例的getDrawable
和getColorStateList
方法去加載系統的Drawable
資源和ColorStateList
資源。
以getDrawable(id,null)
方法爲例,咱們來看下具體幹了啥:
public Drawable getDrawable(@DrawableRes int id, @Nullable Theme theme){
// 請注意此處傳入的 density=0
return getDrawableForDensity(id, 0, theme);
}
public Drawable getDrawableForDensity(...) {
return mResourcesImpl.loadDrawable(this, value, id, density, theme);
}
複製代碼
最後仍是調用了具體實現類ResourcesImpl
的loadDrawable
方法,咱們來看下總體流程:
Drawable loadDrawable(@NonNull Resources wrapper, @NonNull TypedValue value, int id, int density, @Nullable Resources.Theme theme) throws NotFoundException {
// getDrawable 傳入的參數density = 0
// 因此對於Zygote來講,useCache始終爲true
final boolean useCache = density == 0 || value.density == mMetrics.densityDpi;
try {
final boolean isColorDrawable;
final DrawableCache caches;
final long key;
// 判斷要加載的 Drawable 類型,普通資源仍是Color
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;
}
// 非預加載階段纔會走到這裏
// 檢查要加載的資源是否已經存在於cache中
// 有的話直接返回,沒有找到繼續檢查預加載相關的cache
if (!mPreloading && useCache) {
final Drawable cachedDrawable = caches.getInstance(key, wrapper, theme);
if (cachedDrawable != null) {
......
return cachedDrawable;
}
}
......
// 檢查預加載相關的cache
// ConstantState這是一個抽象類
// 每一個Drawable的子類都要實現它,能夠用來生成對應的Drawable實例
// 很經典的一個類
final Drawable.ConstantState cs;
if (isColorDrawable) {
cs = sPreloadedColorDrawables.get(key);
} else {
cs = sPreloadedDrawables[mConfiguration.getLayoutDirection()].get(key);
}
......
Drawable dr;
boolean needsNewDrawableAfterCache = false;
if (cs != null) {
// 在預加載cache中找到資源
// 生成對應的Drawable實例
dr = cs.newDrawable(wrapper);
} else if (isColorDrawable) {
// 預加載cache中沒有找到資源
// 直接建立Color類的資源,不是咱們此次分析的重點哈
dr = new ColorDrawable(value.data);
} else {
// 預加載cache中沒有找到資源
// 經過 loadDrawableForCookie 從XML中加載資源
dr = loadDrawableForCookie(wrapper, value, id, density);
}
......
// 對Drawable實例進行簡單的配置工做
if (dr != null) {
dr.setChangingConfigurations(value.changingConfigurations);
if (useCache) {
// 經過 cacheDrawable 進行資源緩存
cacheDrawable(value, isColorDrawable, caches, theme, canApplyTheme, key, dr);
......
}
}
return dr;
} catch (Exception e) {
}
}
複製代碼
從上面代碼看,當緩存中沒有找到須要的Drawable
資源,會經過loadDrawableForCookie
來加載資源,最後根據須要把資源緩存到cache
中。
先來看下loadDrawableForCookie
的核心代碼以下:
if (file.endsWith(".xml")) {
// 加載XML類型的drawable資源
final XmlResourceParser rp = loadXmlResourceParser(
file, id, value.assetCookie, "drawable");
dr = Drawable.createFromXmlForDensity(wrapper, rp, density, null);
rp.close();
} else {
// 加載圖片類型的drawable資源
final InputStream is = mAssets.openNonAsset(
value.assetCookie, file, AssetManager.ACCESS_STREAMING);
AssetInputStream ais = (AssetInputStream) is;
dr = decodeImageDrawable(ais, wrapper, value);
}
return dr;
複製代碼
資源裝載分紅了xml
和image
兩種:
xml
:經過loadXmlResourceParser()
來加載,流程主要分爲兩部分:
// First see if this block is in our cache.
final int num = cachedXmlBlockFiles.length;
for (int i = 0; i < num; i++) {
if (cachedXmlBlockCookies[i] == assetCookie
&& cachedXmlBlockFiles[i] != null
&& cachedXmlBlockFiles[i].equals(file)) {
return cachedXmlBlocks[i].newParser();
}
}
複製代碼
AssetManager
來裝載文件並緩存// Not in the cache, create a new block and put it at
// the next slot in the cache.
final XmlBlock block = mAssets.openXmlBlockAsset(assetCookie, file);
if (block != null) {
final int pos = (mLastCachedXmlBlockIndex + 1) % num;
mLastCachedXmlBlockIndex = pos;
final XmlBlock oldBlock = cachedXmlBlocks[pos];
if (oldBlock != null) {
oldBlock.close();
}
cachedXmlBlockCookies[pos] = assetCookie;
cachedXmlBlockFiles[pos] = file;
cachedXmlBlocks[pos] = block;
return block.newParser();
}
複製代碼
image
:經過mAssets.openNonAsset
來加載二進制流,並經過decodeImageDrawable()
來轉化爲相應的Drawable
對象到這裏,對於Resource
類的做用咱們就梳理的差很少了,主要的功能是緩存Drawable
、ColorStateList
和XML
文件等資源,而具體的加載細節,都和一個叫AssetManager
的類有關係。
AssetManager
等下再看,咱們能夠先總結一下:
Framework
資源的Resources
實例系統只有一份,經過Resources
類中的靜態成員變量mSystem
引用。Drawable
資源和ColorStateList
資源也是經過Resources
類中的靜態成員變量引用。而且在整個系統中共享,不會浪費內存Drawable
、ColorStateList
和XML
資源在Resources
中會被緩存,所以反覆裝載這些資源的操做不會真正的重複加載,不會浪費額外的內存AssetManager
類的做用AssetManager
類是Android
中實際管理資源的模塊,在Java層
和native層
都有代碼。咱們先看Java層
的AssetManager
類
Java層
的AssetManager
類咱們先看下AssetManager
類中的變量:
// Not private for LayoutLib's BridgeAssetManager.
@UnsupportedAppUsage
@GuardedBy("sSync") static AssetManager sSystem = null;
@GuardedBy("sSync") private static ApkAssets[] sSystemApkAssets = new ApkAssets[0];
@GuardedBy("sSync") private static ArraySet<ApkAssets> sSystemApkAssetsSet;
// Pointer to native implementation, stuffed inside a long.
@UnsupportedAppUsage
@GuardedBy("this") private long mObject;
// The loaded asset paths.
@GuardedBy("this") private ApkAssets[] mApkAssets;
複製代碼
sSystem
是和Resources
類中mSystem
對應的AssetManager
對象,用來管理系統Framewrok
的資源
mSystem
的初始化 部分sSystemApkAssets
、sSystemApkAssetsSet
都是和系統資源相關的ApkAssets
的集合,在Zygote
初始化時就已經建立完成
ApkAssets
究竟是啥?
/** * The loaded, immutable, in-memory representation of an APK. * * The main implementation is native C++ and there is very little API surface exposed here. The APK * is mainly accessed via {@link AssetManager}. * * Since the ApkAssets instance is immutable, it can be reused and shared across AssetManagers, * making the creation of AssetManagers very cheap. * @hide */
public final class ApkAssets {
@GuardedBy("this") private final long mNativePtr;
@GuardedBy("this") private final StringBlock mStringBlock;
}
private ApkAssets(@NonNull String path, boolean system, boolean forceSharedLib, boolean overlay) throws IOException {
mNativePtr = nativeLoad(path, system, forceSharedLib, overlay);
mStringBlock = new StringBlock(nativeGetStringBlock(mNativePtr), true /*useSparse*/);
}
public static @NonNull ApkAssets loadFromPath(@NonNull String path) throws IOException {
return new ApkAssets(path, false /*system*/, false /*forceSharedLib*/, false /*overlay*/);
}
複製代碼
ApkAssets
主要是與native
層進行聯繫,經過各類native
調用加載相關資源ApkAssets
還有一個功能是把native層
加載字符串資源保存到mStringBlock
中sSystemApkAssets
是一個ApkAssets
的數組,一般一個ApkAssets
管理的是一個資源包中的資源,用數組的話說明可能會管理多個資源包mObject
:用來存儲在native層
建立的AssetManager
對象指針mApkAssets
:記錄全部已經加載的asset
資源,也包括sSystemApkAssets
AssetManager
的setApkAssets()
函數還記得
Resources
初始化時調用的AssetManager.getSystem()
函數麼?咱們來看下具體幹了些啥?
註釋比較詳細:
private AssetManager(boolean sentinel) {
// nativeCreate()的做用是建立一個native層的AssetManager實例,並返回對象指針
// 從9.0的源碼看,建立的是AssetManager2的實例
mObject = nativeCreate();
}
public static AssetManager getSystem() {
synchronized (sSync) {
createSystemAssetsInZygoteLocked();
return sSystem;
}
}
private static void createSystemAssetsInZygoteLocked() {
if (sSystem != null) {
return;
}
final ArrayList<ApkAssets> apkAssets = new ArrayList<>();
// 經過 ApkAssets 的 loadFromPath 來加載 Framework 資源
// 最終是經過 nativeLoad() native函數來完成
apkAssets.add(ApkAssets.loadFromPath("/system/framework/frmework-res.apk", true /*system*/));
// 省略一些資源的額外操做
......
// 分別賦值給 sSystemApkAssetsSet 和 sSystemApkAssets 集合
sSystemApkAssetsSet = new ArraySet<>(apkAssets);
sSystemApkAssets = apkAssets.toArray(newApkAssets[apkAssets.size()]);
// 建立系統專用的AssetManager實例
// 最終調用的是 nativeCreate 函數
sSystem = new AssetManager(true /*sentinel*/);
// 設置資源到已加載集合,並賦值給mApkAssets變量
sSystem.setApkAssets(sSystemApkAssets, false /*invalidateCaches*/);
}
複製代碼
關於AssetManager
的建立咱們比較清晰了,咱們會發現大多數的過程都是經過native
調用來完成的,這也是Java層
的AssetManager
的主要做用:擔當經過native層的橋樑
咱們以Resources
的getString()
方法,來追蹤下調用過程:
// Resources.java
public String getString(@StringRes int id) throws NotFoundException {
return getText(id).toString();
}
public CharSequence getText(@StringRes int id) throws NotFoundException {
// 調用的AssetManager的getResourceText函數
CharSequence res = mResourcesImpl.getAssets().getResourceText(id);
if (res != null) {
return res;
}
......
}
// AssetManager.java
CharSequence getResourceText(@StringRes int resId) {
synchronized (this) {
// 經過 TypedValue 對象來接收轉換數據
final TypedValue outValue = mValue;
if (getResourceValue(resId, 0, outValue, true)) {
return outValue.coerceToString();
}
return null;
}
}
boolean getResourceValue(@AnyRes int resId, int densityDpi, @NonNull TypedValue outValue, boolean resolveRefs) {
Preconditions.checkNotNull(outValue, "outValue");
synchronized (this) {
//經過 nativeGetResourceValue 函數查找resId資源對應ApkAssets的cookie值
//並把相關數據更新到TypedValue對象中
//資源cookie值映射的是Java層的已加載資源mApkAssets集合的index值
final int cookie = nativeGetResourceValue(
mObject, resId, (short) densityDpi, outValue, resolveRefs);
if (cookie <= 0) {
return false;
}
......
if (outValue.type == TypedValue.TYPE_STRING) {
// 當數值類型判斷正確後
// 經過ApkAssets對象的getStringFromPool讀取數據
// 須要注意的是outValue.data
// 它表示字符串在對應ApkAssets對象中的mStringBlock數組的index值
outValue.string = mApkAssets[cookie - 1].getStringFromPool(outValue.data);
}
return true;
}
}
// ApkAssets.java
CharSequence getStringFromPool(int idx) {
synchronized (this) {
// 從mStringBlock獲取字符串數據
return mStringBlock.get(idx);
}
}
複製代碼
調用過程很清晰了哈!
能夠看出AssetManager
的功能實現依賴了不少的native
函數,接下來咱們就看看Android
在native
層是怎麼設計的吧!
native
層的AssetManager
從上面咱們知道,native
層的AssetManager
纔是真正裝載資源的部分。
Android 資源管理
這部分是Google
爸爸折騰最多的地方,對比9.0
和5.0
的源碼真滴是天差地別啊
5.0
的書籍已經用不上了,差異太大。。。。。不過,從上面Java
層的知識咱們已經知道:
framwork-res.apk
)的入口是ApkAssets.loadFromPath()
AssetManager.setApkAssets()
咱們就先從這兩個函數入手看看
ApkAssets
裝載資源ApkAssets.loadFromPath
調用的是ApkAssets
的私有構造函數:
// ApkAssets.java
private ApkAssets(@NonNull String path, boolean system, boolean forceSharedLib, boolean overlay) throws IOException {
mNativePtr = nativeLoad(path, system, forceSharedLib, overlay);
mStringBlock = new StringBlock(nativeGetStringBlock(mNativePtr), true /*useSparse*/);
}
複製代碼
ApkAssets
的私有構造函數經過nativeLoad()
本地調用來實現真正的資源加載
nativeLoad()
函數調用示意:
// android_content_res_ApkAssets.cpp
static jlong NativeLoad(JNIEnv* env, jclass /*clazz*/, jstring java_path, jboolean system, jboolean force_shared_lib, jboolean overlay) {
.......
// 調用了 native層的 ApkAssets 的 Load 函數
apk_assets = ApkAssets::Load(path.c_str(), system);
......
return reinterpret_cast<jlong>(apk_assets.release());
}
// ApkAssets.cpp
std::unique_ptr<const ApkAssets> ApkAssets::Load(const std::string& path, bool system) {
// 奇怪的C++智能指針
// 真正進行加載的是 LoadImpl() 函數
return LoadImpl({} /*fd*/, path, nullptr, nullptr, system, false /*load_as_shared_library*/);
}
複製代碼
nativeLoad()
函數調用了native
層ApkAssets
類的LoadImpl
函數。從這個函數開始,就開始對resources.arsc
文件進行操做了,因此,咱們先配上一幅結構圖再一步一步的看函數實現
resources.arsc
文件圖片來源於大神LinJW博客,受益不淺:
resources.arsc
是以一個個Chunk塊
的形式組織的,Chunk
的頭部信息記錄了這個Chunk
的類型、長度等數據。從總體上來看,其結構爲:資源索引表頭部
+字符串資源池
+N個Package數據塊
簡單瞭解便可,讓咱們從LoadImpl
函數開始跟蹤吧
ApkAssets::LoadImpl()
函數std::unique_ptr<const ApkAssets> ApkAssets::LoadImpl(......) {
// 一個用來指向解壓後的資源文件的指針
::ZipArchiveHandle unmanaged_handle;
// 解壓資源文件
::OpenArchive(path.c_str(), &unmanaged_handle);
// 將解壓後的資源文件包裝成ApkAssets對象
// Wrap the handle in a unique_ptr so it gets automatically closed.
std::unique_ptr<ApkAssets> loaded_apk(new ApkAssets(unmanaged_handle, path));
// resources.arsc 就是咱們apk中的資源索引表
::ZipString entry_name("resources.arsc");
::ZipEntry entry;
// 從解壓後的apk資源中查找 resources.arsc
result = ::FindEntry(loaded_apk->zip_handle_.get(), entry_name, &entry);
if (result != 0) {
// There is no resources.arsc, so create an empty LoadedArsc and return.
loaded_apk->loaded_arsc_ = LoadedArsc::CreateEmpty();
// 對於一個Java coder,std::move真的是一個SAO操做
return std::move(loaded_apk);
}
// Open the resource table via mmap unless it is compressed.
// This logic is taken care of by Open.
// 經過Open函數加載ARSC文件到resources_asset_,英文註釋也就是方法的執行邏輯了
loaded_apk->resources_asset_ = loaded_apk->Open(kResourcesArsc, Asset::AccessMode::ACCESS_BUFFER);
// Must retain ownership of the IDMAP Asset so that all pointers to its mmapped data remain valid.
// 其實這行代碼主要是給loadOverlay狀況用的,其餘狀況下都至關於賦值爲null
// loadOverlay中已經包含了一個idmap,因此在這裏須要保留下來原來的
loaded_apk->idmap_asset_ = std::move(idmap_asset);
// 將resources_asset_(加載完成的ASRC數據)打包成StringPiece對象
const StringPiece data(reinterpret_cast<const char*>(loaded_apk->resources_asset_->getBuffer(true /*wordAligned*/)),loaded_apk->resources_asset_->getLength());
// 調用 LoadedArsc 的 Load 函數解析轉化爲資源數據,咱們等下重點看下
loaded_apk->loaded_arsc_ = LoadedArsc::Load(data, loaded_idmap.get(), system, load_as_shared_library);
......
// Need to force a move for mingw32.
return std::move(loaded_apk);
}
複製代碼
上面的代碼註釋比較詳細哈,再也不贅述。接下來重點關注下LoadedArsc::Load()
函數。
LoadedArsc::Load()
函數解析resources.arsc
std::unique_ptr<const LoadedArsc> LoadedArsc::Load(const StringPiece& data, const LoadedIdmap* loaded_idmap, bool system, bool load_as_shared_library) {
// Not using make_unique because the constructor is private.
// 建立 LoadedArsc 實例
std::unique_ptr<LoadedArsc> loaded_arsc(new LoadedArsc());
loaded_arsc->system_ = system;
// 遍歷StringPiece對象(也就是加載好的arsc)
ChunkIterator iter(data.data(), data.size());
while (iter.HasNext()) {
const Chunk chunk = iter.Next();
switch (chunk.type()) {
case RES_TABLE_TYPE:
// 若是數據類型是 RES_TABLE_TYPE
// 經過 LoadTable 將對應資源加載到 table 中
if (!loaded_arsc->LoadTable(chunk, loaded_idmap, load_as_shared_library)) {
return {};
}
break;
}
}
return std::move(loaded_arsc);
}
複製代碼
從上面能夠看到,資源的加載處理調用到了LoadTable()
函數,咱們來看下
LoadedArsc::LoadTable()
函數解析resources.arsc
bool LoadedArsc::LoadTable(const Chunk& chunk, const LoadedIdmap* loaded_idmap, bool load_as_shared_library) {
const ResTable_header* header = chunk.header<ResTable_header>();
......
ChunkIterator iter(chunk.data_ptr(), chunk.data_size());
while (iter.HasNext()) {
const Chunk child_chunk = iter.Next();
switch (child_chunk.type()) {
case RES_STRING_POOL_TYPE:
// Only use the first string pool. Ignore others.
// 若是是字符常量池類型的資源
// 數據保存到global_string_pool_中,這是全局字符串
// Java層ApkAssets中的mStringBlock指向的就是這部分數據
status_t err = global_string_pool_.setTo(child_chunk.header<ResStringPool_header>(), child_chunk.size());
break;
case RES_TABLE_PACKAGE_TYPE: {
// 若是是pacakge類型的資源
// 使用 LoadedPackage::Load() 進行加載
......
std::unique_ptr<const LoadedPackage> loaded_package =
LoadedPackage::Load(child_chunk, loaded_idmap, system_, load_as_shared_library);
packages_.push_back(std::move(loaded_package));
} break;
}
}
......
return true;
}
複製代碼
從上面的代碼看,字符串資源放到了global_string_pool_
中,對於RES_TABLE_PACKAGE_TYPE
類型的資源經過LoadedPackage::Load()
函數
LoadedPackage::Load()
函數解析resources.arsc
std::unique_ptr<const LoadedPackage> LoadedPackage::Load(......) {
// 建立LoadedPackage實例
std::unique_ptr<LoadedPackage> loaded_package(new LoadedPackage());
......
// 設置是否爲系統資源
loaded_package->system_ = system;
// 根據packageID 判斷是否爲共享資源
loaded_package->package_id_ = dtohl(header->id);
if (loaded_package->package_id_ == 0 ||
(loaded_package->package_id_ == kAppPackageId && load_as_shared_library)) {
// Package ID of 0 means this is a shared library.
loaded_package->dynamic_ = true;
}
......
while (iter.HasNext()) {
const Chunk child_chunk = iter.Next();
switch (child_chunk.type()) {
case RES_STRING_POOL_TYPE:
// 這裏會解析兩種字符串池類型
// 一種是資源類型字符串池,好比:ainim、attr、bool、color等
// 另外一種是資源項名稱字符串池,好比:app_name、activity_main等
......
case RES_TABLE_TYPE_SPEC_TYPE:
......
case RES_TABLE_TYPE_TYPE:
......
case RES_TABLE_LIBRARY_TYPE:
const ResTable_lib_entry* const entry_end = entry_begin + dtohl(lib->count);
for (auto entry_iter = entry_begin; entry_iter != entry_end; ++entry_iter) {
......
// 添加package_name和packageId到loaded_package的dynamic_package_map_中
loaded_package->dynamic_package_map_.emplace_back(std::move(package_name),dtohl(entry_iter->packageId));
}
......
}
}
......
return std::move(loaded_package);
}
複製代碼
這部分函數的核心就是把chunk
按類型進行解析處理,而後返回解析完成的loaded_package
指針。
到這裏一個res.apk
的裝載過程基本就完成了,過程當中忽略了不少細節,不要着急哈,讓咱們先搞清總體流程。
讓咱們從ApkAssets
開始來梳理下流程:
LoadedArsc
對象中的global_string_pool
和package
相關的資源已經完成加載ApkAssets
對象中的resource_asset_
和loaded_arsc_
也加載完成這樣,一個res.apk
的裝載過程咱們清楚了,可是AssetManager
是怎麼組織它的呢?
咱們來看下AssetManager.setApkAssets()
函數
AssetManager.setApkAssets()
添加資源咱們先看AssetManager.setApkAssets()
函數:
public void setApkAssets(@NonNull ApkAssets[] apkAssets, boolean invalidateCaches) {
......
ApkAssets[] newApkAssets = new ApkAssets[sSystemApkAssets.length + apkAssets.length];
// Copy the system assets first.
System.arraycopy(sSystemApkAssets, 0, newApkAssets, 0, sSystemApkAssets.length);
// Copy the given ApkAssets if they are not already in the system list.
int newLength = sSystemApkAssets.length;
for (ApkAssets apkAsset : apkAssets) {
if (!sSystemApkAssetsSet.contains(apkAsset)) {
newApkAssets[newLength++] = apkAsset;
}
}
......
mApkAssets = newApkAssets;
nativeSetApkAssets(mObject, mApkAssets, invalidateCaches);
}
複製代碼
Java
部分比較簡單:
setApkAssets()
會先把sSystemApkAssets
中的資源拷貝到一個全新數組newApkAssets
中sSystemApkAssets
沒有的資源,並添加到數組newApkAssets
mApkAssets
nativeSetApkAssets
處理native層
的資源咱們看下NativeSetApkAssets
函數作了啥:
static void NativeSetApkAssets(JNIEnv* env, jclass /*clazz*/, jlong ptr, jobjectArray apk_assets_array, jboolean invalidate_caches) {
// 根據Java層傳遞的數值長度,建立對應的ApkAssets集合
const jsize apk_assets_len = env->GetArrayLength(apk_assets_array);
std::vector<const ApkAssets*> apk_assets;
apk_assets.reserve(apk_assets_len);
// 將 Java 層保存的ApkAssets指針取出並添加到集合中
for (jsize i = 0; i < apk_assets_len; i++) {
jobject obj = env->GetObjectArrayElement(apk_assets_array, i);
jlong apk_assets_native_ptr = env->GetLongField(obj, gApkAssetsFields.native_ptr);
apk_assets.push_back(reinterpret_cast<const ApkAssets*>(apk_assets_native_ptr));
}
ScopedLock<AssetManager2> assetmanager(AssetManagerFromLong(ptr));
// 調用 AssetManager2 的 SetApkAssets 函數
assetmanager->SetApkAssets(apk_assets, invalidate_caches);
}
複製代碼
NativeSetApkAssets()
函數對Java層
的數據進行簡單處理後,調用了AssetManager2
的SetApkAssets
函數,函數以下:
bool AssetManager2::SetApkAssets(const std::vector<const ApkAssets*>& apk_assets, bool invalidate_caches) {
// 將ApkAssets賦值給apk_assets_
apk_assets_ = apk_assets;
// 構建動態資源引用表
BuildDynamicRefTable();
// 從新構建符合當前設備的資源,即 filtered_configs_
RebuildFilterList();
......
return true;
}
複製代碼
真的簡潔。。。。。。核心函數也很突出,爲了方便理解接下來的BuildDynamicRefTable()
函數,咱們先來看AssetManager2
類的相關定義
AssetManager2
的關鍵定義class AssetManager2 {
//用來存儲該AssetManager2已經加載的全部APK包
std::vector<const ApkAssets*> apk_assets_;
/** * 用來將apk_assets_分組,主要仍是用來處理Runtime Resources Overlay的 */
std::vector<PackageGroup> package_groups_;
/** * 它的key表示APK包也就是ApkAssets的id * 它的value表示APK包也就是ApkAssets所在的PackageGroup在package_groups_中的索引 */
std::array<uint8_t, std::numeric_limits<uint8_t>::max() + 1> package_ids_;
//表示設備當前的配置信息
ResTable_config configuration_;
/** * 用來緩存資源的Bag * 它的key表示一個資源的id,好比一個style,一個array * 它的value 表示已經從resources.arsc中解析出來了的,該資源的全部Bag */
std::unordered_map<uint32_t, util::unique_cptr<ResolvedBag>> cached_bags_;
}
// A collection of configurations and their associated ResTable_type that match the current
// AssetManager configuration.
struct FilteredConfigGroup {
// 該ResTable_typeSpec中符合設備當前配置的全部的config
std::vector<ResTable_config> configurations;
// 該ResTable_typeSpec中符合設備當前配置的全部的ResTable_type
std::vector<const ResTable_type*> types;
};
// Represents an single package.
struct ConfiguredPackage {
// A pointer to the immutable, loaded package info.
const LoadedPackage* loaded_package_;
// 咱們在獲取資源的時候,要根據設備的當前配置信息,去選擇最合適的資源項
// 這個過程要通過match、isBetterThan、isMoreSpecificThan 等比較的過程
// 如今爲了加快獲取資源的速度,在加載完資源後,系統就會先選出匹配設備當前配置的資源存放在filtered_configs_中。
// 當咱們獲取資源的時候,就能夠節省篩選步驟
// filtered_configs_中的每一項表明一個ResTable_typeSpec中符合設備當前配置的全部ResTable_type
ByteBucketArray<FilteredConfigGroup> filtered_configs_;
};
using ApkAssetsCookie = int32_t;
// Represents a logical package, which can be made up of many individual packages. Each package
// in a PackageGroup shares the same package name and package ID.
// 你們留意上面的註釋
struct PackageGroup {
// 相同loaded_package name的集合,包括target package和overlay package
// 若是一個Package沒有overlay package,那麼它應該獨佔一個PackageGroup
std::vector<ConfiguredPackage> packages_;
// The cookies associated with each package in the group. They share the same order as
// packages_.
// 表示 ConfiguredPackage 所表明的 ApkAssets 在 AssetManager2 的 apk_assets_ 集合中的位置
// cookies_集合的存放順序與packages_的順序一一對應
std::vector<ApkAssetsCookie> cookies_;
// A library reference table that contains build-package ID to runtime-package ID mappings.
// 一個用來描述資源共享庫的編譯時id和運行時id的映射關係表
DynamicRefTable dynamic_ref_table;
};
複製代碼
結合上面的定義,咱們來看下BuildDynamicRefTable()
幹了啥
BuildDynamicRefTable()
構建動態資源引用表void AssetManager2::BuildDynamicRefTable() {
// 簡單的初始化操做
package_groups_.clear();
package_ids_.fill(0xff);
// 0x01 is reserved for the android package.
int next_package_id = 0x02;
const size_t apk_assets_count = apk_assets_.size();
// 遍歷處理apk_assets_集合
for (size_t i = 0; i < apk_assets_count; i++) {
// 獲取已經解析加載完成的arsc資源,即loaded_arsc
const LoadedArsc* loaded_arsc = apk_assets_[i]->GetLoadedArsc();
// 從loaded_arsc中取出解析完成的package資源,即loaded_package
for (const std::unique_ptr<const LoadedPackage>& package : loaded_arsc->GetPackages()) {
// Get the package ID or assign one if a shared library.
int package_id;
if (package->IsDynamic()) {
// 若是是共享資源,分配一個特殊的package id
package_id = next_package_id++;
} else {
// 不是共享資源,使用原有的package id
package_id = package->GetPackageId();
}
// Add the mapping for package ID to index if not present.
uint8_t idx = package_ids_[package_id];
if (idx == 0xff) {
// 0xff 說明package_ids還未記錄
package_ids_[package_id] = idx = static_cast<uint8_t>(package_groups_.size());
// 先向package_groups_的尾部添加一個新的PackageGroup對象
package_groups_.push_back({});
// 而後獲取對象動態資源引用表的指針
DynamicRefTable& ref_table = package_groups_.back().dynamic_ref_table;
// 賦值 package_id,幹啥用的後面看看再說
ref_table.mAssignedPackageId = package_id;
// 設置當前是否爲共享資源,常量值 0x7F 值得研究研究
ref_table.mAppAsLib = package->IsDynamic() && package->GetPackageId() == 0x7f;
}
// 取出PackageGroup對象,開始進行數據填充
PackageGroup* package_group = &package_groups_[idx];
// Add the package and to the set of packages with the same ID.
// 設置 packages_ 和 cookies_
package_group->packages_.push_back(ConfiguredPackage{package.get(), {}});
// 留意下這個變量i,其實就是當前ApkAsset在apk_assets_中的數組下標
package_group->cookies_.push_back(static_cast<ApkAssetsCookie>(i));
// Add the package name -> build time ID mappings.
for (const DynamicPackageEntry& entry : package->GetDynamicPackageMap()) {
// 解析loaded_package中的 dynamic_package_map_ 一個packageName:packageID格式的集合
String16 package_name(entry.package_name.c_str(), entry.package_name.size());
// 並設置到 package_group 中的動態資源引用表中
package_group->dynamic_ref_table.mEntries.replaceValueFor(
package_name, static_cast<uint8_t>(entry.package_id));
}
}
}
// 到這裏,ApkAssets集合中的數據就被解析的差很少了
// package_groups_和package_ids_集合中的數據也被填充的差很少了
// Now assign the runtime IDs so that we have a build-time to runtime ID map.
// 接下來的這部分是將資源的 Build ID 與 runtime ID 關聯起來
// 還記得前面生成的 mAssignedPackageId 麼,在這裏就起到做用了
const auto package_groups_end = package_groups_.end();
for (auto iter = package_groups_.begin(); iter != package_groups_end; ++iter) {
const std::string& package_name = iter->packages_[0].loaded_package_->GetPackageName();
for (auto iter2 = package_groups_.begin(); iter2 != package_groups_end; ++iter2) {
// 跟蹤addMapping函數就會發現,這部分的操做是:
// 根據 package_name 查找的 build packageID
// 而後把 build packageID 做爲索引,關聯runtimeID,也就是 mAssignedPackageId
iter2->dynamic_ref_table.addMapping(package_name, iter->dynamic_ref_table.mAssignedPackageId);
}
}
}
複製代碼
BuildDynamicRefTable()
函數基本上把AssetManager2
定義的關鍵數據都填充完了,總體流程註釋的比較詳細,再也不補充啦
這裏仍是要吐槽一下,
Android
資源相關的struct嵌套有點多,短期理解起來仍是有難度的。好在資源加載部分的業務邏輯應該不多遇到改動需求,慶幸一下下先
到這裏整個framework
資源的加載流程就差很少完成了,咱們簡單總結下
AssetManager
加載過程的簡單總結資源管理部分還有比較重要的一個知識點是
RRO
,就不在這裏介紹了,AssetManager2
中的不少數據定義都是爲了RRO
而設計的,須要的話能夠先從Google官網-RRO瞭解先
咱們仍是先經過調用時序圖來看下資源加載過程,從AssetManager.getSystem()
開始,流程以下:
資源加載完後的總體結構,以下圖:
看完這兩張圖後思路有木有更清晰了呢,哈哈哈
AssetManager
的資源查找瞭解了AssetManager
的資源加載過程,查找過程就比較容易了,AssetManager
的資源查找入口能夠簡單分爲兩種:
open
方式:像open()
、openNonAsset()
等getResource
方式:像getResourceText()
、getResourceArray()
等咱們來簡單看下
open
方式對於open
類的接口,基本上處理的都是一些xml
或者文件
形式的數據,從函數調用來看,最後都走到了native
層的OpenNonAsset
函數,這部分處理比較簡單,源碼以下:
std::unique_ptr<Asset> AssetManager2::OpenNonAsset(const std::string& filename, Asset::AccessMode mode, ApkAssetsCookie* out_cookie) const {
for (int32_t i = apk_assets_.size() - 1; i >= 0; i--) {
std::unique_ptr<Asset> asset = apk_assets_[i]->Open(filename, mode);
if (asset) {
if (out_cookie != nullptr) {
*out_cookie = i;
}
return asset;
}
}
......
}
std::unique_ptr<Asset> AssetManager2::OpenNonAsset(const std::string& filename, ApkAssetsCookie cookie, Asset::AccessMode mode) const {
......
// 從這裏就能夠看出 ApkAssetsCookie 只是一個下標索引
// 若是以前查找過對應的資源,就記錄下來對應的數組下標
// 下次獲取時就不須要像上面同樣用for循環去查找了
return apk_assets_[cookie]->Open(filename, mode);
}
複製代碼
函數根據cookie
需求作了一個簡單的重載,可是最終都是調用到了ApkAssets
的Open()
函數。
這個函數咱們在資源加載時遇到過,做用就是加載資源,若是是壓縮數據會進行相應的解壓處理。
這裏面還涉及到了Asset
類,你們感興趣能夠深刻了解下
getResource
方式跟蹤getResource
類型的函數調用,咱們最終找到的native
層接口是AssetManager2::FindEntry()
,簡要流程以下:
ApkAssetsCookie AssetManager2::FindEntry(uint32_t resid, uint16_t density_override, bool /*stop_at_first_match*/, FindEntryResult* out_entry) const {
......
// 根據resid解析出對應的pacakge信息
const uint32_t package_id = get_package_id(resid);
const uint8_t type_idx = get_type_id(resid) - 1;
const uint16_t entry_idx = get_entry_id(resid);
const uint8_t package_idx = package_ids_[package_id];
if (package_idx == 0xff) {
// 資源對應的package爲空,返回
LOG(ERROR) << base::StringPrintf("No package ID %02x found for ID 0x%08x.", package_id, resid);
return kInvalidCookie;
}
// 查找到資源對應的 PackageGroup 對象,PackageGroup 對象中包含着一個一個的loaded_pacakge
const PackageGroup& package_group = package_groups_[package_idx];
// 記錄 PackageGroup 對象中的loaded_package_數量
const size_t package_count = package_group.packages_.size();
......
// 開始從資源對應的 PackageGroup 中查找最合適的資源
for (size_t pi = 0; pi < package_count; pi++) {
......
// If desired_config is the same as the set configuration, then we can use our filtered list
// and we don't need to match the configurations, since they already matched.
// 若是獲取資源時的配置信息和資源load時的配置信息一致的話,可使用快速查找邏輯
const bool use_fast_path = desired_config == &configuration_;
// 獲取最適合當前配置的資源集合
const FilteredConfigGroup& filtered_group = loaded_package_impl.filtered_configs_[type_idx];
if (use_fast_path) {
// 快速查找,直接使用filtered_configs_中的配置資源便可
} else {
// This is the slower path, which doesn't use the filtered list of configurations.
// Here we must read the ResTable_config from the mmapped APK, convert it to host endianness
// and fill in any new fields that did not exist when the APK was compiled.
// Furthermore when selecting configurations we can't just record the pointer to the
// ResTable_config, we must copy it.
// 須要查找資源對應的全部類型,從中找出匹配的資源,過程當中還會涉及到內存拷貝,比較緩慢
......
}
}
......
}
複製代碼
AssetManager2::FindEntry()
函數會查找到最匹配的資源,而後打包成FindEntryResult
格式,即out_entry
,並返回該資源在apk_assets_
數組中的下標,也就是ApkAssetsCookie
。獲取到ApkAssetsCookie
後和out_entry
後,再根據不一樣的資源類型進行其餘的業務處理:
getResourceText
:經過mApkAssets[cookie - 1].getStringFromPool
來獲取getResourceTextArray
:
native
層會遍歷out_entry
,找到該entry
包含的全部資源,並以[cookie,index,cookie,index......]
內容格式的數組形式返回java
層java
層經過mApkAssets[cookie - 1].getStringFromPool(index)
來獲取原函數還有不少細節沒有展現,不過總體流程已經梳理出來了。再詳細的部分就涉及到ARSC
中的數據結構了,爲了不篇幅過大就先到這裏啦!
感受要想真正搞懂資源管理這部分仍是要去熟悉arsc
文件的格式,以及加載後的內存模型
學到這裏算是找到了進入Android資源管理
世界入口和鑰匙了,後面再來深刻學習研究吧(挖坑ING......)
對於Android資源管理
這部分的學習來講,進度比以往慢了不少,書中5.0
的資源管理與當前系統差別着實不小,少了一個好的嚮導,學習起來迷茫了一些,好在對比着源碼磕磕絆絆的理清了。
總結下來,還只是剛剛入門,像資源適配
、RRO
等知識還未詳細涉及,但爲了避免拖延大局,只能先到此爲止了,等完成Android
系統的總體學習後再來補充吧
先附上參考連接以供分享學習之用:
下一篇學習SystemServer
進程