原文出處——>[Android應用程序資源的查找過程分析](http://blog.csdn.net/luoshengyang/article/details/8806798)
我們知道,在Android系統中,每一個應用程序一般都會配置很多資源,用來適配不同密度、大小和方向的屏幕,以及適配不同的國家、地區和語言等等。這些資源是在應用程序運行時自動根據設備的當前配置信息進行適配的。這也就是說,給定一個相同的資源ID,在不同的設備配置之下,查找到的可能是不同的資源。這個資源查找過程對應用程序來說,是完全透明的。在本文中,我們就詳細分析資源管理框架是如何根據ID來查找資源的。
從前面[Android應用程序資源管理器(Asset Manager)的創建過程分析](http://blog.csdn.net/luoshengyang/article/details/8791064)一文可以知道,Android資源管理框架實際就是由AssetManager和Resources兩個類來實現的。其中,Resources類可以根據ID來查找資源,而AssetManager類根據文件名來查找資源。事實上,如果一個資源ID對應的是一個文件,那么Resources類是先根據ID來找到資源文件名稱,然后再將該文件名稱交給AssetManager類來打開對應的文件的,這個過程如圖1所示。

圖1 應用程序查找資源的過程示意圖
在圖1中,Resources類根據資源ID來查到資源名稱實際上也是要通過AssetManager類來實現的,這是因為資源ID與資源名稱的對應關系是由打包在APK里面的resources.arsc文件中的。當Resources類查找的資源對應的是一個文件的時候,它就會再次將資源名稱交給AssetManager,以便后者可以打開對應的文件,否則的話,上一步找到的資源名稱就是最終的查找結果。
從前面[Android應用程序資源的編譯和打包過程分析](http://blog.csdn.net/luoshengyang/article/details/8744683)一文可以知道,APK包里面的resources.arsc文件是在編譯應用程序資源的時候生成的,然后連同其它被編譯的以及原生的資源一起打包在一個APK包里面。
從前面[Android資源管理框架(Asset Manager)簡要介紹和學習計劃](http://blog.csdn.net/luoshengyang/article/details/8738877)一文又可以知道,Android應用程序資源是可以劃分是很多類別的,但是從資源查找的過程來看,它們可以歸結為兩大類。第一類資源是不對應有文件的,而第二類資源是對應有文件的,例如,字符串資源是直接編譯在resources.arsc文件中的,而界面布局資源是在APK包里面是對應的單獨的文件的。如上所述,不對應文件的資源只需要執行從資源ID到資源名稱的轉換即可,而對應有文件的資源還需要根據資源名稱來打開對應的文件。在本文中,我們就以界面布局資源的查找過程為例,來說明Android資源管理框架查找資源的過程。
我們知道,每一個Activity組件創建的時候,它的成員函數onCreate都會被調用,而在Activity組件的成員函數onCreate中,我們基本上都無一例外地調用setContentView來設置Activity組件的界面。在調用Activity組件的成員函數setContentView的時候,需要指定一個layout類型的資源ID,以便Android資源管理框架可以找到指定的Xml資源文件來填充(inflate)為Activity組件的界面。接下來,我們就從Activity類的成員函數setContentView開始,分析Android資源管理框架查找layout資源的過程,如圖2所示。

圖2 類型為layout的資源的查找過程
這個過程可以分為22個步驟,接下來我們就詳細分析每一個步驟。
#### **Step 1. Activity.setContentView**
~~~
public class Activity extends ContextThemeWrapper
implements LayoutInflater.Factory,
Window.Callback, KeyEvent.Callback,
OnCreateContextMenuListener, ComponentCallbacks {
......
private Window mWindow;
......
public Window getWindow() {
return mWindow;
}
.....
public void setContentView(int layoutResID) {
getWindow().setContentView(layoutResID);
}
......
}
~~~
這個函數定義在文件frameworks/base/core/java/android/app/Activity.java中。
從前面[Android應用程序窗口(Activity)的窗口對象(Window)的創建過程分析](http://blog.csdn.net/luoshengyang/article/details/8223770)一文可以知道,Activity類的成員變量mWindow指向的是一個PhoneWindow對象,因此,Activity類的成員函數setContentView實際上是調用PhoneWindow類的成員函數setContentView來進一步操作。
#### **Step 2. PhoneWindow.setContentView**
~~~
public class PhoneWindow extends Window implements MenuBuilder.Callback {
......
// This is the view in which the window contents are placed. It is either
// mDecor itself, or a child of mDecor where the contents go.
private ViewGroup mContentParent;
......
private LayoutInflater mLayoutInflater;
......
@Override
public void setContentView(int layoutResID) {
if (mContentParent == null) {
installDecor();
} else {
mContentParent.removeAllViews();
}
mLayoutInflater.inflate(layoutResID, mContentParent);
final Callback cb = getCallback();
if (cb != null) {
cb.onContentChanged();
}
}
......
}
~~~
這個函數定義在文件frameworks/base/policy/src/com/android/internal/policy/impl/PhoneWindow.java中。
PhoneWindow類的成員變量mContentParent用來描述一個類型為DecorView的視圖對象,或者這個類型為DecorView的視圖對象的一個子視圖對象,用作UI容器。當它的值等于null的時候,就說明當前正在處理的Activity組件的視圖對象還沒有創建。在這種情況下,就會調用成員函數installDecor來創建當前正在處理的Activity組件的視圖對象。否則的話,就說明是要重新設置當前正在處理的Activity組件的視圖。在重新設置之前,首先調用成員變量mContentParent所描述的一個ViewGroup對象來移除原來的UI內容。
PhoneWindow類的成員變量mLayoutInflater指向的是一個PhoneLayoutInflater對象。PhoneLayoutInflater類是從LayoutInflater類繼續下來的,同時它也繼承了LayoutInflater類的成員函數inflate。通過調用PhoneWindow類的成員變量mLayoutInflater所指向的一個PhoneLayoutInflater對象的成員函數inflate,也就是從父類繼承下來的成員函數inflate,就可以將參數layoutResID所描述的一個UI布局設置到mContentParent所描述的一個視圖容器中去。這樣就可以將當前正在處理的Activity組件的UI創建出來。
最后,PhoneWindow類的成員函數還會調用一個Callback接口的成員函數onContentChanged來通知當前正在處理的Activity組件,它的視圖內容發生改變了。從前面[Android應用程序窗口(Activity)的窗口對象(Window)的創建過程分析](http://blog.csdn.net/luoshengyang/article/details/8223770)一文可以知道,每一個Activity組件都實現了一個Callback接口,并且將這個Callback接口設置到了與它所關聯的PhoneWindow的內部去,因此,最后調用的實際上是Activity類的成員函數onContentChanged。
接下來,我們就繼續分析LayoutInflater類的成員函數inflate的實現,以便可以了解Android資源管理框架是如何找到參數layoutResID所描述的UI布局文件的。
#### **Step 3. LayoutInflater.inflate**
~~~
public abstract class LayoutInflater {
......
public View inflate(int resource, ViewGroup root) {
return inflate(resource, root, root != null);
}
......
public View inflate(int resource, ViewGroup root, boolean attachToRoot) {
......
XmlResourceParser parser = getContext().getResources().getLayout(resource);
try {
return inflate(parser, root, attachToRoot);
} finally {
parser.close();
}
}
......
}
~~~
這個函數定義在文件frameworks/base/core/java/android/view/LayoutInflater.java中。
LayoutInflater類兩個參數版本的成員函數inflate通過調用三個參數版本的成員函數inflate來查找參數resource所描述的UI布局文件。
在LayoutInflater類三個參數版本的成員函數inflate中,首先是獲得用來描述當前運行上下文環境的一個Resources對象,然后接調用這個Resources對象的成員函數getLayout來查找參數resource所描述的UI布局文件。
Resources類的成員函數getLayout找到了指定的UI布局文件之后,就會打開它。由于Android系統的UI布局文件是一個Xml文件,因此,Resources類的成員函數getLayout打開它之后,得到的是一個XmlResourceParser對象。有了這個XmlResourceParser對象之后,LayoutInflater類三個參數版本的成員函數inflate就將它傳遞給另外一個三個參數版本的成員函數inflate,以便后者可以通過它來創建一個UI界面。
接下來,我們就首先分析Resources類的成員函數getLayout的實現,然后再分析LayoutInflater類的另外一個三個參數版本的成員函數inflate的實現。
#### **Step 4. Resources.getLayout**
~~~
public class Resources {
......
public XmlResourceParser getLayout(int id) throws NotFoundException {
return loadXmlResourceParser(id, "layout");
}
......
}
~~~
這個函數定義在文件frameworks/base/core/java/android/content/res/Resources.java中。
Resources類的成員函數getLayout的實現很簡單,它通過調用另外一個成員函數loadXmlResourceParser來查找并且打開由參數id所描述的一個UI布局文件。
#### **Step 5. Resources.loadXmlResourceParser**
~~~
public class Resources {
......
/*package*/ XmlResourceParser loadXmlResourceParser(int id, String type)
throws NotFoundException {
synchronized (mTmpValue) {
TypedValue value = mTmpValue;
getValue(id, value, true);
if (value.type == TypedValue.TYPE_STRING) {
return loadXmlResourceParser(value.string.toString(), id,
value.assetCookie, type);
}
throw new NotFoundException(
"Resource ID #0x" + Integer.toHexString(id) + " type #0x"
+ Integer.toHexString(value.type) + " is not valid");
}
}
......
}
~~~
這個函數定義在文件frameworks/base/core/java/android/content/res/Resources.java中。
參數id描述的是一個資源ID,Resources類的成員函數loadXmlResourceParser首先調用另外一個成員函數getValue來獲得該資源ID所對應的資源值,并且保存在一個類型為TypedValue的變量value中。在我們這個情景中,參數id描述的是一個類型為layout的資源ID,從前面[Android應用程序資源的編譯和打包過程分析](http://blog.csdn.net/luoshengyang/article/details/8744683)一文可以知道,類型為layout的資源ID對應的資源值即為一個UI布局文件名稱。有了這個UI布局文件名稱之后,Resources類的成員函數loadXmlResourceParser接著再調用另外一個四個參數版本的成員函數loadXmlResourceParser來加載對應的UI布局文件,并且得到一個XmlResourceParser對象返回給調用者。
注意,如果Resources類的成員函數getValue沒有找到與參數id所描述的資源,或者找到的資源的值不是字符串類型的,那么Resources類的成員函數loadXmlResourceParser就會拋出一個類型為NotFoundException的異常。
接下來,我們就首先分析Resources類的成員函數getValue的實現,接著再分析Resources類四個參數版本的成員函數loadXmlResourceParser的實現。
#### **Step 6. Resources.getValue**
~~~
public class Resources {
......
/*package*/ final AssetManager mAssets;
......
public void getValue(int id, TypedValue outValue, boolean resolveRefs)
throws NotFoundException {
boolean found = mAssets.getResourceValue(id, outValue, resolveRefs);
if (found) {
return;
}
throw new NotFoundException("Resource ID #0x"
+ Integer.toHexString(id));
}
......
}
~~~
這個函數定義在文件frameworks/base/core/java/android/content/res/Resources.java中。
Resources類的成員變量mAssets指向的是一個AssetManager對象,Resources類的成員函數getValue通過調用它的成員函數getResourceValue來獲得與參數id所對應的資源的值。注意,如果AssetManager類的成員函數getResourceValue查找不到與參數id所對應的資源,那么Resources類的成員函數getValue就會拋出一個類型為NotFoundException的異常。
接下來,我們就繼續分析AssetManager類的成員函數getResourceValue的實現。
#### **Step 7. AssetManager.getResourceValue**
~~~
public final class AssetManager {
......
private StringBlock mStringBlocks[] = null;
......
/*package*/ final boolean getResourceValue(int ident,
TypedValue outValue,
boolean resolveRefs)
{
int block = loadResourceValue(ident, outValue, resolveRefs);
if (block >= 0) {
if (outValue.type != TypedValue.TYPE_STRING) {
return true;
}
outValue.string = mStringBlocks[block].get(outValue.data);
return true;
}
return false;
}
......
}
~~~
這個函數定義在文件frameworks/base/core/java/android/content/res/AssetManager.java中。
AssetManager類的成員函數getResourceValue通過調用另外一個成員函數loadResourceValue來加載參數ident所描述的資源。如果加載成功,那么結果就會保存在參數outValue所描述的一個TypedValue對象中,并且AssetManager類的成員函數loadResourceValue的返回值block大于等于0。
從前面[Android應用程序資源管理器(Asset Manager)的創建過程分析](http://blog.csdn.net/luoshengyang/article/details/8791064)一文可以知道,AssetManager類的成員變量mStringBlock描述的是一個StringBlock數組。這個StringBlock數組中的每一個StringBlock對象描述的都是當前應用程序使用的每一個資源索引表的資源項值字符串資源池。關于資源索引表的格式以及生成過程,可以參考前面[Android應用程序資源的編譯和打包過程分析](http://blog.csdn.net/luoshengyang/article/details/8744683)一文。
了解了上述背景之后,我們就可以知道,當AssetManager類的成員函數loadResourceValue的返回值block大于等于0的時候,實際上就表示參數ident所描述的資源項在當前應用程序使用的第block個資源索引表中,而當參數ident所描述的資源項是一個字符串時,那么就可以在第block個資源索引表的資源項值字符串資源池中找到對應的字符串,并且保存在參數outValue所描述的一個TypedValue對象的成員變量string中,以便返回給調用者使用。注意,最終得到的字符串在第block個資源索引表的資源項值字符串資源池中的位置就保存在參數outValue所描述的一個TypedValue對象的成員變量data中。
接下來,我們就繼續分析AssetManager類的成員函數loadResourceValue的實現。
#### **Step 8. AssetManager.loadResourceValue**
~~~
public final class AssetManager {
......
/** Returns true if the resource was found, filling in mRetStringBlock and
* mRetData. */
private native final int loadResourceValue(int ident, TypedValue outValue,
boolean resolve);
......
}
~~~
這個函數定義在文件frameworks/base/core/java/android/content/res/AssetManager.java中。
AssetManager類的成員函數loadResourceValue是一個JNI方法,它是由C++層的函數android_content_AssetManager_loadResourceValue來實現的,如下所示:
~~~
static jint android_content_AssetManager_loadResourceValue(JNIEnv* env, jobject clazz,
jint ident,
jobject outValue,
jboolean resolve)
{
AssetManager* am = assetManagerForJavaObject(env, clazz);
if (am == NULL) {
return 0;
}
const ResTable& res(am->getResources());
Res_value value;
ResTable_config config;
uint32_t typeSpecFlags;
ssize_t block = res.getResource(ident, &value, false, &typeSpecFlags, &config);
......
uint32_t ref = ident;
if (resolve) {
block = res.resolveReference(&value, block, &ref);
......
}
return block >= 0 ? copyValue(env, outValue, &res, value, ref, block, typeSpecFlags, &config) : block;
}
~~~
這個函數定義在文件frameworks/base/core/jni/android_util_AssetManager.cpp中。
函數android_content_AssetManager_loadResourceValue主要是執行以下五個操作:
1. 調用函數assetManagerForJavaObject來將參數clazz所描述的一個Java層的AssetManager對象的成員變量mObject轉換為一個C++層的AssetManager對象。
2. 調用上述得到的C++層的AssetManager對象的成員函數getResources來獲得一個ResTable對象,這個ResTable對象描述的是一個資源表。
3. 調用上述得到的ResTable對象的成員函數getResource來獲得與參數ident所對應的資源項值及其配置信息,并且保存在類型為Res_value的變量value以及類型為ResTable_config的變量config中。
4. 如果參數resolve的值等于true,那么就繼續調用上述得到的ResTable對象的成員函數resolveReference來解析前面所得到的資源項值。
5. 調用函數copyValue將上述得到的資源項值及其配置信息拷貝到參數outValue所描述的一個Java層的TypedValue對象中去,返回調用者可以獲得與參數ident所對應的資源項內容。
接下來,我們就主要分析第2~4操作,即AssetManager對象的成員函數getResources以及ResTable類的成員函數getResource和resolveReference的實現,以便可以了解Android應用程序資源的查找過程。
#### **Step 9. AssetManager.getResources**
~~~
const ResTable& AssetManager::getResources(bool required) const
{
const ResTable* rt = getResTable(required);
return *rt;
}
~~~
這個函數定義在文件frameworks/base/libs/utils/AssetManager.cpp中。
AssetManager類的成員函數getResources通過調用另外一個成員函數getResTable來獲得當前應用程序所使用的資源表,后者的實現如下所示:
~~~
const ResTable* AssetManager::getResTable(bool required) const
{
ResTable* rt = mResources;
if (rt) {
return rt;
}
// Iterate through all asset packages, collecting resources from each.
AutoMutex _l(mLock);
if (mResources != NULL) {
return mResources;
}
......
const size_t N = mAssetPaths.size();
for (size_t i=0; i<N; i++) {
Asset* ass = NULL;
ResTable* sharedRes = NULL;
bool shared = true;
const asset_path& ap = mAssetPaths.itemAt(i);
Asset* idmap = openIdmapLocked(ap);
......
if (ap.type != kFileTypeDirectory) {
if (i == 0) {
// The first item is typically the framework resources,
// which we want to avoid parsing every time.
sharedRes = const_cast<AssetManager*>(this)->
mZipSet.getZipResourceTable(ap.path);
}
if (sharedRes == NULL) {
ass = const_cast<AssetManager*>(this)->
mZipSet.getZipResourceTableAsset(ap.path);
if (ass == NULL) {
......
ass = const_cast<AssetManager*>(this)->
openNonAssetInPathLocked("resources.arsc",
Asset::ACCESS_BUFFER,
ap);
if (ass != NULL && ass != kExcludedAsset) {
ass = const_cast<AssetManager*>(this)->
mZipSet.setZipResourceTableAsset(ap.path, ass);
}
}
if (i == 0 && ass != NULL) {
// If this is the first resource table in the asset
// manager, then we are going to cache it so that we
// can quickly copy it out for others.
LOGV("Creating shared resources for %s", ap.path.string());
sharedRes = new ResTable();
sharedRes->add(ass, (void*)(i+1), false, idmap);
sharedRes = const_cast<AssetManager*>(this)->
mZipSet.setZipResourceTable(ap.path, sharedRes);
}
}
} else {
......
Asset* ass = const_cast<AssetManager*>(this)->
openNonAssetInPathLocked("resources.arsc",
Asset::ACCESS_BUFFER,
ap);
shared = false;
}
if ((ass != NULL || sharedRes != NULL) && ass != kExcludedAsset) {
if (rt == NULL) {
mResources = rt = new ResTable();
updateResourceParamsLocked();
}
......
if (sharedRes != NULL) {
......
rt->add(sharedRes);
} else {
......
rt->add(ass, (void*)(i+1), !shared, idmap);
}
if (!shared) {
delete ass;
}
}
if (idmap != NULL) {
delete idmap;
}
}
......
if (!rt) {
mResources = rt = new ResTable();
}
return rt;
}
~~~
這個函數定義在文件frameworks/base/libs/utils/AssetManager.cpp中。
AssetManager類的成員函數getResources的實現看起來比較復雜,但是它要做的事情就是解析當前應用程序所使用的資源包里面的resources.arsc文件。從前面[Android應用程序資源管理器(Asset Manager)的創建過程分析](http://blog.csdn.net/luoshengyang/article/details/8791064)一文可以知道,當前應用程序所使用的資源包有兩個,其中一個是系統資源包,即/system/framework/framework-res.apk,另外一個就是自己的APK文件。這些APK文件的路徑都分別使用一個asset_path對象來描述,并且保存在AssetManager類的成員變量mAssetPaths中。
AssetManager類的成員變量mResources指向的是一個ResTable對象,如果它的值不等于NULL,那么就說明當前應用程序已經解析過它使用的資源包里面的resources.arsc文件,因此,這時候AssetManager類的成員函數getResources就可以直接將該ResTable對象返回給調用者。
如果當前應用程序還沒有解析過它使用的資源包里面的resources.arsc文件,那么AssetManager類的成員函數getResources就會先獲取由成員變量mLock所描述的一個互斥鎖,避免多個線程同時去解析當前應用程序還沒有解析過它使用的資源包里面的resources.arsc文件。注意,獲取鎖成功之后,有可能其它線程已經搶先一步解析了當前應用程序使用的資源包里面的resources.arsc文件了,因此,這時候就需要再次判斷 AssetManager類的成員函數mResources是否等于NULL。如果不等于NULL,就可以將它所指向的ResTable對象返回給調用者了。
AssetManager類的成員函數getResources接下來按照以下步驟來解析當前應用程序所使用的每一個資源包里面的resources.arsc文件:
1. 檢查資源包里面的resources.arsc文件已經提取出來。如果已經提取出來的話,那么以當前正在處理的資源包路徑為參數,調用當前正在處理的AssetManager對象的成員變量mZipSet所指向的一個ZipSet對象的成員函數getZipResourceTableAsset就可以獲得一個對應的Asset對象。
2. 如果資源包里面的resources.arsc文件還沒有提取出來,那么就會調用當前正在處理的AssetManager對象的成員函數openNonAssetInPathLocked來將該resources.arsc文件提取出來。提取的結果就是獲得一個對應的Asset對象,保存在變量ass中。注意,如果當前提取出來的Asset對象的地址值不等于全局變量kExcludedAsset的值,那么就將該Asset對象設置為當前正在處理的AssetManager對象的成員變量mZipSet所指向的一個ZipSet對象中去,這是通過調用該ZipSet對象的成員函數setZipResourceTableAsset來實現的。
3. 將上面獲得的用來描述resources.arsc文件的Asset對象ass添加到變量rt所描述的一個ResTable對象中去,這是通過調用該ResTable對象的成員函數add來實現的。注意,如果該ResTable對象還沒有創建,那么它就會首先被創建,并且同時保存在AssetManager類的成員變量mResources和變量rt中。另外一個地方需要注意的是,ResTable類的成員函數add在增加一個Asset對象時,會對該Asset對象所描述的resources.arsc文件的內容進行解析,結果就是得到一個系列的Package信息。每一個Package又包含了一個資源類型字符串資源池和一個資源項名稱字符串資源池,以及一系列的資源類型規范數據塊和一系列的資源項數據塊。這些內容可以參考前面[Android應用程序資源的編譯和打包過程分析](http://blog.csdn.net/luoshengyang/article/details/8744683)一文。還有第三個地方需要注意的是,每一個資源包里面的所有Pacakge形成一個PackageGroup,保存在變量rt所指向的一個ResTable對象的成員變量mPackageGroups中。總之,ResTable類的作用就類似于在前面[Android應用程序資源的編譯和打包過程分析](http://blog.csdn.net/luoshengyang/article/details/8744683)一文所介紹的ResourceTable類。
一般來說,在AssetManager類的成員變量mAssetPaths中,第一個資源包路徑指向的就是系統資源包,而系統資源包在當前正在處理的AssetManager對象創建的時候,可能就已經提取或者初始化過了,也就是它的resources.arsc文件已經提取或者解析過了。如果已經解析過,那么在代碼中的for循環中,當i等于0時,調用當前正在處理的AssetManager對象的成員變量mZipSet所指向的一個ZipSet對象的成員函數getZipResourceTable就可以獲得一個ResTable對象,并且保存在變量sharedRes中,最終就可以直接將該ResTable對象添加到變量rt所指向的一個ResTable對象中去,也就是添加到AssetManager類的成員變量mResources所指向的一個ResTable對象中去。如果只是提取過,但是還沒有解析過,那么就會首先對它進行解析,并且將得到的ResTable對象保存在變量sharedRes中,最后再將該ResTable對象添加到變量rt所指向的一個ResTable對象中去。
此外,AssetManager類的成員變量mAssetPaths保存的資源包路徑指向可能不是一個APK文件,而是一個目錄文件。在這種情況下,AssetManager類的成員函數getResources就會直接調用另外一個成員函數openNonAssetInPathLocked來打開該目錄下的resources.arsc文件,并且獲得一個Asset對象,同樣是保存在變量ass中。
在前面[Android應用程序資源管理器(Asset Manager)的創建過程分析](http://blog.csdn.net/luoshengyang/article/details/8791064)一文還提到,Android系統的資源管理框架提供了一種idmap機制,用來個性化定制一個資源包里面已有的資源項,也就是說,每一個資源包都可能有一個對應的idmap文件,用來描述它所個性化定制的資源項。在提取和解析資源包的過程中,如果該資源包存在idmap文件,那么該idmap文件也會被解析,并且解析得到的一個Asset對象也會同時被增加到變量rt所指向的一個ResTable對象中去。
經過上述的一系列操作之后,AssetManager類的成員變量mResources所指向的一個ResTable對象就包含了當前應用程序所使用的資源包的所有信息,該ResTable對象最后就會返回給調用者來使用。
這一步執行完成之后,回到前面的Step 8中,即AssetManager類的成員函數loadResourceValue中,接下來它就會調用前面所獲得的一個ResTable對象的成員函數getResource來獲得指定的資源項內容。
#### **Step 10. ResTable.getResource**
~~~
ssize_t ResTable::getResource(uint32_t resID, Res_value* outValue, bool mayBeBag,
uint32_t* outSpecFlags, ResTable_config* outConfig) const
{
......
const ssize_t p = getResourcePackageIndex(resID);
const int t = Res_GETTYPE(resID);
const int e = Res_GETENTRY(resID);
......
const Res_value* bestValue = NULL;
const Package* bestPackage = NULL;
ResTable_config bestItem;
memset(&bestItem, 0, sizeof(bestItem)); // make the compiler shut up
if (outSpecFlags != NULL) *outSpecFlags = 0;
// Look through all resource packages, starting with the most
// recently added.
const PackageGroup* const grp = mPackageGroups[p];
......
size_t ip = grp->packages.size();
while (ip > 0) {
ip--;
int T = t;
int E = e;
const Package* const package = grp->packages[ip];
if (package->header->resourceIDMap) {
uint32_t overlayResID = 0x0;
status_t retval = idmapLookup(package->header->resourceIDMap,
package->header->resourceIDMapSize,
resID, &overlayResID);
if (retval == NO_ERROR && overlayResID != 0x0) {
// for this loop iteration, this is the type and entry we really want
......
T = Res_GETTYPE(overlayResID);
E = Res_GETENTRY(overlayResID);
} else {
// resource not present in overlay package, continue with the next package
continue;
}
}
const ResTable_type* type;
const ResTable_entry* entry;
const Type* typeClass;
ssize_t offset = getEntry(package, T, E, &mParams, &type, &entry, &typeClass);
if (offset <= 0) {
// No {entry, appropriate config} pair found in package. If this
// package is an overlay package (ip != 0), this simply means the
// overlay package did not specify a default.
// Non-overlay packages are still required to provide a default.
if (offset < 0 && ip == 0) {
......
return offset;
}
continue;
}
if ((dtohs(entry->flags)&entry->FLAG_COMPLEX) != 0) {
......
continue;
}
......
const Res_value* item =
(const Res_value*)(((const uint8_t*)type) + offset);
ResTable_config thisConfig;
thisConfig.copyFromDtoH(type->config);
if (outSpecFlags != NULL) {
if (typeClass->typeSpecFlags != NULL) {
*outSpecFlags |= dtohl(typeClass->typeSpecFlags[E]);
} else {
*outSpecFlags = -1;
}
}
if (bestPackage != NULL &&
(bestItem.isMoreSpecificThan(thisConfig) || bestItem.diff(thisConfig) == 0)) {
// Discard thisConfig not only if bestItem is more specific, but also if the two configs
// are identical (diff == 0), or overlay packages will not take effect.
continue;
}
bestItem = thisConfig;
bestValue = item;
bestPackage = package;
}
......
if (bestValue) {
outValue->size = dtohs(bestValue->size);
outValue->res0 = bestValue->res0;
outValue->dataType = bestValue->dataType;
outValue->data = dtohl(bestValue->data);
if (outConfig != NULL) {
*outConfig = bestItem;
}
......
return bestPackage->header->index;
}
return BAD_VALUE;
}
~~~
這個函數定義在文件frameworks/base/libs/utils/ResourceTypes.cpp中。
參數resID描述的是要查找的資源的ID,ResTable類的成員函數getResource分別獲得它的Pakcage ID、Type ID以及Entry ID,保存在變量p、t以及e中。知道了Pakcage ID之后,就可以在ResTable類的成員變量mPackageGroups中找到對應的PakcageGroup。
注意,前面獲得的PakcageGroup可能包含有多個Package,這些Package都保存在PakcageGroup的成員變量packages所描述的一個數組中,因此,ResTable類的成員函數getResource就通過一個while循環來在每一個Package中查找最符合條件的資源項。
如果當前正在處理的Package的成員變量header所描述的一個Header對象的成員變量resourceIDMap的值不等于NULL,那么它所指向的就是一個idmap,同時也說明當前正在處理的Package是一個Overlay Package,也就是說,它是用來覆蓋已存在的資源項的。在這種情況下,ResTable類的成員函數getResource就會將參數resID所描述的資源ID映射為覆蓋后的資源ID。注意,如果不能將參數resID所描述的資源ID映射為覆蓋后的ID,那么當前正在處理的Package就會被跳過。
無論當前正在處理的Package是否是一個Overlay Package,最要要找到的資源項的Type ID和Entry ID都保存在變量T和E中,接下來ResTable類的成員函數getResource就會以這兩個變量為參數來調用另外一個成員函數getEntry來在當前正在處理的Package中檢查是否存在符合條件的資源項。如果存在的話,那么調用ResTable類的成員函數getEntry得到的返回值offset就會大于0,同時還會得到三個類型分別為ResTable_type、ResTable_entry和Type結構體,分別保存在變量type、entry和typeClass中。其中,ResTable_type用來描述一個資源類型,ResTable_entry用來描述一個資源項,而Type用來描述一個資源類型規范,關于這些結構的詳細解釋可以參考前面[Android應用程序資源的編譯和打包過程分析](http://blog.csdn.net/luoshengyang/article/details/8744683)一文。
從前面[Android應用程序資源的編譯和打包過程分析](http://blog.csdn.net/luoshengyang/article/details/8744683)一文還可以知道,對于一個普通的資源項來說,它在資源表文件resources.arsc中,由一個ResTable_entry和一個Res_value結構體前面連接在一起表示,其中,結構體ResTable_entry用來描述資源項的頭部,而結構體Res_value用來描述資源項的值,并且這兩個結構體是嵌入在一個ResTable_type結構體里面的。
理解了上述背景知道之后,我們就可以解釋前面得到的返回值offset的含義了,它表示一個Res_value結構體在一個ResTable_type結構體中的偏移,也就是說,將前面獲得的ResTable_type結構體type的開始地址,再加偏移量offset,就可以得到一個Res_value結構體item,而這個Res_value結構體就是表示資源ID等于resID的資源項的值。
注意,在調用ResTable類的成員函數getEntry來在當前正在處理的Package中查找與參數resID對應的資源項時,還會指定設備的當前配置信息。設備的當前配置信息是由ResTable類的成員變量mParams所指向的一個ResTable_config結構體來描述的。如果ResTable類的成員函數getEntry能成功找到一個匹配的資源項,那么它還會通過ResTable_type結構體type的成員變量config所指向的一個ResTable_config結構體來返回該資源項的實際配置信息,并且保存在另外一個ResTable_config結構體thisConfig中。
現在一切就準備就緒了,ResTable類的成員函數getResource要做的事情就是比較在前后兩個Package中找到的兩個資源項中,哪一個資源項更匹配設備的當前配置信息。注意,在前一個Package中找到的資源項的值及其所對應的Package和配置信息分別保存在Res_value結構體bestValue、Package結構體bestPackage和ResTable_config結構體bestItem中,而在后一個Package中找到的資源項對應的配置信息保存在ResTable_config結構體thisConfig中。
如果ResTable_config結構體bestItem描述的配置信息比ResTable_config結構體thisConfig描述的配置信息更具體,或者它們完全是一樣的,那么ResTable類的成員函數getResource就會認為在前一個Package中找到的資源項更匹配設備的當前配置信息,于是就保持Res_value結構體bestValue、Package結構體bestPackage和ResTable_config結構體bestItem的值不變,否則的話,就會將在后一個Package中找到的資源項的值及其所對應的Package和配置信息分別保存在Res_value結構體bestValue、Package結構體bestPackage和ResTable_config結構體bestItem中。
ResTable類的成員函數getResource執行完成中間的while循環之后,最終得到的資源項的值以及配置信息就保存在Res_value結構體bestValue和ResTable_config結構體bestItem,最后就可以分別將它們的內容拷貝到輸出參數outValue和outConfig中去,以便可以返回給調用者。同時,ResTable類的成員函數getResource還會將最終得到的資源項所在的Package的索引返回給調用者。通過這個Package的索引值,調用者就可以知道它所找到的資源項是在哪一個資源包中找到的,例如,是在系統資源包找到的,還是在應用程序本身的資源包找到的。
事實上,ResTable類的成員函數getResource返回給調用者的還有一個很重要的信息,那就是參數resID所描述的資源項的配置狀況,也就是說,參數resID所描述的資源項的配置差異性信息。這個配置差異性信息就保存在前面得到的Type結構體typeClass的成員變量typeSpecFlags所描述的一個uint32_t數組中的第E個元素中,這是因為參數resID所描述的資源項的Entry ID等于E。關于資源項的配置差異性信息的詳細描述,可以參考前面[Android應用程序資源的編譯和打包過程分析](http://blog.csdn.net/luoshengyang/article/details/8744683)一文所提到的ResTable_typeSpec結構體。
在ResTable類的成員函數getResource查找資源的過程中,還有兩個地方是需要注意的。
第一個地方是ResTable類的成員函數getResource是從后往前遍歷Pakcage ID等于p的PackageGroup中的每一個Package的。在一個PackageGroup中,第一個Package是一個Base Package,其它的Package都是屬于Overlay Package,其中,在Overlay Package中定義的資源是用來覆蓋在Base Package中定義的資源的。如果ResTable類的成員函數getResource在調用另外一個成員函數getEntry來在某一個package中找不到對應的資源項時,即調用ResTable類的成員函數getEntry得到的返回值offset小于等于0的時候,需要進一步檢查該package是一個Base Package還是一個Overlay Package。如果是一個Overlay Package,那么這種情況是允許發生的,因為一個Overlay Package只需要定義它需要覆蓋的資源。另一方面,如果是一個Base Package,那么就是這種情況就是異常的,因為一個Base Package無論如何都要保證在給定一個合法的資源ID的前提下,一定可以找到一個對應的資源項,在最壞的情況下,這個資源項就是一個default類型的。
第二個地方是只有當調用ResTable類的成員函數getEntry得到資源項是一個普通資源項時,即得到的ResTable_entry結構體entry的成員變量flags的值的FLAG_COMPLEX位等于0時,才可以將得到的ResTable_type結構體type的偏移位置offset轉換為一個Res_value結構體來訪問。這是因為如果找到的資源項不是一個普通資源項,而是一個Bag資源項時,得到的ResTable_type結構體type的偏移位置offset是一個ResTable_map結構體數組,而不是一個Res_value結構體,這一點可以參考前面[Android應用程序資源的編譯和打包過程分析](http://blog.csdn.net/luoshengyang/article/details/8744683)一文。
接下來,我們就繼續分析ResTable類的成員函數getEntry的實現,以便可以了解它是如何在一個Package中找到指定Type ID、Entry ID以及配置信息的資源項的,如下所示:
~~~
ssize_t ResTable::getEntry(
const Package* package, int typeIndex, int entryIndex,
const ResTable_config* config,
const ResTable_type** outType, const ResTable_entry** outEntry,
const Type** outTypeClass) const
{
......
const ResTable_package* const pkg = package->package;
const Type* allTypes = package->getType(typeIndex);
......
const ResTable_type* type = NULL;
uint32_t offset = ResTable_type::NO_ENTRY;
ResTable_config bestConfig;
memset(&bestConfig, 0, sizeof(bestConfig)); // make the compiler shut up
const size_t NT = allTypes->configs.size();
for (size_t i=0; i<NT; i++) {
const ResTable_type* const thisType = allTypes->configs[i];
if (thisType == NULL) continue;
ResTable_config thisConfig;
thisConfig.copyFromDtoH(thisType->config);
......
// Check to make sure this one is valid for the current parameters.
if (config && !thisConfig.match(*config)) {
......
continue;
}
// Check if there is the desired entry in this type.
const uint8_t* const end = ((const uint8_t*)thisType)
+ dtohl(thisType->header.size);
const uint32_t* const eindex = (const uint32_t*)
(((const uint8_t*)thisType) + dtohs(thisType->header.headerSize));
uint32_t thisOffset = dtohl(eindex[entryIndex]);
if (thisOffset == ResTable_type::NO_ENTRY) {
......
continue;
}
if (type != NULL) {
// Check if this one is less specific than the last found. If so,
// we will skip it. We check starting with things we most care
// about to those we least care about.
if (!thisConfig.isBetterThan(bestConfig, config)) {
......
continue;
}
}
type = thisType;
offset = thisOffset;
bestConfig = thisConfig;
......
if (!config) break;
}
if (type == NULL) {
......
return BAD_INDEX;
}
offset += dtohl(type->entriesStart);
......
const ResTable_entry* const entry = (const ResTable_entry*)
(((const uint8_t*)type) + offset);
......
*outType = type;
*outEntry = entry;
if (outTypeClass != NULL) {
*outTypeClass = allTypes;
}
return offset + dtohs(entry->size);
}
~~~
這個函數定義在文件frameworks/base/libs/utils/ResourceTypes.cpp中。
ResTable類的成員函數getEntry首先是在參數pakcage所描述的一個Package中找到與參數typeIndex所對應的一個Type結構體allTypes。這個Type結構體綜合了我們在前面[Android應用程序資源的編譯和打包過程分析](http://blog.csdn.net/luoshengyang/article/details/8744683)一文所描述的資源項的ResTable_typeSpec數據塊和ResTable_type數據塊,也就是,這個Type結構體的成員變量typeSpecFlags所指向的一個uint32_t數組描述了同一種類型的所有資源項的配置差異性性信息,即相當于一系列ResTable_typeSpec數據塊,而成員變量configs所指向的一個ResTable_type數組描述的同一種類型的資源項的具體內容,即相當于一系列ResTable_type數據塊。
結合前面[Android應用程序資源的編譯和打包過程分析](http://blog.csdn.net/luoshengyang/article/details/8744683)一文對ResTable_typeSpec數據塊和ResTable_type數據塊的描述,我們就可以比較容易理解ResTable類的成員函數getEntry的實現了。由于Type結構體allTypes的成員變量configs所指向的一個ResTable_type數組描述的所有類型為typeIndex的資源項的具體內容,因此,ResTable類的成員函數getEntry只要遍歷這個ResTable_type數組,并且找到與參數entryIdex所對應的最匹配資源項即可,也就是最能匹配參數config所描述的配置信息的資源項即可。
我們知道,在每一個ResTable_type結構體中,都包含有一個類型為uint32_t的偏移數組。偏移數組中的第entryIndex個元素的值就表示Entry ID等于entryIndex的資源項的具體內容相對于ResTable_type結構體的偏移值。有了這個偏移值之后,我們就得到Entry ID等于entryIndex的資源項的具體內容了,也就是得到一個對應的ResTable_entry結構體。
注意,每一個ResTable_type結構體都有一個成員變量config,它描述的是一個ResTable_config對象,表示該ResTable_type結構體的配置信息。如果該配置信息要求的配置信息不匹配,也就是與參數config所描述的設置配置信息不匹配,那么就不需要進一步檢查該ResTable_type結構體是否存在Entry ID等于entryIndex的資源項了。
如果一個ResTable_type結構體的配置信息與參數config所描述的設置配置信息匹配,但是它不存在Entry ID等于entryIndex的資源項,即它的偏移數組中的第entryIndex個元素的值等于ResTable_type::NO_ENTRY,那么也不需要進一步對該ResTable_type結構體進行處理了。
如果一個ResTable_type結構體的配置信息與參數config所描述的設置配置信息匹配,并且它也存在Entry ID等于entryIndex的資源項,那么就需要比較前后兩個ResTable_type結構體的配置信息與參數config所描述的設置配置信息相比,哪一個更具體一些。具有更具體的配置信息的資源項將作為最匹配的資源項返回給調用者。注意,前一個ResTable_type結構體及其配置信息分別保存在變量type和bestConfig中,并且在該ResTable_type結構體中Entry ID等于entryIndex的資源項相對它的偏移值保存在變量offset中。
遍歷完成上面所述的ResTable_type數組之后,最終得到的最區配資源項的相關信息就保存在變量type、bestConfig和offset中。這時候將變量type所描述的ResTable_type結構體的起始位置,加上它的成員變量entriesStart的值,以及再加上變量offset的值,就可以得到一個對應的ResTable_entry結構體entry。這個ResTable_entry結構體entry就是用來描述最終在參數package所描述的Package中,找到與參數typeIndex、entryIndex和config最匹配的資源項的信息了。
最終,ResTable類的成員函數getEntry就將前面所得到的ResTable_entry結構體entry、ResTable_type結構體type以及Type結構體allTypes分別保存在輸出參數outEntry、outType和outTypeClass返回給調用者了。同時,ResTable類的成員函數getEntry還會將變量offset的值加上ResTable_entry結構體entry的大小的結果返回給調用者。調用都得到這個結果之后,就可以將它作為輸出參數outType所指向的ResTable_type結構體的偏移量,從而可以得到參數outEntry所指向的ResTable_entry結構體所描述的資源項的具體內容,也就是得到一個Res_value結構體,這個具體就可以參考前面對ResTable類的成員函數getResource的分析。
這一步執行完成之后,回到前面的Step 8中,即AssetManager類的成員函數loadResourceValue中,接下來它就會調用前面所獲得的一個ResTable對象的成員函數resolveReference來解析前面所獲得的資源項的內容了。
#### **Step 11. ResTable.resolveReference**
~~~
ssize_t ResTable::resolveReference(Res_value* value, ssize_t blockIndex,
uint32_t* outLastRef, uint32_t* inoutTypeSpecFlags,
ResTable_config* outConfig) const
{
int count=0;
while (blockIndex >= 0 && value->dataType == value->TYPE_REFERENCE
&& value->data != 0 && count < 20) {
if (outLastRef) *outLastRef = value->data;
uint32_t lastRef = value->data;
uint32_t newFlags = 0;
const ssize_t newIndex = getResource(value->data, value, true, &newFlags,
outConfig);
if (newIndex == BAD_INDEX) {
return BAD_INDEX;
}
......
//printf("Getting reference 0x%08x: newIndex=%d\n", value->data, newIndex);
if (inoutTypeSpecFlags != NULL) *inoutTypeSpecFlags |= newFlags;
if (newIndex < 0) {
// This can fail if the resource being referenced is a style...
// in this case, just return the reference, and expect the
// caller to deal with.
return blockIndex;
}
blockIndex = newIndex;
count++;
}
return blockIndex;
}
~~~
這個函數定義在文件frameworks/base/libs/utils/ResourceTypes.cpp中。
ResTable類的成員函數resolveReference的實現其實很簡單,它就是對參數value所描述的一個資源項值進行解析,前提是這個資源項值是一個引用,即value所指向的一個Res_value結構體的成員變量dataType的值等于TYPE_REFERENCE,因為如果不是引用類型的資源項值,就沒有必要解析了。
注意,一個資源項的值有可能是嵌套引用的,也就是可能是引用的引用,因此,ResTable類的成員函數resolveReference需要使用一個while循環來不斷地對參數value所描述的一個資源項值進行解析,直到最后一次解析出來的結果不是引用為止。但是,為了防止無限地解析下去,該while循環最多只允許執行20次。每一次解析都是調用ResTable類的成員函數getResource來實現的,這個成員函數我們在前面的Step 10中已經分析過了。
此外,上述while循環還有兩個條件需要滿足。第一個條件是每一次調用ResTable類的成員函數getResource來解析參數value所描述的資源項值之后,得到的返回值blockIndex都必須是大于等于0的,這是因為它表示該次解析是成功的。由于參數blockIndex最開始的值是由調用者傳進來的,因此,也要保證它最開始的值大于等于0,才會執行代碼中的while循環對參數value所描述的資源項值進行解析。第二個條件是參數value所描述的資源項的值不能等于0,即它所指向的一個Res_value結構體的成員變量data的值不能等于0,這是因為引用者都是不可能等于0的。
這一步執行完成之后,沿著調用路徑最終返回到前面的Step 5中,即Resources類的成員函數loadXmlResourceParser中,我們就可以得到參數id所描述的資源項的值了。在我們這個情景中,參數id描述的是一個layout資源ID,它所對應的資源項的值是一個字符串,這個字符串描述的便是一個UI布局文件,即一個經過編譯的、以二進制格式保存的Xml資源文件。有了這個Xml資源文件的路徑之后,Resources類的另外一個四個參數版本的成員函數loadXmlResourceParser就會被調用來對該Xml資源文件進行解析,以便可以得到一個UI布局視圖。
#### **Step 12. Resources.loadXmlResourceParser**
~~~
public class Resources {
......
private int mLastCachedXmlBlockIndex = -1;
private final int[] mCachedXmlBlockIds = { 0, 0, 0, 0 };
private final XmlBlock[] mCachedXmlBlocks = new XmlBlock[4];
......
/*package*/ XmlResourceParser loadXmlResourceParser(String file, int id,
int assetCookie, String type) throws NotFoundException {
if (id != 0) {
try {
// These may be compiled...
synchronized (mCachedXmlBlockIds) {
// First see if this block is in our cache.
final int num = mCachedXmlBlockIds.length;
for (int i=0; i<num; i++) {
if (mCachedXmlBlockIds[i] == id) {
......
return mCachedXmlBlocks[i].newParser();
}
}
// Not in the cache, create a new block and put it at
// the next slot in the cache.
XmlBlock block = mAssets.openXmlBlockAsset(
assetCookie, file);
if (block != null) {
int pos = mLastCachedXmlBlockIndex+1;
if (pos >= num) pos = 0;
mLastCachedXmlBlockIndex = pos;
XmlBlock oldBlock = mCachedXmlBlocks[pos];
if (oldBlock != null) {
oldBlock.close();
}
mCachedXmlBlockIds[pos] = id;
mCachedXmlBlocks[pos] = block;
......
return block.newParser();
}
}
} catch (Exception e) {
NotFoundException rnf = new NotFoundException(
"File " + file + " from xml type " + type + " resource ID #0x"
+ Integer.toHexString(id));
rnf.initCause(e);
throw rnf;
}
}
throw new NotFoundException(
"File " + file + " from xml type " + type + " resource ID #0x"
+ Integer.toHexString(id));
}
......
}
~~~
這個函數定義在文件frameworks/base/core/java/android/content/res/Resources.java中
Resources類有三個成員變量是與Xml資源文件緩存文件有關的,它們分別是mCachedXmlBlocks、mCachedXmlBlockIds和mLastCachedXmlBlockIndex。其中,mCachedXmlBlocks指向的是一個大小等于4的XmlBlock數組,mCachedXmlBlockIds指向的也是一個大小等于4的資源ID數組,而mLastCachedXmlBlockIndex表示上一次緩存的Xml資源文件在上述XmlBlock數組和資源ID數組中的索引。這就是說,Resources類最多可以緩存最近讀取的四個Xml資源文件的內容,讀取超過四個Xml資源文件之后 ,上述的XmlBlock數組和資源ID數組就會被循環利用。
理解了Resources類的上述三個成員變量的含義之后,Resources類的成員函數loadXmlResourceParser的實現就容易理解了。它首先是在成員變量mCachedXmlBlockIds所指向的資源ID數組中檢查是否存在一個資源ID與參數id所描述的資源ID相等。如果存在的話,那么就會在成員變量mCachedXmlBlocks所指向的XmlBlock數組中找到一個對應的XmlBlock對象,并且調用這個XmlBlock對象的成員函數newParser來創建一個XmlResourceParser對象返回給調用者。
如果在Resources類的成員變量mCachedXmlBlockIds所指向的資源ID數組找到對應的資源ID的話,那么Resources類的成員函數loadXmlResourceParser就會調用成員變量mAssets所指向的一個Java層的AssetManager對象的成員函數openXmlBlockAsset來打開參數file所指定的Xml資源文件,從而獲得一個XmlBlock對象block。這個XmlBlock對象block以及參數id所描述的資源ID同時也會被緩存在Resources類的成員變量mCachedXmlBlocks和mCachedXmlBlockIds所描述的XmlBlock數組和資源ID數組中。
最后,Resources類的成員函數loadXmlResourceParser就可以調用前面得到的XmlBlock對象block的成員函數newParser來創建一個XmlResourceParser對象返回給調用者。
接下來,我們就首先分析Java層的AssetManager類的成員函數openXmlBlockAsset的實現,接著再分析XmlBlock類的成員函數newParser的實現,以便可以了解一個Xml資源文件的打開過程。
#### **Step 13. AssetManager.openXmlBlockAsset**
~~~
public final class AssetManager {
......
/*package*/ final XmlBlock openXmlBlockAsset(int cookie, String fileName)
throws IOException {
synchronized (this) {
if (!mOpen) {
throw new RuntimeException("Assetmanager has been closed");
}
int xmlBlock = openXmlAssetNative(cookie, fileName);
if (xmlBlock != 0) {
XmlBlock res = new XmlBlock(this, xmlBlock);
incRefsLocked(res.hashCode());
return res;
}
}
throw new FileNotFoundException("Asset XML file: " + fileName);
}
......
}
~~~
這個函數定義在文件frameworks/base/core/java/android/content/res/AssetManager.java中。
AssetManager類的成員函數openXmlBlockAsset首先檢查成員變量mOpen的值是否不等于true。如果不等于true的話,那么就說明當前正在處理的AssetManager對象還沒有經過初始化,或者已經關閉了。
我們假設AssetManager類的成員變量mOpen的值等于true,那么接下來AssetManager類的成員函數openXmlBlockAsset就會調用另外一個成員函數openXmlAssetNative來打開參數fileName所指定的Xml資源文件。
成功打開參數fileName所指向的Xml資源文件之后,就會得到一個C++層的ResXMLTree對象的地址值xmlBlock,最后AssetManager類的成員函數openXmlBlockAsset就將該C++層的ResXMLTree對象的地址值封裝在一個Java層的XmlBlock對象中,并且將該XmlBlock對象返回給調用者。
接下來,我們就繼續分析AssetManager類的成員函數openXmlAssetNative的實現。
#### **Step 14. AssetManager.openXmlAssetNative**
~~~
public final class AssetManager {
......
private native final int openXmlAssetNative(int cookie, String fileName);
......
}
~~~
這個函數定義在文件frameworks/base/core/java/android/content/res/AssetManager.java中。
AssetManager類的成員函數openXmlAssetNative是一個JNI方法,它是C++層的函數android_content_AssetManager_openXmlAssetNative來實現的,如下所示:
~~~
static jint android_content_AssetManager_openXmlAssetNative(JNIEnv* env, jobject clazz,
jint cookie,
jstring fileName)
{
AssetManager* am = assetManagerForJavaObject(env, clazz);
......
const char* fileName8 = env->GetStringUTFChars(fileName, NULL);
Asset* a = cookie
? am->openNonAsset((void*)cookie, fileName8, Asset::ACCESS_BUFFER)
: am->openNonAsset(fileName8, Asset::ACCESS_BUFFER);
......
env->ReleaseStringUTFChars(fileName, fileName8);
ResXMLTree* block = new ResXMLTree();
status_t err = block->setTo(a->getBuffer(true), a->getLength(), true);
a->close();
delete a;
......
return (jint)block;
}
~~~
這個函數定義在文件frameworks/base/core/jni/android_util_AssetManager.cpp中。
函數android_content_AssetManager_openXmlAssetNative首先調用另外一個函數assetManagerForJavaObject來將參數clazz所指向的一個Java層的AssetManager對象成員變量mObject轉換為一個C++層的AssetManager對象。有了這個C++層的AssetManager對象之后,就可以調用它的成員函數openNonAsset來打開參數fileName所指定的Xml資源文件。
調用C++層的AssetManager對象的成員函數openNonAsset來成功地打開參數fileName所指定的Xml資源文件之后,函數android_content_AssetManager_openXmlAssetNative接下來就會創建一個ResXMLTree對象,并且將前面所打開的Xml資源文件的內容設置到該ResXMLTree對象中去,并且將該ResXMLTree對象的地址值返回給調用者。
假設參數cookie的值大于0,因此,函數android_content_AssetManager_openXmlAssetNative實際調用的是C++層的AssetManager類的三個參數版本的成員函數openNonAsset來打開參數fileName所指定的Xml資源文件,接下來,我們就分析這個函數的實現。
#### **Step 15. AssetManager.openNonAsset**
~~~
Asset* AssetManager::openNonAsset(void* cookie, const char* fileName, AccessMode mode)
{
const size_t which = ((size_t)cookie)-1;
AutoMutex _l(mLock);
......
if (which < mAssetPaths.size()) {
......
Asset* pAsset = openNonAssetInPathLocked(
fileName, mode, mAssetPaths.itemAt(which));
if (pAsset != NULL) {
return pAsset != kExcludedAsset ? pAsset : NULL;
}
}
return NULL;
}
~~~
這個函數定義在文件frameworks/base/libs/utils/AssetManager.cpp中。
參數cookie是用來標識另外一個參數fileName所指向的文件是屬于哪個APK文件的。從前面[Android應用程序資源管理器(Asset Manager)的創建過程分析](http://blog.csdn.net/luoshengyang/article/details/8791064)一文可以知道,參數cookie實際上是一個整數,將這個整數減去1之后,得到的結果就是參數fileName所指向的文件所屬于的APK文件在AssetManager類的成員變量mAssetPaths所描述的一個數組的索引。AssetManager類的成員變量mAssetPaths描述的是一個asset_path數組,數組中的每一個元素都是表示一個APK文件路徑,這些APK文件就包含了當前應用程序所要使用的資源包。
AssetManager類的成員函數openNonAsset通過參數cookie知道了當前要打開的文件是位于哪個APK文件之后,接著就繼續調用另外一個成員函數openNonAssetInPathLocked來打開該文件。
#### **Step 16. AssetManager.openNonAssetInPathLocked**
~~~
Asset* AssetManager::openNonAssetInPathLocked(const char* fileName, AccessMode mode,
const asset_path& ap)
{
Asset* pAsset = NULL;
/* look at the filesystem on disk */
if (ap.type == kFileTypeDirectory) {
String8 path(ap.path);
path.appendPath(fileName);
pAsset = openAssetFromFileLocked(path, mode);
if (pAsset == NULL) {
/* try again, this time with ".gz" */
path.append(".gz");
pAsset = openAssetFromFileLocked(path, mode);
}
if (pAsset != NULL) {
//printf("FOUND NA '%s' on disk\n", fileName);
pAsset->setAssetSource(path);
}
/* look inside the zip file */
} else {
String8 path(fileName);
/* check the appropriate Zip file */
ZipFileRO* pZip;
ZipEntryRO entry;
pZip = getZipFileLocked(ap);
if (pZip != NULL) {
//printf("GOT zip, checking NA '%s'\n", (const char*) path);
entry = pZip->findEntryByName(path.string());
if (entry != NULL) {
//printf("FOUND NA in Zip file for %s\n", appName ? appName : kAppCommon);
pAsset = openAssetFromZipLocked(pZip, entry, mode, path);
}
}
if (pAsset != NULL) {
/* create a "source" name, for debug/display */
pAsset->setAssetSource(
createZipSourceNameLocked(ZipSet::getPathName(ap.path.string()), String8(""),
String8(fileName)));
}
}
return pAsset;
}
~~~
這個函數定義在文件frameworks/base/libs/utils/AssetManager.cpp中。
AssetManager類的成員函數openNonAssetInPathLocked的實現是比較簡單的,它按照以下兩種方式來打開參數fileName所指向的文件。
如果參數ap描述的文件路徑是一個目錄,那么它就會直接將參數fileName所指向的文件名稱附加到該目錄后面去,然后直接調用另外一個成員函數openAssetFromFileLocked來打開參數fileName所指向的文件。如果打開失敗,那么就再假定參數ap描述的文件路徑是一個以“.gz“為后綴的壓縮包,然后再次調用成員函數openAssetFromFileLocked來打開參數fileName所指向的文件。
如果參數ap描述的文件路徑是一個普通文件,那么就意味著參數ap描述的是一個壓縮文件,因此,它就會先調用成員函數getZipFileLocked來打開該壓縮文件,然后再調用成員函數openAssetFromZipLocked來在該壓縮包將參數fileName所指向的文件提取出來。
Android應用程序的資源一般都是打包在一個APK文件里面的,而APK文件就是一個Zip格式的壓縮包,因此,AssetManager類的成員函數openNonAssetInPathLocked一般就是按照第二種方式來打開參數fileName所指向的文件。
接下來,我們就繼續分析AssetManager類的成員函數openAssetFromZipLocked的實現。
#### **Step 17. AssetManager.openAssetFromZipLocked**
~~~
Asset* AssetManager::openAssetFromZipLocked(const ZipFileRO* pZipFile,
const ZipEntryRO entry, AccessMode mode, const String8& entryName)
{
Asset* pAsset = NULL;
// TODO: look for previously-created shared memory slice?
int method;
size_t uncompressedLen;
......
if (!pZipFile->getEntryInfo(entry, &method, &uncompressedLen, NULL, NULL,
NULL, NULL))
{
LOGW("getEntryInfo failed\n");
return NULL;
}
FileMap* dataMap = pZipFile->createEntryFileMap(entry);
if (dataMap == NULL) {
LOGW("create map from entry failed\n");
return NULL;
}
if (method == ZipFileRO::kCompressStored) {
pAsset = Asset::createFromUncompressedMap(dataMap, mode);
LOGV("Opened uncompressed entry %s in zip %s mode %d: %p", entryName.string(),
dataMap->getFileName(), mode, pAsset);
} else {
pAsset = Asset::createFromCompressedMap(dataMap, method,
uncompressedLen, mode);
LOGV("Opened compressed entry %s in zip %s mode %d: %p", entryName.string(),
dataMap->getFileName(), mode, pAsset);
}
if (pAsset == NULL) {
/* unexpected */
LOGW("create from segment failed\n");
}
return pAsset;
}
~~~
這個函數定義在文件frameworks/base/libs/utils/AssetManager.cpp中。
AssetManager類的成員函數openAssetFromZipLocked的實現也很簡單,它無非就是從參數pZipFile所描述的壓縮包中將參數entryName所描述的文件提取出來,并且根據提取出來的內容保存在一個Asset對象中返回給調用者。
注意,參數entryName所描述的文件有可能是經過是經過壓縮后再打包到參數pZipFile所描述的壓縮包去的。在這種情況下,AssetManager類的成員函數openAssetFromZipLocked就會調用Asset類的靜態成員函數createFromCompressedMap來對它進行解壓,然后再將解壓完成后得到的內容保存在一個Asset對象中。否則的話,AssetManager類的成員函數openAssetFromZipLocked就會調用Asset類的靜態成員函數createFromUncompressedMap來將它的內容保存在一個Asset對象中。
這一步執行完成之后,返回到前面的Step 12中,即Resources類的成員函數loadXmlResourceParser中,接下來就會調用Java層的XmlBlock類的成員函數newParser來創建一個XmlResourceParser對象,以便可以用來解析前面打開的Xml資源文件。
#### **Step 18. XmlBlock.newParser**
~~~
final class XmlBlock {
......
public XmlResourceParser newParser() {
synchronized (this) {
if (mNative != 0) {
return new Parser(nativeCreateParseState(mNative), this);
}
return null;
}
}
......
private final int mNative;
......
private static final native int nativeCreateParseState(int obj);
......
}
~~~
這個函數定義在文件frameworks/base/core/java/android/content/res/XmlBlock.java中。
XmlBlock類的成員函數mNative指向的是C++層的一個ResXMLTree對象。這個ResXMLTree對象是在前面的Step 14中創建的,用來描述前面所打開的一個Xml資源文件。
XmlBlock類的成員函數newParser首先是以成員變量mNative所描述的一個C++層的ResXMLTree對象的地址為參數,來調用JNI方法nativeCreateParseState,用來在C++層創建一個ResXMLParser對象,最后再將該C++層的ResXMLParser對象封裝成Java層的一個Parser對象中,并且將該Parser對象返回給調用者。
這一步執行完成之后,返回到前面的Step 3中,即LayoutInflater類的成員函數inflate中,接下來它就會以前面所獲得的一個Java層的Parser對象來參數,來調用另外一個重載版本的成員函數inflate,用來解析前面所打開的Xml資源文件,即一個UI布局文件,以便可以創建相應的UI布局出來。
#### **Step 19. LayoutInflater.inflate**
~~~
public abstract class LayoutInflater {
......
public View inflate(XmlPullParser parser, ViewGroup root, boolean attachToRoot) {
synchronized (mConstructorArgs) {
final AttributeSet attrs = Xml.asAttributeSet(parser);
Context lastContext = (Context)mConstructorArgs[0];
mConstructorArgs[0] = mContext;
View result = root;
try {
// Look for the root node.
int type;
while ((type = parser.next()) != XmlPullParser.START_TAG &&
type != XmlPullParser.END_DOCUMENT) {
// Empty
}
if (type != XmlPullParser.START_TAG) {
throw new InflateException(parser.getPositionDescription()
+ ": No start tag found!");
}
final String name = parser.getName();
......
if (TAG_MERGE.equals(name)) {
......
rInflate(parser, root, attrs);
} else {
// Temp is the root view that was found in the xml
View temp = createViewFromTag(name, attrs);
ViewGroup.LayoutParams params = null;
if (root != null) {
......
// Create layout params that match root, if supplied
params = root.generateLayoutParams(attrs);
if (!attachToRoot) {
// Set the layout params for temp if we are not
// attaching. (If we are, we use addView, below)
temp.setLayoutParams(params);
}
}
......
// Inflate all children under temp
rInflate(parser, temp, attrs);
......
// We are supposed to attach all the views we found (int temp)
// to root. Do that now.
if (root != null && attachToRoot) {
root.addView(temp, params);
}
// Decide whether to return the root that was passed in or the
// top view found in xml.
if (root == null || !attachToRoot) {
result = temp;
}
}
} catch (XmlPullParserException e) {
......
} catch (IOException e) {
......
} finally {
......
}
return result;
}
}
......
}
~~~
這個函數定義在文件frameworks/base/core/java/android/view/LayoutInflater.java中。
LayoutInflater類的成員函數inflate主要負責處理前面所打開的Xml資源文件的根節點,然后再調用另外一個成員函數rInflate來處理根節點的子節點。每一個節點都表示一個UI控件,這個UI控件是通過調用LayoutInflater類的成員函數createViewFromTag來創建的。
LayoutInflater類的成員函數createViewFromTag需要兩個參數來創建一個UI控件。這兩個參數分別對應于當前正在處理的Xml節點的名稱以及屬性集。注意,如果參數root的值不等于null,那么它所描述的一個ViewGroup就是調用LayoutInflater類的成員函數createViewFromTag獲得的UI控件的父控件。因此,調用LayoutInflater類的成員函數createViewFromTag獲得的UI控件及其對應的布局參數,最后都需要添加到參數root所描述的一個ViewGroup中去。
有一種特殊情況,如果當前正在處理的Xml節點的名稱等于TAG_MERGE,即“merge”,那么就表示不用處理的當前正在處理的Xml節點,而是直接去處理當前正在處理的Xml節點的子節點。這是Android系統為提供的一種UI布局優化機制,實際上就是減少了一層UI嵌套,具體可以參考官方文檔:http://developer.android.com/training/improving-layouts/reusing-layouts.html。
LayoutInflater類的成員函數rInflate在處理當前節點的子節點的時候,也是通過調用成員函數createViewFromTag來創建相應的UI控件的,因此,接下來我們就主要分析LayoutInflater類的成員函數createViewFromTag的實現。
#### **Step 20. LayoutInflater.createViewFromTag**
~~~
public abstract class LayoutInflater {
......
private Factory mFactory;
......
View createViewFromTag(String name, AttributeSet attrs) {
if (name.equals("view")) {
name = attrs.getAttributeValue(null, "class");
}
......
try {
View view = (mFactory == null) ? null : mFactory.onCreateView(name,
mContext, attrs);
if (view == null) {
if (-1 == name.indexOf('.')) {
view = onCreateView(name, attrs);
} else {
view = createView(name, null, attrs);
}
}
......
return view;
} catch (InflateException e) {
......
} catch (ClassNotFoundException e) {
......
} catch (Exception e) {
......
}
}
......
}
~~~
這個函數定義在文件frameworks/base/core/java/android/view/LayoutInflater.java中。
參數name表示當前正在處理的Xml節點的名稱。如果它的值等于“view”的話,那么真正要創建的UI控件的類名記錄在參數attrs所描述的一個屬性集中的一個名稱為“class”的屬性中。因此,當參數name的值等于“view”的時候,LayoutInflater類的成員函數createViewFromTag首先要做的便是從參數attrs所描述的一個屬性集中獲取接下來真正要創建的UI控件的類名。
LayoutInflater類的成員函數createViewFromTag接下來檢查成員變量mFactory的值是否不等于null。如果不等于null的話,那么它就會指向一個Factory對象,該Factory對象描述的是一個UI控件創建工廠,專門用來負責創建UI控件。
如果LayoutInflater類的成員變量mFactory的值等于null,那么LayoutInflater類的成員函數createViewFromTag就會調用成員函數onCreateView或者createView來創建由參數name所指定的UI控件,取決于參數name是否包含了一個“.”字符。注意,如果參數name是否包含了一個“.”字符,那么就說明當前所創建的UI控件是一個用戶自定義的UI控件,也就是不是Android提供的標準控件。
我們假設LayoutInflater類的成員變量mFactory的值等于null,并且參數name沒有包含有“.”字符,那么LayoutInflater類的成員函數createViewFromTag最后就會調用成員函數onCreateView來創建由參數name所指定的UI控件。
LayoutInflater類的成員函數onCreateView是由其子類來重寫的。從前面的Step 2可以知道,當前正在處理的實際上是一個PhoneLayoutInflater對象。PhoneLayoutInflater類繼承了LayoutInflater類,并且重寫了成員函數onCreateView。因此,接下來我們就繼續分析PhoneLayoutInflater類的成員函數onCreateView的實現。
#### **Step 21. PhoneLayoutInflater.onCreateView**
~~~
public class PhoneLayoutInflater extends LayoutInflater {
private static final String[] sClassPrefixList = {
"android.widget.",
"android.webkit."
};
@Override protected View onCreateView(String name, AttributeSet attrs) throws ClassNotFoundException {
for (String prefix : sClassPrefixList) {
try {
View view = createView(name, prefix, attrs);
if (view != null) {
return view;
}
} catch (ClassNotFoundException e) {
// In this case we want to let the base class take a crack
// at it.
}
}
return super.onCreateView(name, attrs);
}
......
}
~~~
這個函數定義在文件frameworks/base/policy/src/com/android/internal/policy/impl/PhoneLayoutInflater.java中。
PhoneLayoutInflater類的成員函數onCreateView只負責創建兩類標準的UI控件,一種是屬于android.widget包的,另一種是屬于android.webkit包的,其中,優先創建android.widget包的UI控件。
如果參數name所描述的UI控件既不屬于android.widget包的,也不屬于android.webkit包的,那么 PhoneLayoutInflater類的成員函數onCreateView就會將創建UI控件的操作交給父類來處理,即通過調用父類的成員函數onCreateView來創建。
如果參數name所描述的UI控件是屬于android.widget包或者android.webkit包的,那么PhoneLayoutInflater類的成員函數onCreateView就會直接調用父類LayoutInflater的成員函數createView來創建參數name所描述的UI控件,因此,接下來我們就繼續分析LayoutInflater類的成員函數createView的實現。
#### **Step 22. LayoutInflater.createView**
~~~
public abstract class LayoutInflater {
......
private Filter mFilter;
private final Object[] mConstructorArgs = new Object[2];
......
private static final HashMap<String, Constructor> sConstructorMap =
new HashMap<String, Constructor>();
private HashMap<String, Boolean> mFilterMap;
......
public final View createView(String name, String prefix, AttributeSet attrs)
throws ClassNotFoundException, InflateException {
Constructor constructor = sConstructorMap.get(name);
Class clazz = null;
try {
if (constructor == null) {
// Class not found in the cache, see if it's real, and try to add it
clazz = mContext.getClassLoader().loadClass(
prefix != null ? (prefix + name) : name);
if (mFilter != null && clazz != null) {
boolean allowed = mFilter.onLoadClass(clazz);
if (!allowed) {
failNotAllowed(name, prefix, attrs);
}
}
constructor = clazz.getConstructor(mConstructorSignature);
sConstructorMap.put(name, constructor);
} else {
// If we have a filter, apply it to cached constructor
if (mFilter != null) {
// Have we seen this name before?
Boolean allowedState = mFilterMap.get(name);
if (allowedState == null) {
// New class -- remember whether it is allowed
clazz = mContext.getClassLoader().loadClass(
prefix != null ? (prefix + name) : name);
boolean allowed = clazz != null && mFilter.onLoadClass(clazz);
mFilterMap.put(name, allowed);
if (!allowed) {
failNotAllowed(name, prefix, attrs);
}
} else if (allowedState.equals(Boolean.FALSE)) {
failNotAllowed(name, prefix, attrs);
}
}
}
Object[] args = mConstructorArgs;
args[1] = attrs;
return (View) constructor.newInstance(args);
} catch (NoSuchMethodException e) {
......
} catch (ClassNotFoundException e) {
......
} catch (Exception e) {
......
}
}
......
}
~~~
這個函數定義在文件frameworks/base/core/java/android/view/LayoutInflater.java。
LayoutInflater類的靜態成員變量sConstructorMap指向的是一個HashMap。這個HashMap緩存了當前應用程序使用過的每一種類型的UI控件類的構造函數。這些構造函數必須具有兩個參數,其中第一個參數是一個Context,第二個參數是一個AttributeSet。LayoutInflater類的成員函數createView在創建一個UI控件的時候,就會將上述兩個參數保存在LayoutInflater類的成員變量mConstructorArgs所描述的一個大小為2的數組中,并且以這個數組為參數,來調用對應的UI控件類的構造函數。
LayoutInflater類的mFilter指向的是一個Filter對象。這個Filter對象描述的是一個過濾器,LayoutInflater類的成員函數createView在創建一個UI控件之前,會先調用該過濾器的成員函數onLoadClass,來詢問該過濾器是允許創建由參數name所描述的UI控件。如果不允許的話,LayoutInflater類的成員函數createView就會調用另外一個成員函數failNotAllowed來拋出一個異常。
為了避免每次創建一個UI控件時,都去詢問過濾器是否允許創建,LayoutInflater類的成員函數createView只會在第一次創建一個名稱為name的UI控件時,才會詢問過濾器,并且將詢問結果保存在成員變量mFilterMap所指向的一個HashMap。這樣當LayoutInflater類的成員函數createView以后再創建同名的UI控件時,就可以直接通過成員變量mFilterMap所指向的一個HashMap來知道該UI控件是否是允許創建的。
理解了LayoutInflater類的靜態成員變量sConstructorMap以及成員變量mConstructorArgs、mFilter和mFilterMap的含義之后,讀者就可以自己去理解LayoutInflater類的成員函數createView的實現了。這里需要重復強調的一點就是,我們在自定義一個UI控件的時候,一定要提供一個具有兩個參數類型分別為Context和AttributeSet的構造函數,否則的話,該自定義控件就不可以在UI布局文件中使用。
至此,我們就以layout資源為例,詳細地分析了Android應用程序資源的查找過程了,并且也完整地分析完成Android系統的資源管理框架了。重新學習Android系統的資源管理框架,請參考前面[Android資源管理框架(Asset Manager)簡要介紹和學習計劃](http://blog.csdn.net/luoshengyang/article/details/8738877)一文。
- 前言
- Android組件設計思想
- Android源代碼開發和調試環境搭建
- Android源代碼下載和編譯
- Android源代碼情景分析法
- Android源代碼調試分析法
- 手把手教你為手機編譯ROM
- 在Ubuntu上下載、編譯和安裝Android最新源代碼
- 在Ubuntu上下載、編譯和安裝Android最新內核源代碼(Linux Kernel)
- 如何單獨編譯Android源代碼中的模塊
- 在Ubuntu上為Android系統編寫Linux內核驅動程序
- 在Ubuntu上為Android系統內置C可執行程序測試Linux內核驅動程序
- 在Ubuntu上為Android增加硬件抽象層(HAL)模塊訪問Linux內核驅動程序
- 在Ubuntu為Android硬件抽象層(HAL)模塊編寫JNI方法提供Java訪問硬件服務接口
- 在Ubuntu上為Android系統的Application Frameworks層增加硬件訪問服務
- 在Ubuntu上為Android系統內置Java應用程序測試Application Frameworks層的硬件服務
- Android源代碼倉庫及其管理工具Repo分析
- Android編譯系統簡要介紹和學習計劃
- Android編譯系統環境初始化過程分析
- Android源代碼編譯命令m/mm/mmm/make分析
- Android系統鏡像文件的打包過程分析
- 從CM刷機過程和原理分析Android系統結構
- Android系統架構概述
- Android系統整體架構
- android專用驅動
- Android硬件抽象層HAL
- Android應用程序組件
- Android應用程序框架
- Android用戶界面架構
- Android虛擬機之Dalvik虛擬機
- Android硬件抽象層
- Android硬件抽象層(HAL)概要介紹和學習計劃
- Android專用驅動
- Android Logger驅動系統
- Android日志系統驅動程序Logger源代碼分析
- Android應用程序框架層和系統運行庫層日志系統源代碼分析
- Android日志系統Logcat源代碼簡要分析
- Android Binder驅動系統
- Android進程間通信(IPC)機制Binder簡要介紹和學習計劃
- 淺談Service Manager成為Android進程間通信(IPC)機制Binder守護進程之路
- 淺談Android系統進程間通信(IPC)機制Binder中的Server和Client獲得Service Manager接口之路
- Android系統進程間通信(IPC)機制Binder中的Server啟動過程源代碼分析
- Android系統進程間通信(IPC)機制Binder中的Client獲得Server遠程接口過程源代碼分析
- Android系統進程間通信Binder機制在應用程序框架層的Java接口源代碼分析
- Android Ashmem驅動系統
- Android系統匿名共享內存Ashmem(Anonymous Shared Memory)簡要介紹和學習計劃
- Android系統匿名共享內存Ashmem(Anonymous Shared Memory)驅動程序源代碼分析
- Android系統匿名共享內存Ashmem(Anonymous Shared Memory)在進程間共享的原理分析
- Android系統匿名共享內存(Anonymous Shared Memory)C++調用接口分析
- Android應用程序進程管理
- Android應用程序進程啟動過程的源代碼分析
- Android系統進程Zygote啟動過程的源代碼分析
- Android系統默認Home應用程序(Launcher)的啟動過程源代碼分析
- Android應用程序消息機制
- Android應用程序消息處理機制(Looper、Handler)分析
- Android應用程序線程消息循環模型分析
- Android應用程序輸入事件分發和處理機制
- Android應用程序鍵盤(Keyboard)消息處理機制分析
- Android應用程序UI架構
- Android系統的開機畫面顯示過程分析
- Android幀緩沖區(Frame Buffer)硬件抽象層(HAL)模塊Gralloc的實現原理分析
- SurfaceFlinger
- Android系統Surface機制的SurfaceFlinger服務
- SurfaceFlinger服務簡要介紹和學習計劃
- 啟動過程分析
- 對幀緩沖區(Frame Buffer)的管理分析
- 線程模型分析
- 渲染應用程序UI的過程分析
- Android應用程序與SurfaceFlinger服務的關系
- 概述和學習計劃
- 連接過程分析
- 共享UI元數據(SharedClient)的創建過程分析
- 創建Surface的過程分析
- 渲染Surface的過程分析
- Android應用程序窗口(Activity)
- 實現框架簡要介紹和學習計劃
- 運行上下文環境(Context)的創建過程分析
- 窗口對象(Window)的創建過程分析
- 視圖對象(View)的創建過程分析
- 與WindowManagerService服務的連接過程分析
- 繪圖表面(Surface)的創建過程分析
- 測量(Measure)、布局(Layout)和繪制(Draw)過程分析
- WindowManagerService
- WindowManagerService的簡要介紹和學習計劃
- 計算Activity窗口大小的過程分析
- 對窗口的組織方式分析
- 對輸入法窗口(Input Method Window)的管理分析
- 對壁紙窗口(Wallpaper Window)的管理分析
- 計算窗口Z軸位置的過程分析
- 顯示Activity組件的啟動窗口(Starting Window)的過程分析
- 切換Activity窗口(App Transition)的過程分析
- 顯示窗口動畫的原理分析
- Android控件TextView的實現原理分析
- Android視圖SurfaceView的實現原理分析
- Android應用程序UI硬件加速渲染
- 簡要介紹和學習計劃
- 環境初始化過程分析
- 預加載資源地圖集服務(Asset Atlas Service)分析
- Display List構建過程分析
- Display List渲染過程分析
- 動畫執行過程分析
- Android應用程序資源管理框架
- Android資源管理框架(Asset Manager)
- Asset Manager 簡要介紹和學習計劃
- 編譯和打包過程分析
- Asset Manager的創建過程分析
- 查找過程分析
- Dalvik虛擬機和ART虛擬機
- Dalvik虛擬機
- Dalvik虛擬機簡要介紹和學習計劃
- Dalvik虛擬機的啟動過程分析
- Dalvik虛擬機的運行過程分析
- Dalvik虛擬機JNI方法的注冊過程分析
- Dalvik虛擬機進程和線程的創建過程分析
- Dalvik虛擬機垃圾收集機制簡要介紹和學習計劃
- Dalvik虛擬機Java堆創建過程分析
- Dalvik虛擬機為新創建對象分配內存的過程分析
- Dalvik虛擬機垃圾收集(GC)過程分析
- ART虛擬機
- Android ART運行時無縫替換Dalvik虛擬機的過程分析
- Android運行時ART簡要介紹和學習計劃
- Android運行時ART加載OAT文件的過程分析
- Android運行時ART加載類和方法的過程分析
- Android運行時ART執行類方法的過程分析
- ART運行時垃圾收集機制簡要介紹和學習計劃
- ART運行時Java堆創建過程分析
- ART運行時為新創建對象分配內存的過程分析
- ART運行時垃圾收集(GC)過程分析
- ART運行時Compacting GC簡要介紹和學習計劃
- ART運行時Compacting GC堆創建過程分析
- ART運行時Compacting GC為新創建對象分配內存的過程分析
- ART運行時Semi-Space(SS)和Generational Semi-Space(GSS)GC執行過程分析
- ART運行時Mark-Compact( MC)GC執行過程分析
- ART運行時Foreground GC和Background GC切換過程分析
- Android安全機制
- SEAndroid安全機制簡要介紹和學習計劃
- SEAndroid安全機制框架分析
- SEAndroid安全機制中的文件安全上下文關聯分析
- SEAndroid安全機制中的進程安全上下文關聯分析
- SEAndroid安全機制對Android屬性訪問的保護分析
- SEAndroid安全機制對Binder IPC的保護分析
- 從NDK在非Root手機上的調試原理探討Android的安全機制
- APK防反編譯
- Android視頻硬解穩定性問題探討和處理
- Android系統的智能指針(輕量級指針、強指針和弱指針)的實現原理分析
- Android應用程序安裝過程源代碼分析
- Android應用程序啟動過程源代碼分析
- 四大組件源代碼分析
- Activity
- Android應用程序的Activity啟動過程簡要介紹和學習計劃
- Android應用程序內部啟動Activity過程(startActivity)的源代碼分析
- 解開Android應用程序組件Activity的"singleTask"之謎
- Android應用程序在新的進程中啟動新的Activity的方法和過程分析
- Service
- Android應用程序綁定服務(bindService)的過程源代碼分析
- ContentProvider
- Android應用程序組件Content Provider簡要介紹和學習計劃
- Android應用程序組件Content Provider應用實例
- Android應用程序組件Content Provider的啟動過程源代碼分析
- Android應用程序組件Content Provider在應用程序之間共享數據的原理分析
- Android應用程序組件Content Provider的共享數據更新通知機制分析
- BroadcastReceiver
- Android系統中的廣播(Broadcast)機制簡要介紹和學習計劃
- Android應用程序注冊廣播接收器(registerReceiver)的過程分析
- Android應用程序發送廣播(sendBroadcast)的過程分析