淺談Android的資源編譯過程
Android APK
一.APK的結構以及生成
APK是Android Package的縮寫,即Android application package檔案或Android安裝包。每個要安裝到Android平臺的應用都要被編譯打包為一個單獨的檔案,副檔名為 .apk。APK檔案是用編譯器編譯生成的檔案包,其中包含了應用的二進位制程式碼、資源、配置檔案等。通過將APK檔案直接傳到Android手機中執行即可安裝。APK檔案其實就是zip格式,但其副檔名被改為apk。在這裡我們為了詳細講述Android應用程式我們將建立一個永恆的話題, 它就是HelloWorld
程式,在這裡我們建立的Android的HelloWorld程式的目錄結構如下所示:
一個典型的APK檔案通常由下列內容組成:
AndroidManifest.xml 程式全域性配置檔案
classes.dex Dalvik位元組碼
resources.arsc 資源索引表, 解壓縮resources.ap_就能看到
res\ 該目錄存放資原始檔(圖片,文字,xml佈局)
assets\ 該目錄可以存放一些配置檔案
src\ java原始碼檔案
libs\ 存放應用程式所依賴的庫
gen\ 編譯器根據資原始檔生成的java檔案
bin\ 由編譯器生成的apk檔案和各種依賴的資源
META-INF\ 該目錄下存放的是簽名資訊
首先來看一下使用Java語言編寫的Android應用程式從原始碼到安裝包的整個過程,示意圖如下,其中包含編譯、連結和簽名等:
(1). 使用aapt工具將資原始檔生成R.java檔案, resources.arsc和打包資原始檔
(2). 使用aidl工具將.aidl檔案編譯成.java檔案
(3). 使用javac工具將.java檔案編譯成.class檔案
(4). 使用dx指令碼將眾多.class檔案轉換成一個.dex檔案
(5). 使用apkbuilder指令碼將資原始檔和.dex檔案生成未簽名的apk安裝檔案
(6). 使用jdk中的jarsigner對apk安裝檔案進行簽名
上述工具都儲存在android-sdk-linux中的tools/和platform-tools資料夾下面.
範例:
src/com.example.helloworldactivity:
package com.example.helloworldactivity;
import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.TextView;
public class MainActivity extends Activity {
private final static String TAG = "MainActivity";
private TextView mTextView = null;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mTextView = (TextView)findViewById(R.id.text_view);
Button showButton = (Button)findViewById(R.id.button);
showButton.setOnClickListener(new OnClickListener() {
public void onClick(View v) {
mTextView.setText(R.string.hello_world);
}
});
}
}
res/layout/activity_main.xml:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent" >
<Button
android:id="@+id/button"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/show" />
<TextView
android:id="@+id/text_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="" />
</LinearLayout>
res/values/strings.xml:
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="app_name">HelloWorldActivity</string>
<string name="action_settings">Settings</string>
<string name="show">Show</string>
<string name="hello_world">Hello world!</string>
</resources>
AndroidManifest.xml:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.helloworldactivity"
android:versionCode="1"
android:versionName="1.0" >
<uses-sdk
android:minSdkVersion="8"
android:targetSdkVersion="17" />
<application
android:allowBackup="true"
android:icon="@drawable/ic_launcher"
android:label="@string/app_name"
android:theme="@style/AppTheme" >
<activity
android:name="com.example.helloworldactivity.MainActivity"
android:label="@string/app_name" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
我們前面建立的HelloWorldActivity應用程式資源目錄結構如下所示:
project
接下來,我們在HelloWorldActivity工程目錄下可以使用aapt命令:
aapt p -f -m -J mygen/ -S res/
-I ~/tool/android-sdk-linux/platforms/android-17/android.jar -A assets/
-M AndroidManifest.xml -F helloworldresources.apk
在mygen目錄下生成一個資源ID檔案R.java和在當前目錄下生成一個名為helloworldresources.apk的資源包,解壓縮裡面內容如下所示:
被打包的APK資原始檔中包含有:資源索引表檔案resources.arsc, AndroidManifest.xml二進位制檔案和res目錄下的應用程式圖片資源及layout目錄下的二進位制activity_main.xml檔案, res目錄下資訊如下所示:
注意:res/values目錄下的字串資訊被編譯進了resources.arsc資源索引檔案中,而在R.java檔案中僅僅儲存了資源ID資訊. R.java資訊如下所示:
package com.example.helloworldactivity;
public final class R {
public static final class attr {
}
public static final class dimen {
public static final int activity_horizontal_margin=0x7f040000;
public static final int activity_vertical_margin=0x7f040001;
}
public static final class drawable {
public static final int ic_launcher=0x7f020000;
}
public static final class id {
public static final int button=0x7f070000;
public static final int text_view=0x7f070001;
}
public static final class layout {
public static final int activity_main=0x7f030000;
}
public static final class string {
public static final int action_settings=0x7f050001;
public static final int app_name=0x7f050000;
public static final int hello_world=0x7f050003;
public static final int show=0x7f050002;
}
public static final class style {
public static final int AppBaseTheme=0x7f060000;
public static final int AppTheme=0x7f060001;
}
}
下面我們根據分析appt的原始碼詳細講述命令:
aapt p -f -m -J mygen/ -S res/
-I ~/tool/android-sdk-linux/platforms/android-17/android.jar -A assets/
-M AndroidManifest.xml -F helloworldresources.apk
是如何將上述應用程式資源編譯生成一個R.java檔案, 資源索引表檔案resources.arsc, AndroidManifest.xml二進位制檔案和res目錄下的應用程式圖片資源及layout目錄下的二進位制activity_main.xml檔案的.
appt入口函式main具體實現如下所示:
路徑:frameworks/base/tools/aapt/Main.cpp
int main(int argc, char* const argv[])
{
char *prog = argv[0];
Bundle bundle; // 定義一個Bundle類儲存appt命令的各種編譯選項
bool wantUsage = false;
int result = 1; // pessimistically assume an error.
int tolerance = 0;
/* default to compression
** 設定預設的壓縮標準*/
bundle.setCompressionMethod(ZipEntry::kCompressDeflated);
if (argc < 2) {
wantUsage = true;
goto bail;
}
if (argv[1][0] == 'v')
bundle.setCommand(kCommandVersion);
......
else if (argv[1][0] == 'p') // 命令列選項p表示我們要打包資源
bundle.setCommand(kCommandPackage);
......
argc -= 2;
argv += 2;
/*
* Pull out flags. We support "-fv" and "-f -v".
* 一下while迴圈將各種aapt編譯選項提取出來存放到bundle中 */
while (argc && argv[0][0] == '-') {
/* flag(s) found */
const char* cp = argv[0] +1;
while (*cp != '\0') {
switch (*cp) {
......
case 'f': // 如果編譯出來的檔案已經存在,強制覆蓋
bundle.setForce(true); // bundle.mForce(bool)
break;
........
case 'm': // 使生成的包的目錄存放在-J引數指定的目錄
bundle.setMakePackageDirs(true); // bundle.mMakePackageDirs(bool)
break;
......
case 'A': // assert資料夾路徑
argc--;
argv++;
if (!argc) {
fprintf(stderr, "ERROR: No argument supplied for '-A' option\n");
wantUsage = true;
goto bail;
}
convertPath(argv[0]); // 裝換為指定OS的路徑
bundle.setAssetSourceDir(argv[0]); // mAssetSourceDir(const char*)
break;
......
case 'I': // 某個版本平臺的android.jar的路徑
argc--;
argv++;
if (!argc) {
fprintf(stderr, "ERROR: No argument supplied for '-I' option\n");
wantUsage = true;
goto bail;
}
convertPath(argv[0]);
// mPackageIncludes.add(file); android::Vector<const char*>
bundle.addPackageInclude(argv[0]);
break;
case 'F': // 具體指定APK檔案的輸出
argc--;
argv++;
if (!argc) {
fprintf(stderr, "ERROR: No argument supplied for '-F' option\n");
wantUsage = true;
goto bail;
}
convertPath(argv[0]);
// mOutputAPKFile(const char*)
bundle.setOutputAPKFile(argv[0]);
break;
case 'J': // 指定生成的R.java 的輸出目錄
argc--;
argv++;
if (!argc) {
fprintf(stderr, "ERROR: No argument supplied for '-J' option\n");
wantUsage = true;
goto bail;
}
convertPath(argv[0]);
bundle.setRClassDir(argv[0]); // mRClassDir(const char*)
break;
case 'M': // 指定AndroidManifest.xml檔案路徑
argc--;
argv++;
if (!argc) {
fprintf(stderr, "ERROR: No argument supplied for '-M' option\n");
wantUsage = true;
goto bail;
}
convertPath(argv[0]);
// mAndroidMainifestFile(const char*)
bundle.setAndroidManifestFile(argv[0]);
break;
......
case 'S': // res資料夾路徑
argc--;
argv++;
if (!argc) {
fprintf(stderr, "ERROR: No argument supplied for '-S' option\n");
wantUsage = true;
goto bail;
}
convertPath(argv[0]);
// android::Vector<const char*> mResourceSourceDirs;
// mResourceSourceDirs.insertAt(dir,0);
bundle.addResourceSourceDir(argv[0]);
break;
......
default:
fprintf(stderr, "ERROR: Unknown flag '-%c'\n", *cp);
wantUsage = true;
goto bail;
}
cp++;
}
argc--;
argv++;
}
/*
* We're past the flags. The rest all goes straight in.
* 設定Bundle的成員變數mArgv和mArgc分別為argv, argc */
bundle.setFileSpec(argv, argc);
/* 通過handleCommand函式來處理指定命令 */
result = handleCommand(&bundle);
bail:
if (wantUsage) {
usage();
result = 2;
}
//printf("--> returning %d\n", result);
return result;
}
處理完aapt的編譯選項之後,接著呼叫handleCommand函式來處理對應的功能:
路徑:frameworks/base/tools/aapt/Main.cpp
int handleCommand(Bundle* bundle)
{
......
switch (bundle->getCommand()) {
.......
case kCommandPackage: return doPackage(bundle);
......
default:
fprintf(stderr, "%s: requested command not yet supported\n", gProgName);
return 1;
}
}
最終打包APK的工作由函式doPackage完成,而打包一個應用程式資源的過程非常複雜,我們分如下模組一一講解:
一. 收錄一個應用程式所有資原始檔
路徑:frameworks/base/tools/aapt/Command.cpp
/*
* Package up an asset directory and associated application files.
* 打包應用程式中的資原始檔 */
int doPackage(Bundle* bundle)
{
const char* outputAPKFile;
int retVal = 1;
status_t err;
sp<AaptAssets> assets;
int N;
FILE* fp;
String8 dependencyFile;
......
// 檢查aapt打包時的引數是否都存在
N = bundle->getFileSpecCount();
if (N < 1 && bundle->getResourceSourceDirs().size() == 0
&& bundle->getJarFiles().size() == 0
&& bundle->getAndroidManifestFile() == NULL
&& bundle->getAssetSourceDir() == NULL) {
fprintf(stderr, "ERROR: no input files\n");
goto bail;
}
// 得到最終將資源打包輸出到的APK名稱
outputAPKFile = bundle->getOutputAPKFile();
// Make sure the filenames provided exist and are of the appropriate type.
// 檢查該檔案是否存在不存在則建立,並確定其實常規檔案
if (outputAPKFile) {
FileType type;
type = getFileType(outputAPKFile);
if (type != kFileTypeNonexistent && type != kFileTypeRegular) {
fprintf(stderr,
"ERROR: output file '%s' exists but is not regular file\n",
outputAPKFile);
goto bail;
}
}
// Load the assets.
// 建立一個AaptAssets物件
assets = new AaptAssets();
......
/* 1.呼叫AaptAssets類的成員函式slurpFromArgs將AndroidManifest.xml檔案,
** 目錄assets和res下的資源目錄和資原始檔收錄起來儲存到AaptAssets中的
** 成員變數中 */
err = assets->slurpFromArgs(bundle);
if (err < 0) {
goto bail;
}
......
}
AaptAssets的slurpFromArgs函式的具體實現如下所示:
路徑:frameworks/base/tools/aapt/AaptAssets.cpp
ssize_t AaptAssets::slurpFromArgs(Bundle* bundle)
{
int count;
int totalCount = 0;
FileType type;
// 獲取res目錄的路徑
const Vector<const char *>& resDirs = bundle->getResourceSourceDirs();
const size_t dirCount =resDirs.size();
sp<AaptAssets> current = this;
// 獲取bundle內所儲存的aapt的命令選項個數,即要完成的功能個數
const int N = bundle->getFileSpecCount();
/*
* If a package manifest was specified, include that first.
* 如果bundle中指定了AndroidManifest.xml檔案,則首先包含它 */
if (bundle->getAndroidManifestFile() != NULL) {
// place at root of zip.
String8 srcFile(bundle->getAndroidManifestFile());
/* 每向AaptAssets的物件中新增一個資原始檔或者一個資源目錄都要新建一個
** 型別AaptGroupEntry的空物件並將其新增到一個型別為SortedVector的
** AaptAssets的成員變數mGroupEntries中, 在這裡呼叫addFile函式是
** 將AndroidManifest.xml檔案新增到成員變數mFiles中去.
*/
addFile(srcFile.getPathLeaf(), AaptGroupEntry(), srcFile.getPathDir(),
NULL, String8());
/* 每新增一個資源就加1統計一次 */
totalCount++;
}
/*
* If a directory of custom assets was supplied, slurp 'em up.
* 判斷是否指定了assets資料夾,如果指定則解析它 */
if (bundle->getAssetSourceDir()) {
const char* assetDir = bundle->getAssetSourceDir(); // 獲取目錄名稱
FileType type = getFileType(assetDir); // 獲取目錄型別
if (type == kFileTypeNonexistent) {
fprintf(stderr, "ERROR: asset directory '%s' does not exist\n", assetDir);
return UNKNOWN_ERROR;
}
if (type != kFileTypeDirectory) {
fprintf(stderr, "ERROR: '%s' is not a directory\n", assetDir);
return UNKNOWN_ERROR;
}
String8 assetRoot(assetDir);
/* 建立一個名為”assets”的AaptDir物件 */
sp<AaptDir> assetAaptDir = makeDir(String8(kAssetDir));
AaptGroupEntry group;
/* 呼叫AaptDir的成員函式slurpFullTree收錄目錄“assets”下的資原始檔,
** 並返回資原始檔個數 */
count = assetAaptDir->slurpFullTree(bundle, assetRoot, group,
String8(), mFullAssetPaths);
if (count < 0) {
totalCount = count;
goto bail;
}
if (count > 0) {
mGroupEntries.add(group);
}
/* 統計資原始檔總個數 */
totalCount += count;
if (bundle->getVerbose())
printf("Found %d custom asset file%s in %s\n",
count, (count==1) ? "" : "s", assetDir);
}
/*
* If a directory of resource-specific assets was supplied, slurp 'em up.
* 收錄指定的res資源目錄下的資原始檔 */
for (size_t i=0; i<dirCount; i++) {
const char *res = resDirs[i];
if (res) {
type = getFileType(res); // 獲取檔案型別
if (type == kFileTypeNonexistent) {
fprintf(stderr, "ERROR: resource directory '%s' does not exist\n", res);
return UNKNOWN_ERROR;
}
if (type == kFileTypeDirectory) {
// 如果指定了多個res資源目錄檔案, 則為其建立多個AaptAssets
// 類來分別收錄這些目錄中的資訊,並將其設定賦值給當前
// AaptAssets物件的成員變數mOverlay
if (i>0) {
sp<AaptAssets> nextOverlay = new AaptAssets();
current->setOverlay(nextOverlay);
current = nextOverlay;
current->setFullResPaths(mFullResPaths);
}
// 呼叫成員函式slurpResourceTree來收錄res目錄下的資原始檔
count = current->slurpResourceTree(bundle, String8(res));
if (count < 0) {
totalCount = count;
goto bail;
}
totalCount += count; // 統計資原始檔個數
}
else {
fprintf(stderr, "ERROR: '%s' is not a directory\n", res);
return UNKNOWN_ERROR;
}
}
}
/*
* Now do any additional raw files.
* 接著收錄剩餘的指定的資原始檔 */
for (int arg=0; arg<N; arg++) {
const char* assetDir = bundle->getFileSpecEntry(arg);
FileType type = getFileType(assetDir);
if (type == kFileTypeNonexistent) {
fprintf(stderr, "ERROR: input directory '%s' does not exist\n", assetDir);
return UNKNOWN_ERROR;
}
if (type != kFileTypeDirectory) {
fprintf(stderr, "ERROR: '%s' is not a directory\n", assetDir);
return UNKNOWN_ERROR;
}
String8 assetRoot(assetDir);
if (bundle->getVerbose())
printf("Processing raw dir '%s'\n", (const char*) assetDir);
/*
* Do a recursive traversal of subdir tree. We don't make any
* guarantees about ordering, so we're okay with an inorder search
* using whatever order the OS happens to hand back to us.
*/
count = slurpFullTree(bundle,
assetRoot, AaptGroupEntry(), String8(), mFullAssetPaths);
if (count < 0) {
/* failure; report error and remove archive */
totalCount = count;
goto bail;
}
totalCount += count;
if (bundle->getVerbose())
printf("Found %d asset file%s in %s\n",
count, (count==1) ? "" : "s", assetDir);
}
count = validate();
if (count != NO_ERROR) {
totalCount = count;
goto bail;
}
count = filter(bundle);
if (count != NO_ERROR) {
totalCount = count;
goto bail;
}
bail:
return totalCount;
}
AaptAssets的成員函式addFile用來向AaptAssets的一個物件新增一個資原始檔到其成員變數mFiles中去或者新增一個資源目錄到其成員變數mDirs中去, 注意:每一個資原始檔都封裝成一個AaptFile類然後用AaptGroup將這些AaptFile物件組織起來,其具體實現如下所示:
路徑:frameworks/base/tools/aapt/AaptAssets.cpp
sp<AaptFile> AaptAssets::addFile(
const String8& filePath, const AaptGroupEntry& entry,
const String8& srcDir, sp<AaptGroup>* outGroup,
const String8& resType)
{
sp<AaptDir> dir = this; // AaptAssets類繼承了一個AaptDir類
sp<AaptGroup> group;
sp<AaptFile> file;
String8 root, remain(filePath), partialPath;
while (remain.length() > 0) {
// 獲取remain所描述檔案的工作目錄,如果其僅僅指定了檔名則返回檔名,
// 如果檔名前新增了路徑,則返回最上層的目錄名
// 例如,remain = “AndroidManifest.xml”,則root=“AndroidManifest.xml”,
// remain = “”; 如果remain=“/rootpath/subpath/AndroidManifest.xml”,
// 則,root=“rootpath”, remain=”subpath/AndroidManifest.xml”
root = remain.walkPath(&remain);
partialPath.appendPath(root);
const String8 rootStr(root);
/* 在這裡remain.length()返回0 */
if (remain.length() == 0) { // 新增資原始檔到mFiles中去
/* dir指向當前AaptAssets物件,其呼叫getFiles返回型別為
** DefaultKeyVector<String8, sp<AaptGroup>>成員變數mFiles,判斷其內部
** 是否包含了名稱為rootStr的AaptGroup物件,並返回其位置值 */
ssize_t i = dir->getFiles().indexOfKey(rootStr);
/* 如果返回的位置值>=0表示mFiles中已經包含了這個名為rootStr的
** AaptGroup物件,則將group指向該物件, 否則新建一個名稱為rootStr
** 的AaptGroup物件並新增到mFiles中去 */
if (i >= 0) {
group = dir->getFiles().valueAt(i);
} else {
group = new AaptGroup(rootStr, filePath);
status_t res = dir->addFile(rootStr, group);
if (res != NO_ERROR) {
return NULL;
}
}
// 新建一個AaptFile物件指向需要新增的原始檔, 並將該AaptFile物件
// 新增到型別為DefaultKeyedVector<AaptGroupEntry, sp<AaptFile> >的
// AaptGroup的成員變數 mFiles中去
file = new AaptFile(srcDir.appendPathCopy(filePath), entry, resType);
status_t res = group->addFile(file);
if (res != NO_ERROR) {
return NULL;
}
break;
} else { // 新增資源目錄到mDirs中去
/* dir指向當前AaptAssets物件,其呼叫getDirs返回型別為
** DefaultKeyVector<String8, sp<AaptDir>>成員變數mDirs,判斷其內部
** 是否包含了名稱為rootStr的AaptDir物件,並返回其位置值 */
ssize_t i = dir->getDirs().indexOfKey(rootStr);
/* 如果返回的位置值>=0表示mDirs中已經包含了這個名為rootStr的
** AaptDir物件,則將dir指向該物件,否則新建一個名稱為rootStr
** 的AaptDir物件並新增到mDirs中去 */
if (i >= 0) {
dir = dir->getDirs().valueAt(i);
} else {
sp<AaptDir> subdir = new AaptDir(rootStr, partialPath);
status_t res = dir->addDir(rootStr, subdir);
if (res != NO_ERROR) {
return NULL;
}
dir = subdir;
}
}
}
/* 將一個空的AaptGroupEntry物件新增到mGroupEntries中去,其是一個SortedVector
*/
mGroupEntries.add(entry);
if (outGroup) *outGroup = group;
return file;
}
AaptAssets的成員函式slurpFullTree將會收錄路徑名為srcDir目錄下的所有資原始檔,並將對應目錄下的檔名都儲存到fullResPaths中去,其具體實現如下所示:
路徑:frameworks/base/tools/aapt/AaptAssets.cpp
ssize_t AaptAssets::slurpFullTree(Bundle* bundle, const String8& srcDir,
const AaptGroupEntry& kind,
const String8& resType,
sp<FilePathStore>& fullResPaths)
{
/* 接著呼叫父類中的AaptDir的成員函式slurpFullTree收錄srcDir中的
** 資原始檔 */
ssize_t res = AaptDir::slurpFullTree(bundle, srcDir, kind, resType, fullResPaths);
/* 如果收錄的資源個數>0,則將其歸為一類,為這類資原始檔建立一個對應
** AaptGroupEntry物件並新增到對應的成員變數mGroupEntries中去 */
if (res > 0) {
mGroupEntries.add(kind);
}
return res;
}
ssize_t AaptDir::slurpFullTree(Bundle* bundle, const String8& srcDir,
const AaptGroupEntry& kind, const String8& resType,
sp<FilePathStore>& fullResPaths)
{
Vector<String8> fileNames;
{
DIR* dir = NULL;
/* 首先開啟將要收錄的資原始檔所在的源目錄 */
dir = opendir(srcDir.string());
if (dir == NULL) {
fprintf(stderr, "ERROR: opendir(%s): %s\n", srcDir.string(), strerror(errno));
return UNKNOWN_ERROR;
}
/*
* Slurp the filenames out of the directory.
* 遍歷srcDir目錄下的每一個資原始檔,將其新增到AaptAssets的成員變數
* mFullAssetPaths中,其繼承了一個Vector<String8> */
while (1) {
struct dirent* entry;
entry = readdir(dir);
if (entry == NULL)
break;
if (isHidden(srcDir.string(), entry->d_name))
continue;
String8 name(entry->d_name);
fileNames.add(name);
// Add fully qualified path for dependency purposes
// if we're collecting them
// 按照全部路徑將資原始檔新增到fullResPaths中去
if (fullResPaths != NULL) {
fullResPaths->add(srcDir.appendPathCopy(name));
}
}
closedir(dir);
}
ssize_t count = 0;
/*
* Stash away the files and recursively descend into subdirectories.
* 遞迴解析srcDir下的子目錄中的資原始檔,指導收錄完所有的
* 目錄中的資原始檔為止 */
const size_t N = fileNames.size();
size_t i;
for (i = 0; i < N; i++) {
String8 pathName(srcDir);
FileType type;
pathName.appendPath(fileNames[i].string());
type = getFileType(pathName.string());
/* 如果是資源子目錄,並且其尚未收錄在mDirs中,則為其建立一個
** AaptDir物件,繼續遞迴遍歷其中的資原始檔及目錄 */
if (type == kFileTypeDirectory) {
sp<AaptDir> subdir;
bool notAdded = false;
/* 如果*/
if (mDirs.indexOfKey(fileNames[i]) >= 0) {
subdir = mDirs.valueFor(fileNames[i]);
} else {
subdir =
new AaptDir(fileNames[i], mPath.appendPathCopy(fileNames[i]));
notAdded = true;
}
ssize_t res = subdir->slurpFullTree(bundle, pathName, kind,
resType, fullResPaths);
if (res < NO_ERROR) {
return res;
}
if (res > 0 && notAdded) {
mDirs.add(fileNames[i], subdir); // 將資源目錄新增到mDirs變數中
}
count += res;
/* 如果其為一個資原始檔,則為其建立一個指定的AaptFile變數
** 併為其建立一個對應的AaptGroup變數, 將這個AaptGroup變數新增
** 到mFiles變數中,然後將AaptFile變數新增到AaptGroup中去 */
} else if (type == kFileTypeRegular) {
sp<AaptFile> file = new AaptFile(pathName, kind, resType);
status_t err = addLeafFile(fileNames[i], file);
if (err != NO_ERROR) {
return err;
}
count++;
} else {
if (bundle->getVerbose())
printf(" (ignoring non-file/dir '%s')\n", pathName.string());
}
}
/* 返回總的資原始檔個數 */
return count;
}
AaptAssets的成員函式slurpResourceTree將會收錄路徑名為srcDir目錄下的所有資原始檔,該函式具體實現如下所示:
路徑:frameworks/base/tools/aapt/AaptAssets.cpp
ssize_t AaptAssets::slurpResourceTree(Bundle* bundle, const String8& srcDir)
{
ssize_t err = 0;
/* 開啟資原始檔夾 */
DIR* dir = opendir(srcDir.string());
if (dir == NULL) {
fprintf(stderr, "ERROR: opendir(%s): %s\n", srcDir.string(), strerror(errno));
return UNKNOWN_ERROR;
}
status_t count = 0;
/*
* Run through the directory, looking for dirs that match the
* expected pattern.
* 遞迴遍歷對應的資原始檔夾 */
while (1) {
struct dirent* entry = readdir(dir);
if (entry == NULL) {
break;
}
if (isHidden(srcDir.string(), entry->d_name)) {
continue;
}
String8 subdirName(srcDir);
subdirName.appendPath(entry->d_name);
AaptGroupEntry group;
String8 resType;
/* 呼叫AaptGroupEntry類的initFromDirName函式來歸類子目錄下的資原始檔
** 並將對應的資原始檔型別通過resType返回
** 按照這樣的順序: mcc, mnc, loc, layoutsize, layoutlong, orient, den,touch,
** key, keysHidden, nav, navHidden, size, vers, uiModeType, uiModeNight,
** smallestwidthdp, widthdp, heightdp收錄對應的資原始檔名稱將其分類
** 儲存到group變數中, 這個函式很簡單就不具體分析 */
bool b = group.initFromDirName(entry->d_name, &resType);
if (!b) {
fprintf(stderr, "invalid resource directory name: %s/%s\n", srcDir.string(),
entry->d_name);
err = -1;
continue;
}
......
FileType type = getFileType(subdirName.string());
/* 如果是一個子目錄檔案, 則為其建立一個對應的AaptDir物件,並呼叫
** 該物件的成員函式slurpFullTree收錄該子目錄下的所有資原始檔 */
if (type == kFileTypeDirectory) {
sp<AaptDir> dir = makeDir(resType);
ssize_t res = dir->slurpFullTree(bundle, subdirName, group,
resType, mFullResPaths);
if (res < 0) {
count = res;
goto bail;
}
if (res > 0) {
mGroupEntries.add(group);
count += res;
}
// Only add this directory if we don't already have a resource dir
// for the current type. This ensures that we only add the dir once
// for all configs.
// 判斷是否新增過對應的資源目錄到成員變數mResDirs中了
// 如果沒有新增過則將其新增進去
sp<AaptDir> rdir = resDir(resType);
if (rdir == NULL) {
mResDirs.add(dir);
}
} else {
if (bundle->getVerbose()) {
fprintf(stderr, " (ignoring file '%s')\n", subdirName.string());
}
}
}
bail:
closedir(dir);
dir = NULL;
if (err != 0) {
return err;
}
return count;
}
二. 編譯AndroidManifest.xml檔案和res目錄下資原始檔
以上通過AaptAssets類的成員函式slurpFromArgs將一個應用程式中的所有資原始檔收錄完成以後,接下來就要呼叫buildResources函式編譯AndroidManifest.xml檔案以及res目錄下的資原始檔,這些資原始檔的路徑等詳細資訊儲存在AaptAssets物件assets中,具體實現如下所示:
路徑:frameworks/base/tools/aapt/Command.cpp
/*
* Package up an asset directory and associated application files.
* 打包應用程式中的資原始檔 */
int doPackage(Bundle* bundle)
{
const char* outputAPKFile;
int retVal = 1;
status_t err;
sp<AaptAssets> assets;
int N;
FILE* fp;
String8 dependencyFile;
......
// If they asked for any fileAs that need to be compiled, do so.
// 編譯res目錄下資原始檔以及AndroidManifest.xml檔案
if (bundle->getResourceSourceDirs().size() || bundle->getAndroidManifestFile()) {
err = buildResources(bundle, assets);
if (err != 0) {
goto bail;
}
}
......
}
將文字xml資原始檔編譯成二進位制資原始檔的方法buildResources函式的具體實現如下所示,由於這個函式非常長,所以我們分開來對其一一進行解析:
1. 編譯AndroidManifest.xml檔案
路徑:frameworks/base/tools/aapt/Resource.cpp
status_t buildResources(Bundle* bundle, const sp<AaptAssets>& assets)
{
// First, look for a package file to parse. This is required to
// be able to generate the resource information.
// 首先從assets中獲取AndroidManifest.xml檔案的資訊
// AndroidManifest.xml檔案資訊是儲存在assets的成員變數mFiles中的,
// 但是其被封裝成一個AaptFile類物件儲存在AaptGroup物件最終再
// 儲存到mFiles中的
sp<AaptGroup> androidManifestFile =
assets->getFiles().valueFor(String8("AndroidManifest.xml"));
if (androidManifestFile == NULL) {
fprintf(stderr, "ERROR: No AndroidManifest.xml file found.\n");
return UNKNOWN_ERROR;
}
// 呼叫parsePackage函式解析AndroidManifest.xml檔案
status_t err = parsePackage(bundle, assets, androidManifestFile);
if (err != NO_ERROR) {
return err;
}
......
}
parsePackage函式的具體實現如下所示, 這個解析過程我們對應著AndroidManifest.xml檔案看, 具體實現如下所示:
路徑:frameworks/base/tools/aapt/Resource.cpp
static status_t parsePackage(Bundle* bundle, const sp<AaptAssets>& assets,
const sp<AaptGroup>& grp)
{
// 以下程式碼確保只有一個AndroidManifest.xml檔案
if (grp->getFiles().size() != 1) {
fprintf(stderr, "warning: Multiple AndroidManifest.xml files found, using %s\n",
grp->getFiles().valueAt(0)->getPrintableSource().string());
}
// 取出存放AndroidManifest.xml檔案資訊的AaptFile物件
sp<AaptFile> file = grp->getFiles().valueAt(0);
// 定義一個ResXMLTree物件,然後呼叫parseXMLResource來詳細解析
// AndroidManifest.xml檔案
// 這個函式主要完成三個工作:
// 1. 收集file檔案指向的xml檔案中字串資源資訊.
// 2. 壓平該file檔案只想的xml檔案中資源資訊
// 3. 將前兩步組織的到的資源資訊最終組織到一個ResXMLTree所描述的資料結構中.
ResXMLTree block;
status_t err = parseXMLResource(file, &block);
if (err != NO_ERROR) {
return err;
}
//printXMLBlock(&block);
ResXMLTree::event_code_t code;
while ((code=block.next()) != ResXMLTree::START_TAG
&& code != ResXMLTree::END_DOCUMENT
&& code != ResXMLTree::BAD_DOCUMENT) {
}
size_t len;
if (code != ResXMLTree::START_TAG) {
fprintf(stderr, "%s:%d: No start tag found\n",
file->getPrintableSource().string(), block.getLineNumber());
return UNKNOWN_ERROR;
}
if (strcmp16(block.getElementName(&len), String16("manifest").string()) != 0) {
fprintf(stderr, "%s:%d: Invalid start tag %s, expected <manifest>\n",
file->getPrintableSource().string(), block.getLineNumber(),
String8(block.getElementName(&len)).string());
return UNKNOWN_ERROR;
}
ssize_t nameIndex = block.indexOfAttribute(NULL, "package");
if (nameIndex < 0) {
fprintf(stderr, "%s:%d: <manifest> does not have package attribute.\n",
file->getPrintableSource().string(), block.getLineNumber());
return UNKNOWN_ERROR;
}
assets->setPackage(String8(block.getAttributeStringValue(nameIndex, &len)));
String16 uses_sdk16("uses-sdk");
while ((code=block.next()) != ResXMLTree::END_DOCUMENT
&& code != ResXMLTree::BAD_DOCUMENT) {
if (code == ResXMLTree::START_TAG) {
if (strcmp16(block.getElementName(&len), uses_sdk16.string()) == 0) {
ssize_t minSdkIndex =
block.indexOfAttribute(RESOURCES_ANDROID_NAMESPACE,
"minSdkVersion");
if (minSdkIndex >= 0) {
const uint16_t* minSdk16 =
block.getAttributeStringValue(minSdkIndex, &len);
const char* minSdk8 = strdup(String8(minSdk16).string());
bundle->setManifestMinSdkVersion(minSdk8);
}
}
}
}
return NO_ERROR;
}
parseXMLResource函式的具體實現如下所示, 下面我們就一一解析其是如何解析一個XML資原始檔的:
路徑:frameworks/base/tools/aapt/XMLNode.cpp
status_t parseXMLResource(const sp<AaptFile>& file, ResXMLTree* outTree,
bool stripAll, bool keepComments,
const char** cDataTags)
{
/* 接著呼叫XMLNode的成員函式parse來解析AndroidManifest.xml檔案 */
sp<XMLNode> root = XMLNode::parse(file);
if (root == NULL) {
return UNKNOWN_ERROR;
}
root->removeWhitespace(stripAll, cDataTags);
/* 新建一個AaptFile作為輸出檔案 */
sp<AaptFile> rsc = new AaptFile(String8(), AaptGroupEntry(), String8());
/* 呼叫flatten函式壓平AndroidManifest.xml檔案, 將壓平後的xml檔案資訊按指定
** 格式組織在rsc的資料緩衝區中 */
status_t err = root->flatten(rsc, !keepComments, false);
if (err != NO_ERROR) {
return err;
}
err = outTree->setTo(rsc->getData(), rsc->getSize(), true);
if (err != NO_ERROR) {
return err;
}
return NO_ERROR;
}
(1). 收集XML文字檔案資訊
收集AdaptFile物件file所指向的xml文字檔案中資訊, 將其組織在一個以XMLNode物件root為根的樹中
sp<XMLNode> XMLNode::parse(const sp<AaptFile>& file)
{
char buf[16384];
/* 以只讀方式開啟AndroidManifest.xml檔案 */
int fd = open(file->getSourceFile().string(), O_RDONLY | O_BINARY);
if (fd < 0) {
SourcePos(file->getSourceFile(), -1).error("Unable to open file for read: %s",
strerror(errno));
return NULL;
}
/* 建立一個XML檔案解析器, 該解析器是定義在expat庫中的
** Expat 是一個用C語言開發的、用來解析XML文件的開發庫,它最初是開源的、
** Mozilla 專案下的一個XML解析器。採用流的方式來解析XML檔案,並且基於
** 事件通知型來呼叫分析到的資料,並不需要把所有XML檔案全部載入到記憶體裡,
** 這樣可以分析非常大的XML檔案。*/
/* 1.建立一個XML分析器。*/
XML_Parser parser = XML_ParserCreateNS(NULL, 1);
ParseState state;
state.filename = file->getPrintableSource(); // 制定檔名稱為AndroidManifest.xml
state.parser = parser;
XML_SetUserData(parser, &state); // 設定使用者資料
/* 2.第一個引數是那個Parser控制程式碼,第二個和第三個引數則是整個Parser的核心,
** 型別為CallBack的函式
*/
XML_SetElementHandler(parser, startElement, endElement);
/* startNamespace: 解析xmlns:android開頭的資訊:
** 引數: prefix = android, uri= android右邊的屬性值資訊
** "http://schemas.android.com/apk/res/android"
** endNamespace - 銷燬ParseState中快取的資料
** 這個特殊的xmlns:android="http://schemas.android.com/apk/res/android"
** 屬性名和屬性值會建立一個XMLNode作為根節點, 其也叫做名稱空間
** 解析名稱空間和標籤元素類似,就不再贅述 */
XML_SetNamespaceDeclHandler(parser, startNamespace, endNamespace);
/* 函式是設定處理一個<>和</>之間的欄位的回撥
** <Item>This is a normal text</Item>
** 那麼字串“This is a normal text”就稱為一個CDATA */
XML_SetCharacterDataHandler(parser, characterData);
/* 處理註釋的函式 */
XML_SetCommentHandler(parser, commentData);
ssize_t len;
bool done;
do {
len = read(fd, buf, sizeof(buf));
done = len < (ssize_t)sizeof(buf);
if (len < 0) {
close(fd);
return NULL;
}
/* 第二個引數是使用者指定的Buffer指標, 第三個是這塊Buffer中實際內容的
** 位元組數,最後引數代表是否這塊Buffer已經結束。比如要解析的XML檔案太大,
** 但記憶體比較吃緊,Buffer比較小,則可以迴圈讀取檔案,然後丟給Parser,
** 在檔案讀取結束前,isFinal引數為FALSE,反之為TRUE。 */
if (XML_Parse(parser, buf, len, done) == XML_STATUS_ERROR) {
close(fd);
return NULL;
}
} while (!done);
XML_ParserFree(parser); // 銷燬一個解析器
if (state.root == NULL) {
SourcePos(file->getSourceFile(), -1).error("No XML data generated when parsing");
}
close(fd);
return state.root;
}
/* startElement解析:<> 或者 </>中的資訊,
** 引數:name為標籤名, atts - 從左到右將儲存=兩邊屬性名和屬性值.
** endElement解析: </> 中的資訊*/
void XMLCALL
XMLNode::startElement(void *userData, const char *name, const char **atts)
{
NOISY_PARSE(printf("Start Element: %s\n", name));
printf("Start Element: %s\n", name);
ParseState* st = (ParseState*)userData;
String16 ns16, name16;
splitName(name, &ns16, &name16);
/* 為每一個名稱為name的標籤建立一個XMLNode物件 */
sp<XMLNode> node = XMLNode::newElement(st->filename, ns16, name16);
/* 設定標籤開始的行號 */
node->setStartLineNumber(XML_GetCurrentLineNumber(st->parser));
if (st->pendingComment.size() > 0) {
node->appendComment(st->pendingComment);
st->pendingComment = String16();
}
if (st->stack.size() > 0) {
/** 而name子標籤作為name標籤XMLNode物件的一個
** 子物件儲存到XMLNode的成員變數mChildren(Vector)中, 而ParseState
** 只是用於快取XMLNode資料資訊用,快取完成之後隨即在endElement函式
** 中銷燬.
st->stack.itemAt(st->stack.size()-1)->addChild(node);
} else {
st->root = node; // 根節點是名稱空間節點
}
st->stack.push(node); // 快取
for (int i = 0; atts[i]; i += 2) {
splitName(atts[i], &ns16, &name16);
printf(" attrs: %s=%s\n", atts[i], atts[i+1]);
node->addAttribute(ns16, name16, String16(atts[i+1]));
}
}
void XMLCALL
XMLNode::endElement(void *userData, const char *name)
{
NOISY_PARSE(printf("End Element: %s\n", name));
printf("End Element: %s\n", name);
ParseState* st = (ParseState*)userData;
sp<XMLNode> node = st->stack.itemAt(st->stack.size()-1);
/* 設定標籤結束行號 */
node->setEndLineNumber(XML_GetCurrentLineNumber(st->parser));
if (st->pendingComment.size() > 0) {
node->appendComment(st->pendingComment);
st->pendingComment = String16();
}
String16 ns16, name16;
splitName(name, &ns16, &name16);
st->stack.pop();
}
/* 成員函式addAttribute將所有屬性名和屬性值的資訊新增到其儲存屬性資訊的
** 成員變數mAttributes和mAttributeOrder中,每一個屬性的資訊會被儲存在一個
** attribute_entry的結構體中,預設為每一個屬性所分配的資源ID是0.
*/
status_t XMLNode::addAttribute(const String16& ns, const String16& name,
const String16& value)
{
if (getType() == TYPE_CDATA) {
SourcePos(mFilename, getStartLineNumber()).error("Child to CDATA node.");
return UNKNOWN_ERROR;
}
if (ns != RESOURCES_TOOLS_NAMESPACE) {
attribute_entry e;
e.index = mNextAttributeIndex++;
e.ns = ns;
e.name = name; // 屬性名
e.string = value; // 屬性值
mAttributes.add(e);
mAttributeOrder.add(e.index, mAttributes.size()-1);
}
return NO_ERROR;
}
(2). 壓平收集完字串資訊的XML文字檔案
至此,解析完一個xml文字檔案之後接著呼叫XMLNode成員函式flatten來壓平該檔案,下面我們一一解析這個flatten過程:
路徑:frameworks/base/tools/aapt/XMLNode.cpp
status_t XMLNode::flatten(const sp<AaptFile>& dest,
bool stripComments, bool stripRawValues) const
{
/* 建立一個字串池StringPool變數strings 儲存屬性名稱字串 */
StringPool strings(mUTF8);
Vector<uint32_t> resids; // 儲存屬性名的資源ID號
// First collect just the strings for attribute names that have a
// resource ID assigned to them. This ensures that the resource ID
// array is compact, and makes it easier to deal with attribute names
// in different namespaces (and thus with different resource IDs).
// 首先收集屬性名字串,這些字串有一個資源ID指向它們.
// 這確保資源ID陣列緊湊的,並且不同於名稱空間的資源ID,
// 這使得處理屬性名變得更簡單
// 注意:在這裡實際上什麼工作都沒有做!!FUCK
collect_resid_strings(&strings, &resids);
// Next collect all remainibng strings.
// 真正的收集工作在這裡才工作,上面什麼工作都沒做還遞迴遍歷半天
collect_strings(&strings, &resids, stripComments, stripRawValues);
......
}
/* 該函式從指向名稱空間的root XMLNode開始遞迴遍歷收集每一個標籤(
** 一個標籤中的屬性資訊儲存在一個XMLNode變數中)的屬性名和ID
*/
status_t XMLNode::collect_resid_strings(StringPool* outPool,
Vector<uint32_t>* outResIds) const
{
/* 真正收集XML檔案中一個標籤的屬性名稱和其ID號的工作
** 在函式collect_attr_strings中完成, 這裡不工作 */
collect_attr_strings(outPool, outResIds, false);
const int NC = mChildren.size();
for (int i=0; i<NC; i++) {
mChildren.itemAt(i)->collect_resid_strings(outPool, outResIds);
}
return NO_ERROR;
}
status_t XMLNode::collect_strings(StringPool* dest, Vector<uint32_t>* outResIds,
bool stripComments, bool stripRawValues) const
{
/* 真正收集XML檔案中一個標籤的屬性名稱和其ID號的工作
** 在函式collect_attr_strings中完成, 這裡工作 */
collect_attr_strings(dest, outResIds, true);
/* 下列程式碼在收集完屬性名稱之後,接著將對應的其它資訊按照
** 一定的順序收集儲存到字串資源池 */
int i;
if (RESOURCES_TOOLS_NAMESPACE != mNamespaceUri) {
if (mNamespacePrefix.size() > 0) {
dest->add(mNamespacePrefix, true);
}
if (mNamespaceUri.size() > 0) {
dest->add(mNamespaceUri, true);
}
}
if (mElementName.size() > 0) {
dest->add(mElementName, true);
}
if (!stripComments && mComment.size() > 0) {
dest->add(mComment, true);
}
const int NA = mAttributes.size();
for (i=0; i<NA; i++) {
const attribute_entry& ae = mAttributes.itemAt(i);
if (ae.ns.size() > 0) {
dest->add(ae.ns, true);
}
if (!stripRawValues || ae.needStringValue()) {
dest->add(ae.string, true);
}
/*
if (ae.value.dataType == Res_value::TYPE_NULL
|| ae.value.dataType == Res_value::TYPE_STRING) {
dest->add(ae.string, true);
}
*/
}
if (mElementName.size() == 0) {
// If not an element, include the CDATA, even if it is empty.
dest->add(mChars, true);
}
const int NC = mChildren.size();
for (i=0; i<NC; i++) {
mChildren.itemAt(i)->collect_strings(dest, outResIds,
stripComments, stripRawValues);
}
return NO_ERROR;
}
/* 收集XML檔案中一個標籤中的屬性名稱及其ID分別儲存到字串資源池和ID池中 */
status_t XMLNode::collect_attr_strings(StringPool* outPool,
Vector<uint32_t>* outResIds, bool allAttrs) const {
/* 獲取屬性個數 */
const int NA = mAttributes.size();
for (int i=0; i<NA; i++) {
/* 每一個屬性使用一個attribute_entry結構體來表示 */
const attribute_entry& attr = mAttributes.itemAt(i);
// 獲取資源屬性ID,預設的資源屬性ID為0
// 故,在這裡該函式什麼工作都沒有做
uint32_t id = attr.nameResId;
if (id || allAttrs) {
// See if we have already assigned this resource ID to a pooled
// string...
// 檢測在字串池中是否已經收集到了指定屬性名稱的字串
const Vector<size_t>* indices = outPool->offsetsForString(attr.name);
ssize_t idx = -1;
if (indices != NULL) {
const int NJ = indices->size();
const size_t NR = outResIds->size();
for (int j=0; j<NJ; j++) {
size_t strIdx = indices->itemAt(j);
if (strIdx >= NR) {
if (id == 0) {
// We don't need to assign a resource ID for this one.
idx = strIdx;
break;
}
// Just ignore strings that are out of range of
// the currently assigned resource IDs... we add
// strings as we assign the first ID.
} else if (outResIds->itemAt(strIdx) == id) {
idx = strIdx;
break;
}
}
}
if (idx < 0) {
// 尚未將指定的屬性名新增到字串資源池中,如果add函式後面跟隨
// 著描述字串屬性的entry_style_span的Vector則將字串屬性一併
// 加入, 並返回其在字串資源池中位置
idx = outPool->add(attr.name);
// 判斷是否為屬性名分配過資源ID
if (id != 0) {
/* 確保屬性名資源ID與屬性名對應 */
while ((ssize_t)outResIds->size() <= idx) {
outResIds->add(0);
}
// 替換原有資源ID
outResIds->replaceAt(id, idx);
}
}
attr.namePoolIdx = idx;
}
}
return NO_ERROR;
}
為收集到的字串資訊分配字串資源池並將收集到的資訊按照指定格式組織起來,在介紹字串緩衝塊之前,我們首先了解下StringPool的幾個重要的成員變數的含義:
// The following data structures represent the actual structures
// that will be generated for the final string pool.
// Raw array of unique strings, in some arbitrary order. This is the
// actual strings that appear in the final string pool, in the order
// that they will be written.
// mEntries用於儲存屬性字串資訊, 每一個字串都會被封裝成一個
// struct entry,這個結構體儲存了這個字串的各種配置屬性, 每次將
// 一個字串資訊add到StringPool中時都要將其封裝成一個entry
// 然後add到mEntries中
// 注意: 每一個entry結構體的indices這個Vector中儲存著這個字串在
// mValues中的位置值
Vector<entry> mEntries;
// Array of indices into mEntries, in the order they were
// added to the pool. This can be different than mEntries
// if the same string was added multiple times (it will appear
// once in mEntries, with multiple occurrences in this array).
// This is the lookup array that will be written for finding
// the string for each offset/position in the string pool.
// mEntryArray中儲存著某一個字串在mEntries中的位置
Vector<size_t> mEntryArray;
// Optional style span information associated with each index of
// mEntryArray.
// mEntryStyleArray中儲存著mValues中的字串的樣式,其跟
// mValues中的字串是一一對應的
Vector<entry_style> mEntryStyleArray;
// The following data structures are used for book-keeping as the
// string pool is constructed.
// Unique set of all the strings added to the pool, mapped to
// the first index of mEntryArray where the value was added.
// mValues儲存著一個字串的值以及其在mValues中的位置值
DefaultKeyedVector<String16, ssize_t> mValues;
下面我們詳細介紹是如何為一個StringPool建立一個StringBlock來組織儲存在其中的字串資訊的,具體實現如下所示:
路徑:frameworks/base/tools/aapt/XMLNode.cpp
status_t XMLNode::flatten(const sp<AaptFile>& dest,
bool stripComments, bool stripRawValues) const
{
StringPool strings(mUTF8);
Vector<uint32_t> resids;
......
/* 收集到的屬性資訊都儲存在strings所指向的字串資源池
** 現在為這些字串資源池中的屬性資訊分配字串塊 */
sp<AaptFile> stringPool = strings.createStringBlock();
......
}
路徑:frameworks/base/tools/aapt/StringPool.cpp
sp<AaptFile> StringPool::createStringBlock()
{
/* 首先建立一個匿名AaptFile類物件 */
sp<AaptFile> pool = new AaptFile(String8(), AaptGroupEntry(),
String8());
/* 呼叫writeStringBlock來建立字串塊 */
status_t err = writeStringBlock(pool);
return err == NO_ERROR ? pool : NULL;
}
status_t StringPool::writeStringBlock(const sp<AaptFile>& pool)
{
......
// First we need to add all style span names to the string pool.
// We do this now (instead of when the span is added) so that these
// will appear at the end of the pool, not disrupting the order
// our client placed their own strings in it.
/* 計算儲存字串樣式的Vector大小及其設定一些成員變數 */
const size_t STYLES = mEntryStyleArray.size();
size_t i;
for (i=0; i<STYLES; i++) {
entry_style& style = mEntryStyleArray.editItemAt(i);
const size_t N = style.spans.size();
for (size_t i=0; i<N; i++) {
entry_style_span& span = style.spans.editItemAt(i);
ssize_t idx = add(span.name, true);
if (idx < 0) {
fprintf(stderr, "Error adding span for style tag '%s'\n",
String8(span.name).string());
return idx;
}
span.span.name.index = (uint32_t)idx;
}
}
/* 計算mEntryArray的大小 */
const size_t ENTRIES = mEntryArray.size();
// Now build the pool of unique strings.
/* 計算mEntries的大小, 也就是字串個數 */
const size_t STRINGS = mEntries.size();
/* 計算出預分配記憶體緩衝區大小 */
const size_t preSize = sizeof(ResStringPool_header) // header
+ (sizeof(uint32_t)*ENTRIES) // 字串偏移陣列
+ (sizeof(uint32_t)*STYLES); // 格式偏移陣列
/* 呼叫AaptFile的成員函式editData函式分配指定大小的記憶體緩衝區 */
if (pool->editData(preSize) == NULL) {
fprintf(stderr, "ERROR: Out of memory for string pool\n");
return NO_MEMORY;
}
/* 判斷字元格式 */
const size_t charSize = mUTF8 ? sizeof(uint8_t) : sizeof(char16_t);
/* 下列程式碼詳細計算沒一個字串大小併為其分配記憶體空間並將StringPool中的
** 字串按照二進位制格式拷貝到StringBlock中為其分配的空間中 */
size_t strPos = 0;
for (i=0; i<STRINGS; i++) {
entry& ent = mEntries.editItemAt(i);
const size_t strSize = (ent.value.size());
const size_t lenSize = strSize > (size_t)(1<<((charSize*8)-1))-1 ?
charSize*2 : charSize;
String8 encStr;
if (mUTF8) {
encStr = String8(ent.value);
}
const size_t encSize = mUTF8 ? encStr.size() : 0;
const size_t encLenSize = mUTF8 ?
(encSize > (size_t)(1<<((charSize*8)-1))-1 ?
charSize*2 : charSize) : 0;
ent.offset = strPos; // 計算字串偏移量,以檔案開頭偏移量為0, 以此類推
const size_t totalSize = lenSize + encLenSize +
((mUTF8 ? encSize : strSize)+1)*charSize;
void* dat = (void*)pool->editData(preSize + strPos + totalSize);
if (dat == NULL) {
fprintf(stderr, "ERROR: Out of memory for string pool\n");
return NO_MEMORY;
}
dat = (uint8_t*)dat + preSize + strPos;
if (mUTF8) {
uint8_t* strings = (uint8_t*)dat;
ENCODE_LENGTH(strings, sizeof(uint8_t), strSize)
ENCODE_LENGTH(strings, sizeof(uint8_t), encSize)
strncpy((char*)strings, encStr, encSize+1);
} else {
uint16_t* strings = (uint16_t*)dat;
ENCODE_LENGTH(strings, sizeof(uint16_t), strSize)
strcpy16_htod(strings, ent.value);
}
strPos += totalSize;
}
// Pad ending string position up to a uint32_t boundary.
// 將StringBlock記憶體對齊
if (strPos&0x3) {
size_t padPos = ((strPos+3)&~0x3);
uint8_t* dat = (uint8_t*)pool->editData(preSize + padPos);
if (dat == NULL) {
fprintf(stderr, "ERROR: Out of memory padding string pool\n");
return NO_MEMORY;
}
memset(dat+preSize+strPos, 0, padPos-strPos);
strPos = padPos;
}
// Build the pool of style spans.
// 在隨後的StringBlock中將字串格式按照二進位制格式儲存起來
size_t styPos = strPos;
for (i=0; i<STYLES; i++) {
entry_style& ent = mEntryStyleArray.editItemAt(i);
const size_t N = ent.spans.size();
const size_t totalSize = (N*sizeof(ResStringPool_span))
+ sizeof(ResStringPool_ref);
ent.offset = styPos-strPos;
uint8_t* dat = (uint8_t*)pool->editData(preSize + styPos + totalSize);
if (dat == NULL) {
fprintf(stderr, "ERROR: Out of memory for string styles\n");
return NO_MEMORY;
}
/* 字串樣式在StringBlock中是組裝成一個ResStringPoll_span型別儲存起來 */
ResStringPool_span* span = (ResStringPool_span*)(dat+preSize+styPos);
for (size_t i=0; i<N; i++) {
span->name.index = htodl(ent.spans[i].span.name.index);
span->firstChar = htodl(ent.spans[i].span.firstChar);
span->lastChar = htodl(ent.spans[i].span.lastChar);
span++;
}
/* 寫入每個字串樣式結尾符 */
span->name.index = htodl(ResStringPool_span::END);
styPos += totalSize;
}
/* 在StringBlock中寫入樣式結束符號 */
if (STYLES > 0) {
// Add full terminator at the end (when reading we validate that
// the end of the pool is fully terminated to simplify error
// checking).
size_t extra = sizeof(ResStringPool_span)-sizeof(ResStringPool_ref);
uint8_t* dat = (uint8_t*)pool->editData(preSize + styPos + extra);
if (dat == NULL) {
fprintf(stderr, "ERROR: Out of memory for string styles\n");
return NO_MEMORY;
}
uint32_t* p = (uint32_t*)(dat+preSize+styPos);
while (extra > 0) {
*p++ = htodl(ResStringPool_span::END);
extra -= sizeof(uint32_t);
}
styPos += extra;
}
// Write header.
// 字串緩衝區開始位置寫入一個記錄字串緩衝區資訊的ResStringPool_header
ResStringPool_header* header =
(ResStringPool_header*)pool->padData(sizeof(uint32_t));
if (header == NULL) {
fprintf(stderr, "ERROR: Out of memory for string pool\n");
return NO_MEMORY;
}
memset(header, 0, sizeof(*header));
header->header.type = htods(RES_STRING_POOL_TYPE);
header->header.headerSize = htods(sizeof(*header));
header->header.size = htodl(pool->getSize());
header->stringCount = htodl(ENTRIES);
header->styleCount = htodl(STYLES);
if (mUTF8) {
header->flags |= htodl(ResStringPool_header::UTF8_FLAG);
}
header->stringsStart = htodl(preSize);
header->stylesStart = htodl(STYLES > 0 ? (preSize+strPos) : 0);
// Write string index array.
// 寫入字串偏移陣列
uint32_t* index = (uint32_t*)(header+1);
for (i=0; i<ENTRIES; i++) {
entry& ent = mEntries.editItemAt(mEntryArray[i]);
*index++ = htodl(ent.offset);
}
// Write style index array.
// 寫入字串樣式偏移陣列寫入
for (i=0; i<STYLES; i++) {
*index++ = htodl(mEntryStyleArray[i].offset);
}
return NO_ERROR;
}
至此,我們就將收集在StringPool中的xml文字檔案的資訊,按照指定組織方式以二進位制的儲存方式儲存在一塊記憶體塊中,接下來我們繼續分析xml文字檔案是如何被flatten的,其具體實現如下所示:
路徑:frameworks/base/tools/aapt/XMLNode.cpp
status_t XMLNode::flatten(const sp<AaptFile>& dest,
bool stripComments, bool stripRawValues) const
{
StringPool strings(mUTF8);
Vector<uint32_t> resids;
......
/* 收集到的屬性資訊都儲存在strings所指向的字串資源池
** 現在為這些字串資源池中的屬性資訊分配字串塊
*/
sp<AaptFile> stringPool = strings.createStringBlock();
/* 接著建立一個ResXMLTree_header */
ResXMLTree_header header;
memset(&header, 0, sizeof(header));
header.header.type = htods(RES_XML_TYPE);
header.header.headerSize = htods(sizeof(header));
const size_t basePos = dest->getSize();
/* 在匿名AdaptFile物件dest中先寫入一個header物件用於記錄資訊,接著
** 將我們上面組織好的二進位制xml字串資訊記憶體緩衝塊中資料寫入這個
** 匿名AdaptFile物件dest中的緩衝區去 */
dest->writeData(&header, sizeof(header));
dest->writeData(stringPool->getData(), stringPool->getSize());
// If we have resource IDs, write them.
// 如果已經分配了資源ID則先寫入一個記錄資源ID資訊的ResChunk_header
// 頭,然後將資源ID的資訊寫入,但是這裡尚未對任何資源分配資源ID.
if (resids.size() > 0) {
const size_t resIdsPos = dest->getSize();
const size_t resIdsSize =
sizeof(ResChunk_header)+(sizeof(uint32_t)*resids.size());
ResChunk_header* idsHeader = (ResChunk_header*)
(((const uint8_t*)dest->editData(resIdsPos+resIdsSize))+resIdsPos);
idsHeader->type = htods(RES_XML_RESOURCE_MAP_TYPE);
idsHeader->headerSize = htods(sizeof(*idsHeader));
idsHeader->size = htodl(resIdsSize);
uint32_t* ids = (uint32_t*)(idsHeader+1);
for (size_t i=0; i<resids.size(); i++) {
*ids++ = htodl(resids[i]);
}
}
/* 呼叫flatten_node函式繼續組織收集到的xml檔案中的資訊 */
flatten_node(strings, dest, stripComments, stripRawValues);
/* 最後,再寫入一個ResXMLTree_header標記寫入工作完成並記錄上次寫入這類
** header到剛剛建立的header之間寫入的資料資訊
*/
void* data = dest->editData();
ResXMLTree_header* hd = (ResXMLTree_header*)(((uint8_t*)data)+basePos);
size_t size = dest->getSize()-basePos;
hd->header.size = htodl(dest->getSize()-basePos);
return NO_ERROR;
}
status_t XMLNode::flatten_node(const StringPool& strings, const sp<AaptFile>& dest,
bool stripComments, bool stripRawValues) const
{
ResXMLTree_node node;
ResXMLTree_cdataExt cdataExt;
ResXMLTree_namespaceExt namespaceExt;
ResXMLTree_attrExt attrExt;
const void* extData = NULL;
size_t extSize = 0;
ResXMLTree_attribute attr;
bool writeCurrentNode = true;
/* NA和NC分別記錄屬性個數和子標籤個數 */
const size_t NA = mAttributes.size();
const size_t NC = mChildren.size();
size_t i;
const String16 id16("id");
const String16 class16("class");
const String16 style16("style");
const type type = getType(); // 獲取當前處理的XMLNode所記錄資訊的型別
/* 初始化一個node變數和attr變數 */
memset(&node, 0, sizeof(node));
memset(&attr, 0, sizeof(attr));
node.header.headerSize = htods(sizeof(node));
node.lineNumber = htodl(getStartLineNumber());
if (!stripComments) {
/* 返回註釋字串在StringPool 的成員變數mValues中的位置 */
node.comment.index = htodl(
mComment.size() > 0 ? strings.offsetForString(mComment) : -1);
} else {
node.comment.index = htodl((uint32_t)-1);
}
if (type == TYPE_ELEMENT) {
/* 設定該node型別 */
node.header.type = htods(RES_XML_START_ELEMENT_TYPE);
/* 使用attrExt記錄一個attribute額外的資訊 */
extData = &attrExt;
extSize = sizeof(attrExt);
memset(&attrExt, 0, sizeof(attrExt));
if (mNamespaceUri.size() > 0) {
attrExt.ns.index = htodl(strings.offsetForString(mNamespaceUri));
} else {
attrExt.ns.index = htodl((uint32_t)-1);
}
attrExt.name.index = htodl(strings.offsetForString(mElementName));
attrExt.attributeStart = htods(sizeof(attrExt));
attrExt.attributeSize = htods(sizeof(attr));
attrExt.attributeCount = htods(NA);
attrExt.idIndex = htods(0);
attrExt.classIndex = htods(0);
attrExt.styleIndex = htods(0);
for (i=0; i<NA; i++) {
ssize_t idx = mAttributeOrder.valueAt(i);
const attribute_entry& ae = mAttributes.itemAt(idx);
if (ae.ns.size() == 0) {
if (ae.name == id16) {
attrExt.idIndex = htods(i+1);
} else if (ae.name == class16) {
attrExt.classIndex = htods(i+1);
} else if (ae.name == style16) {
attrExt.styleIndex = htods(i+1);
}
}
}
} else if (type == TYPE_NAMESPACE) {
if (mNamespaceUri == RESOURCES_TOOLS_NAMESPACE) {
writeCurrentNode = false;
} else {
node.header.type = htods(RES_XML_START_NAMESPACE_TYPE);
extData = &namespaceExt;
extSize = sizeof(namespaceExt);
memset(&namespaceExt, 0, sizeof(namespaceExt));
if (mNamespacePrefix.size() > 0) {
namespaceExt.prefix.index =
htodl(strings.offsetForString(mNamespacePrefix));
} else {
namespaceExt.prefix.index = htodl((uint32_t)-1);
}
namespaceExt.prefix.index =
htodl(strings.offsetForString(mNamespacePrefix));
namespaceExt.uri.index = htodl(strings.offsetForString(mNamespaceUri));
}
LOG_ALWAYS_FATAL_IF(NA != 0, "Namespace nodes can't have attributes!");
} else if (type == TYPE_CDATA) {
node.header.type = htods(RES_XML_CDATA_TYPE);
extData = &cdataExt;
extSize = sizeof(cdataExt);
memset(&cdataExt, 0, sizeof(cdataExt));
cdataExt.data.index = htodl(strings.offsetForString(mChars));
cdataExt.typedData.size = htods(sizeof(cdataExt.typedData));
cdataExt.typedData.res0 = 0;
cdataExt.typedData.dataType = mCharsValue.dataType;
cdataExt.typedData.data = htodl(mCharsValue.data);
LOG_ALWAYS_FATAL_IF(NA != 0, "CDATA nodes can't have attributes!");
}
node.header.size = htodl(sizeof(node) + extSize + (sizeof(attr)*NA));
/* 初始化完成後將這個node和extData寫入dest所記錄的緩衝區中 */
if (writeCurrentNode) {
dest->writeData(&node, sizeof(node));
if (extSize > 0) {
dest->writeData(extData, extSize);
}
}
/* 將一個標籤的沒一個屬性建立一個ResXMLAttribute變數然後按照指定的順序
** 組織在dest所描述的緩衝區中, 注意:字串資訊被替換成其在StringPool
** 中成員變數中的位置值 */
for (i=0; i<NA; i++) {
ssize_t idx = mAttributeOrder.valueAt(i);
const attribute_entry& ae = mAttributes.itemAt(idx);
if (ae.ns.size() > 0) {
attr.ns.index = htodl(strings.offsetForString(ae.ns));
} else {
attr.ns.index = htodl((uint32_t)-1);
}
attr.name.index = htodl(ae.namePoolIdx);
if (!stripRawValues || ae.needStringValue()) {
attr.rawValue.index = htodl(strings.offsetForString(ae.string));
} else {
attr.rawValue.index = htodl((uint32_t)-1);
}
attr.typedValue.size = htods(sizeof(attr.typedValue));
if (ae.value.dataType == Res_value::TYPE_NULL
|| ae.value.dataType == Res_value::TYPE_STRING) {
attr.typedValue.res0 = 0;
attr.typedValue.dataType = Res_value::TYPE_STRING;
attr.typedValue.data = htodl(strings.offsetForString(ae.string));
} else {
attr.typedValue.res0 = 0;
attr.typedValue.dataType = ae.value.dataType;
attr.typedValue.data = htodl(ae.value.data);
}
dest->writeData(&attr, sizeof(attr));
}
for (i=0; i<NC; i++) {
status_t err = mChildren.itemAt(i)->flatten_node(strings, dest,
stripComments, stripRawValues);
if (err != NO_ERROR) {
return err;
}
}
/* 寫入標記資料寫入完成header */
if (type == TYPE_ELEMENT) {
ResXMLTree_endElementExt endElementExt;
memset(&endElementExt, 0, sizeof(endElementExt));
node.header.type = htods(RES_XML_END_ELEMENT_TYPE);
node.header.size = htodl(sizeof(node)+sizeof(endElementExt));
node.lineNumber = htodl(getEndLineNumber());
node.comment.index = htodl((uint32_t)-1);
endElementExt.ns.index = attrExt.ns.index;
endElementExt.name.index = attrExt.name.index;
dest->writeData(&node, sizeof(node));
dest->writeData(&endElementExt, sizeof(endElementExt));
} else if (type == TYPE_NAMESPACE) {
if (writeCurrentNode) {
node.header.type = htods(RES_XML_END_NAMESPACE_TYPE);
node.lineNumber = htodl(getEndLineNumber());
node.comment.index = htodl((uint32_t)-1);
node.header.size = htodl(sizeof(node)+extSize);
dest->writeData(&node, sizeof(node));
dest->writeData(extData, extSize);
}
}
return NO_ERROR;
}
綜上,我們在flatten函式中將各種屬性資訊組織成如下方式並儲存在一個AdaptFile物件中:
ResXMLTree_header -- 標記開始 |
ResStringPool_header -- 記錄StringPool中各種資訊 header->header.type = htods(RES_STRING_POOL_TYPE); // 記錄型別資訊 header->header.headerSize = htods(sizeof(*header)); // 記錄header大小 header->header.size = htodl(pool->getSize()); // 記錄StringPool資料總量大小 header->stringCount = htodl(ENTRIES); // 記錄字串條數 header->styleCount = htodl(STYLES); // 記錄格式個數 if (mUTF8) { header->flags |= htodl(ResStringPool_header::UTF8_FLAG); // 字元型別標記 } header->stringsStart = htodl(preSize); // 字元緩衝區起始位置 header->stylesStart = htodl(STYLES > 0 ? (preSize+strPos) : 0); // 字元格式起始位置 |
字串偏移陣列 |
字串格式偏移陣列 |
Xml文字檔案中收集到的所有字串資訊 |
描述上述字串的格式資訊,每種格式由一個ResStringPool_span組織,每個ResStringPool_span結構體採用ResStringPool_ref將其設定為ResStringPool_span::END作為結束 |
ResXMLTree_node -- 使用header標記xml檔案中一個標籤開始資訊,區分標籤中包含的資料型別: RES_XML_START_ELEMENT_TYPE -- 用於記錄屬性資訊的 RES_XML_START_NAMESPACE_TYPE -- 用於記錄名稱空間資訊的 RES_XML_CDATA_TYPE -- 用於記錄CDATA_TYPE型別資料資訊的 上述三種型別資料對應使用如下三種型別記錄一個標籤中其對應資訊: ResXMLTree_attrExt ResXMLTree_namespaceExt ResXMLTree_cdataExt |
將每一個屬性字串資訊組織成一個ResXMLTree_attribute物件 |
ResXMLTree_node -- 使用header標記xml檔案中一個標籤結束資訊 |
ResXMLTree_endElementExt--標記結束 |
如果有資源ID資訊,則將資源ID資訊儲存在這裡 |
ResXMLTree_header--標記結束 |
表格中藍色模組為字串資源池; 紫色模組為由ResXML*的各種資料結構組織起來各種XML文字檔案中資料,其中字串資訊都使用偏移量替換,我私自將其定為字串資源資料結構組織區.
注意:每個資源資料模組使用一個ResChunk_header作為區分,比如字串資源池使用ResStringPool_header標記開始以及記錄資訊,ResStringPool_header的第一個成員變數就是一個ResChunk_header; 而字串資源資料結構組織區以ResXMLTree_node標記開始以及記錄資訊,其第一個成員變數也是一個ResChunk_header
(3). 將壓平的字串資源資訊組織到ResXMLTree所描述的資料結構中
至此,我們在完成壓平xml文字檔案的工作之後,返回到parseXMLResource函式中呼叫ResXMLTree物件outTree的setTo成員函式將儲存在AdaptFile物件rsc中的資料組織到outTree中. setTo函式的具體實現如下:
路徑:frameworks/base/libs/androidfw/ResourcesTypes.cpp
status_t ResXMLTree::setTo(const void* data, size_t size, bool copyData)
{
if (!data || !size) {
return (mError=BAD_TYPE);
}
uninit(); // 初始化各種變數
mEventCode = START_DOCUMENT;
// 在這裡需要拷貝資料, 故將按照上述組織方式組織在AdaptFile物件rsc中
// 資料緩衝區中的資料拷貝到ResXMLTree成員變數mOwnedData所指向
// 的資料緩衝區中, 而rsc資料緩衝區中由mData指向,其內容如上述表格所示
if (copyData) {
mOwnedData = malloc(size);
if (mOwnedData == NULL) {
return (mError=NO_MEMORY);
}
memcpy(mOwnedData, data, size);
data = mOwnedData;
}
/* 按照上述表格所描述的資料緩衝區內容逐一解析
** 首先取出頭:ResXMLTree_header */
mHeader = (const ResXMLTree_header*)data;
mSize = dtohl(mHeader->header.size);
if (dtohs(mHeader->header.headerSize) > mSize || mSize > size) {
restart();
return mError;
}
// 將mDataEnd指向緩衝區末尾
mDataEnd = ((const uint8_t*)mHeader) + mSize;
/* 初始化 */
mStrings.uninit();
mRootNode = NULL;
mResIds = NULL;
mNumResIds = 0;
// First look for a couple interesting chunks: the string block
// and first XML node.
// 取出資料緩衝區中的第一個ResChunk_header, 也就是儲存在
// ResStringPool_header中的header, 如上述表格所示
const ResChunk_header* chunk =
(const ResChunk_header*)(((const uint8_t*)mHeader) +
dtohs(mHeader->header.headerSize));
const ResChunk_header* lastChunk = chunk;
/* 這裡while迴圈逐一取出標記各個資源資料模組的ResChunk_header,
** 在這裡我們有兩個上述描述兩個資料模組 */
while (((const uint8_t*)chunk) < (mDataEnd-sizeof(ResChunk_header)) &&
((const uint8_t*)chunk) < (mDataEnd-dtohl(chunk->size))) {
status_t err = validate_chunk(chunk, sizeof(ResChunk_header), mDataEnd, "XML");
if (err != NO_ERROR) {
mError = err;
goto done;
}
const uint16_t type = dtohs(chunk->type);
const size_t size = dtohl(chunk->size);
// 如果取出的資源型別是字串則將其組織儲存在成員變數mStrings中
// 在這裡會將上述儲存在字串資源池中的內容儲存到mStrings中去
// mString是一個ResStringPool,最終資源池中資料就儲存到其裡面
if (type == RES_STRING_POOL_TYPE) {
mStrings.setTo(chunk, size);
} else if (type == RES_XML_RESOURCE_MAP_TYPE) {
mResIds = (const uint32_t*)
(((const uint8_t*)chunk)+dtohs(chunk->headerSize));
mNumResIds =
(dtohl(chunk->size)-dtohs(chunk->headerSize))/sizeof(uint32_t);
/* 這裡儲存字串資源資料結構組織區*/
} else if (type >= RES_XML_FIRST_CHUNK_TYPE
&& type <= RES_XML_LAST_CHUNK_TYPE) {
if (validateNode((const ResXMLTree_node*)chunk) != NO_ERROR) {
mError = BAD_TYPE;
goto done;
}
mCurNode = (const ResXMLTree_node*)lastChunk;
if (nextNode() == BAD_DOCUMENT) {
mError = BAD_TYPE;
goto done;
}
// 用mRootNode儲存資料結構區根節點資料
mRootNode = mCurNode;
mRootExt = mCurExt;
mRootCode = mEventCode;
break;
} else {
XML_NOISY(printf("Skipping unknown chunk!\n"));
}
lastChunk = chunk;
chunk = (const ResChunk_header*)
(((const uint8_t*)chunk) + size);
}
if (mRootNode == NULL) {
ALOGW("Bad XML block: no root element node found\n");
mError = BAD_TYPE;
goto done;
}
mError = mStrings.getError();
done:
restart();
return mError;
}
至此,我們在呼叫parseXMLResources函式解析完了AndroidManifest.xml檔案,接下來我們返回到parsePackage函式中繼續分析其後續工作過程,具體實現如下所示:
路徑:frameworks/base/tools/aapt/Resource.cpp
static status_t parsePackage(Bundle* bundle, const sp<AaptAssets>& assets,
const sp<AaptGroup>& grp)
{
// 以下程式碼確保只有一個AndroidManifest.xml檔案
if (grp->getFiles().size() != 1) {
fprintf(stderr, "warning: Multiple AndroidManifest.xml files found, using %s\n",
grp->getFiles().valueAt(0)->getPrintableSource().string());
}
// 取出存放AndroidManifest.xml檔案資訊的AaptFile物件
sp<AaptFile> file = grp->getFiles().valueAt(0);
// 定義一個ResXMLTree物件,然後呼叫parseXMLResource來詳細解析
// AndroidManifest.xml檔案
// 這個函式主要完成三個工作:
// 1. 收集file檔案指向的xml檔案中字串資源資訊.
// 2. 壓平該file檔案只想的xml檔案中資源資訊
// 3. 將前兩步組織的到的資源資訊最終組織到一個ResXMLTree所描述的資料結構中.
ResXMLTree block;
status_t err = parseXMLResource(file, &block);
if (err != NO_ERROR) {
return err;
}
//printXMLBlock(&block);
ResXMLTree::event_code_t code;
/* 下列while迴圈找到起開始位置 */
while ((code=block.next()) != ResXMLTree::START_TAG
&& code != ResXMLTree::END_DOCUMENT
&& code != ResXMLTree::BAD_DOCUMENT) {
}
size_t len;
if (code != ResXMLTree::START_TAG) {
fprintf(stderr, "%s:%d: No start tag found\n",
file->getPrintableSource().string(), block.getLineNumber());
return UNKNOWN_ERROR;
}
// 首先找到manifest標籤
if (strcmp16(block.getElementName(&len), String16("manifest").string()) != 0) {
fprintf(stderr, "%s:%d: Invalid start tag %s, expected <manifest>\n",
file->getPrintableSource().string(), block.getLineNumber(),
String8(block.getElementName(&len)).string());
return UNKNOWN_ERROR;
}
// 再找到pacakge標籤屬性所在block中的索引位置
ssize_t nameIndex = block.indexOfAttribute(NULL, "package");
if (nameIndex < 0) {
fprintf(stderr, "%s:%d: <manifest> does not have package attribute.\n",
file->getPrintableSource().string(), block.getLineNumber());
return UNKNOWN_ERROR;
}
// 獲取正在編譯的資源的包名,並設定將其儲存到assets的成員變數
// mPackage中
assets->setPackage(String8(block.getAttributeStringValue(nameIndex, &len)));
String16 uses_sdk16("uses-sdk");
/* 找到SDK版本並設定minSdkVersion */
while ((code=block.next()) != ResXMLTree::END_DOCUMENT
&& code != ResXMLTree::BAD_DOCUMENT) {
if (code == ResXMLTree::START_TAG) {
if (strcmp16(block.getElementName(&len), uses_sdk16.string()) == 0) {
ssize_t minSdkIndex =
block.indexOfAttribute(RESOURCES_ANDROID_NAMESPACE,
"minSdkVersion");
if (minSdkIndex >= 0) {
const uint16_t* minSdk16 =
block.getAttributeStringValue(minSdkIndex, &len);
const char* minSdk8 = strdup(String8(minSdk16).string());
bundle->setManifestMinSdkVersion(minSdk8);
}
}
}
}
return NO_ERROR;
}
至此,我們就編譯完成了AndroidManifest.xml檔案接下來返回到buildResources函式繼續分析後面的build工作,具體實現如下所示:
2. 建立一個ResourcesTable收集當前應用程式編譯所依賴的資源
(1). 首先收集當前編譯應用程式所依賴的系統資源包android.jar資訊
路徑:frameworks/base/tools/aapt/Resource.cpp
status_t buildResources(Bundle* bundle, const sp<AaptAssets>& assets)
{
......
/* 根據包名建立一個對應的ResourceTable */
ResourceTable table(bundle, String16(assets->getPackage()));
/* 呼叫ResourceTable的成員函式addIncludedResources新增其所引用的android.jar
** 包的路徑資訊 */
err = table.addIncludedResources(bundle, assets);
if (err != NO_ERROR) {
return err;
}
......
}
ResourceTable的成員函式addIncludedResources具體實現如下所示:
路徑:frameworks/base/tools/aapt/ResourceTable.cpp
status_t ResourceTable::addIncludedResources(Bundle* bundle,
const sp<AaptAssets>& assets)
{
/* 呼叫AaptAssets的成員函式buildIncludedResources將當前包所依賴系統的
** android.jar包路徑資訊新增到assets的成員變數mIncludedAssets中 */
status_t err = assets->buildIncludedResources(bundle);
if (err != NO_ERROR) {
return err;
}
// For future reference to included resources.
mAssets = assets; // 將ResourceTable類的成員變數mAssets指向assets
/* 接著呼叫AaptAssets的成員函式getIncludedResources獲取一個
** ResTable物件用於描述當前APK所引用的android.jar包中的資源資訊 */
const ResTable& incl = assets->getIncludedResources();
// Retrieve all the packages.
// 恢復所有儲存在ResTable中的包
/* Android系統定義了一套通用資源,這些資源可以被應用程式引用。例如,
** 我們在XML佈局檔案中指定一個LinearLayout的 android:orientation屬性的值為
** “vertical”時,這個“vertical”實際上就是在系統資源包裡面定義的一個值。
** 從上面的分析就可以看出,我們在編譯一個Android應用程式的資源的時候,
** 至少會涉及到兩個包,其中一個被引用的系統資源包,另外一個就是當前正在
** 編譯的應用程式資源包。每一個包都可以定義自己的資源,同時它也可以引用
** 其它包的資源。那麼,一個包是通過什麼方式來引用其它包的資源的呢?這就是
** 我們熟悉的資源ID了。資源ID是一個4位元組的無符號整數,其中,最高位元組表示
** Package ID,次高位元組表示Type ID,最低兩位元組表示Entry ID。
*/
const size_t N = incl.getBasePackageCount();
for (size_t phase=0; phase<2; phase++) {
for (size_t i=0; i<N; i++) {
String16 name(incl.getBasePackageName(i));
uint32_t id = incl.getBasePackageId(i);
// First time through: only add base packages (id
// is not 0); second time through add the other
// packages.
if (phase != 0) {
if (id != 0) {
// Skip base packages -- already one.
id = 0;
} else {
// Assign a dynamic id.
id = mNextPackageId;
}
} else if (id != 0) {
if (id == 127) {
if (mHaveAppPackage) {
return UNKNOWN_ERROR;
}
mHaveAppPackage = true;
}
if (mNextPackageId > id) {
fprintf(stderr, "Included base package ID %d already in use!\n", id);
return UNKNOWN_ERROR;
}
}
if (id != 0) {
sp<Package> p = new Package(name, id);
mPackages.add(name, p);
mOrderedPackages.add(p);
if (id >= mNextPackageId) {
mNextPackageId = id+1;
}
}
}
}
// Every resource table always has one first entry, the bag attributes.
const SourcePos unknown(String8("????"), 0);
sp<Type> attr = getType(mAssetsPackage, String16("attr"), unknown);
return NO_ERROR;
}
AaptAssets的成員函式buildIncludedResources函式的具體實現如下所示:
路徑:frameworks/base/tools/aapt/AaptAssets.cpp
status_t AaptAssets::buildIncludedResources(Bundle* bundle)
{
if (!mHaveIncludedAssets) {
// Add in all includes.
// 首先獲取我們使用-I選項所制定的android.jar包路徑資訊
const Vector<const char*>& incl = bundle->getPackageIncludes();
const size_t N=incl.size();
/* 將指定的所有android.jar的路徑資訊新增到當前物件成員變數
** mIncludedAssets中, mIncludedAssets的成員變數是一個AssetManager
** 物件 */
for (size_t i=0; i<N; i++) {
if (bundle->getVerbose())
printf("Including resources from package: %s\n", incl[i]);
/* 最終呼叫AssetManager物件的addAssetPath將路徑新增到其成員變數
** mAssetPaths中 */
if (!mIncludedAssets.addAssetPath(String8(incl[i]), NULL)) {
fprintf(stderr, "ERROR: Asset package include '%s' not found.\n",
incl[i]);
return UNKNOWN_ERROR;
}
}
mHaveIncludedAssets = true;
}
return NO_ERROR;
}
mIncludedAssets是一個AssetManager類,其成員函式getResources的具體實現如下所示:
路徑: frameworks/base/libs/androidfw/AssetManager.cpp
bool AssetManager::addAssetPath(const String8& path, void** cookie)
{
AutoMutex _l(mLock);
asset_path ap;
/* 利用現有資訊初始化一個asset_path物件 */
String8 realPath(path);
if (kAppZipName) {
realPath.appendPath(kAppZipName);
}
ap.type = ::getFileType(realPath.string());
if (ap.type == kFileTypeRegular) {
ap.path = realPath;
} else {
ap.path = path;
ap.type = ::getFileType(path.string());
if (ap.type != kFileTypeDirectory && ap.type != kFileTypeRegular) {
ALOGW("Asset path %s is neither a directory nor file (type=%d).",
path.string(), (int)ap.type);
return false;
}
}
// Skip if we have it already.
// 檢測是否新增過該檔案
for (size_t i=0; i<mAssetPaths.size(); i++) {
if (mAssetPaths[i].path == ap.path) {
if (cookie) {
*cookie = (void*)(i+1);
}
return true;
}
}
mAssetPaths.add(ap); // 將檔案資訊新增到mAssetPaths中去
// new paths are always added at the end
if (cookie) {
*cookie = (void*)mAssetPaths.size();
}
// add overlay packages for /system/framework; apps are handled by the
// (Java) package manager
// 判斷是否是/system/framework開頭的系統資源.
........
return true;
}
AaptAssets的成員函式getIncludedResources具體實現如下所示:
路徑:frameworks/base/tools/aapt/AaptAssets.cpp
const ResTable& AaptAssets::getIncludedResources() const
{
return mIncludedAssets.getResources(false);
}
mIncludedAssets是一個AssetManager類,其成員函式getResources的具體實現如下所示:
路徑: frameworks/base/libs/androidfw/AssetManager.cpp
const ResTable& AssetManager::getResources(bool required) const
{
const ResTable* rt = getResTable(required);
return *rt;
}
/* getResTable函式是一個Singleton */
const ResTable* AssetManager::getResTable(bool required) const
{
/* 如果已經為AssetManager類的成員變數mResources分配了空間,則直接返回它 */
ResTable* rt = mResources;
if (rt) {
return rt;
}
// Iterate through all asset packages, collecting resources from each.
AutoMutex _l(mLock);
if (mResources != NULL) {
return mResources;
}
/* 載入一些chache檔案 */
if (mCacheMode != CACHE_OFF && !mCacheValid)
const_cast<AssetManager*>(this)->loadFileNameCacheLocked();
/* 逐個掃描所包含的android.jar檔案路徑 */
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); // 取出路徑
// 開啟idmap, 在這裡並未建立過idmap故,返回idmap指向NULL
Asset* idmap = openIdmapLocked(ap);
/* 判斷ap所指向的檔案型別 */
if (ap.type != kFileTypeDirectory) {
/* 如果ap所指向的檔案型別不是目錄檔案 */
if (i == 0) {
// The first item is typically the framework resources,
// which we want to avoid parsing every time.
// 為第一個指向的android.jar包建立一個對應的SharedZip
// 物件,並將其成員變數mResourceTable返回
sharedRes = const_cast<AssetManager*>(this)->
mZipSet.getZipResourceTable(ap.path);
}
// 在這裡返回NULL,表示尚未為該android.jar建立過一個ResTable物件
if (sharedRes == NULL) {
/* 返回對應android.jar包的SharedZip中的mResourceTableAsset物件
*/
ass = const_cast<AssetManager*>(this)->
mZipSet.getZipResourceTableAsset(ap.path);
// 在這裡返回的ass物件為NULL,表示尚未為該android.jar建立過一個
// Asset物件
if (ass == NULL) {
ass = const_cast<AssetManager*>(this)->
openNonAssetInPathLocked("resources.arsc",
Asset::ACCESS_BUFFER,
ap);
if (ass != NULL && ass != kExcludedAsset) {
/* 到這裡我們就為一個android.jar包建立了一個Asset物件
** 並將其儲存到與之對應的SharedZip類物件的
** mResourceTableAsset中 */
ass = const_cast<AssetManager*>(this)->
mZipSet.setZipResourceTableAsset(ap.path, ass);
}
}
/* 為android.jar包建立一個與之對應的ResTable類物件,並將其儲存
** 到與之對應的SharedZip的成員變數mResourceTable中 */
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.
sharedRes = new ResTable();
sharedRes->add(ass, (void*)(i+1), false, idmap);
sharedRes = const_cast<AssetManager*>(this)->
mZipSet.setZipResourceTable(ap.path, sharedRes);
}
}
} else { // 如果ap指向的是一個目錄檔案,則執行如下程式碼
Asset* ass = const_cast<AssetManager*>(this)->
openNonAssetInPathLocked("resources.arsc",
Asset::ACCESS_BUFFER,
ap);
shared = false;
}
/* 完成了為一個android.jar包建立一個與之對應的Asset和ResTable物件之後,
** 則新建一個ResTable物件用於初始化mResources成員變數 */
if ((ass != NULL || sharedRes != NULL) && ass != kExcludedAsset) {
if (rt == NULL) {
mResources = rt = new ResTable();
/* 更新mResources的引數 */
updateResourceParamsLocked();
}
if (sharedRes != NULL) {
ALOGV("Copying existing resources for %s", ap.path.string());
rt->add(sharedRes);
} else {
ALOGV("Parsing resources for %s", ap.path.string());
rt->add(ass, (void*)(i+1), !shared, idmap);
}
if (!shared) {
delete ass;
}
}
if (idmap != NULL) {
delete idmap;
}
}
/* 建立一個ResTable類物件,由mResources和rt指向 */
if (!rt) {
mResources = rt = new ResTable();
}
return rt;
}
/* 以下為SharedZip類成員函式的具體實現如下所示: */
AssetManager::SharedZip::SharedZip(const String8& path, time_t modWhen)
: mPath(path), mZipFile(NULL), mModWhen(modWhen),
mResourceTableAsset(NULL), mResourceTable(NULL)
{
mZipFile = new ZipFileRO; // 建立一個ZipFileRO物件
/* 呼叫mZipFile成員函式open初始化其成員變數 */
if (mZipFile->open(mPath.string()) != NO_ERROR) {
delete mZipFile;
mZipFile = NULL;
}
}
/* get方法用於獲取一個SharedZip物件,該方法同樣實現為Singleton模式 */
sp<AssetManager::SharedZip> AssetManager::SharedZip::get(const String8& path)
{
AutoMutex _l(gLock);
time_t modWhen = getFileModDate(path);
sp<SharedZip> zip = gOpen.valueFor(path).promote();
if (zip != NULL && zip->mModWhen == modWhen) {
return zip;
}
zip = new SharedZip(path, modWhen);
gOpen.add(path, zip);
return zip;
}
Asset* AssetManager::SharedZip::getResourceTableAsset()
{
return mResourceTableAsset;
}
ResTable* AssetManager::SharedZip::getResourceTable()
{
return mResourceTable;
}
/* 以下為AssetManager::ZipSet中個成員函式的實現,具體實現如下所示 */
Asset* AssetManager::ZipSet::getZipResourceTableAsset(const String8& path)
{
int idx = getIndex(path);
sp<SharedZip> zip = mZipFile[idx];
if (zip == NULL) {
zip = SharedZip::get(path);
mZipFile.editItemAt(idx) = zip;
}
// 返回SharedZip的成員變數mResourceTableAsset(Asset*)
return zip->getResourceTableAsset();
}
ResTable* AssetManager::ZipSet::getZipResourceTable(const String8& path)
{
int idx = getIndex(path); // 獲取其在mZipPath中的索引值
// 獲取在mZipFile中的對應索引所指向的SharedZip物件
sp<SharedZip> zip = mZipFile[idx];
// 如果尚未為對應的android.jar包建立一個SharedZip物件則為其建立一個新的
// 物件並將其儲存到mZipFile中去
if (zip == NULL) {
/* get方法用於建立一個新的SharedZip物件 */
zip = SharedZip::get(path);
mZipFile.editItemAt(idx) = zip;
}
// 將SharedZip物件zip的成員變數mResourceTable(ResTable*)返回, 這裡為NULL
return zip->getResourceTable();
}
int AssetManager::ZipSet::getIndex(const String8& zip) const
{
/* 檢測是否在ZipSet的成員變數mZipPath中是否包含當前的
** android.jar包 */
const size_t N = mZipPath.size();
for (size_t i=0; i<N; i++) {
if (mZipPath[i] == zip) {
return i;
}
}
/* 將當前android.jar包新增到其成員變數mZipPath中,並將mZipFile末尾元素
** 設定為NULL */
mZipPath.add(zip);
mZipFile.add(NULL);
return mZipPath.size()-1; // 將mZipPath最後一個元素索引返回給呼叫者
}
在上面的SharedZip類建構函式中,我們會建立一個與android.jar對應的SharedZip物件時會為其成員變數mZipFile建立一個ZipFileRO物件,並呼叫ZipFileRO物件的成員函式open進行初始化工作,具體實現如下所示:
路徑:frameworks/native/include/utils/ZipFileRO.h
class ZipFileRO {
public:
ZipFileRO()
: mFd(-1), mFileName(NULL), mFileLength(-1),
mDirectoryMap(NULL),
mNumEntries(-1), mDirectoryOffset(-1),
mHashTableSize(-1), mHashTable(NULL)
{}
......
/*
* Open an archive.
*/
status_t open(const char* zipFileName);
......
};
其成員函式open的實現如下所示:
路徑:frameworks/native/libs/utils/ZipFileRO.cpp
/*
* Open the specified file read-only. We memory-map the entire thing and
* close the file before returning.
* 以只讀方式開啟一個zip檔案,使用ZipFileRO類物件來初始化該類物件
*/
status_t ZipFileRO::open(const char* zipFileName)
{
int fd = -1;
assert(mDirectoryMap == NULL);
/*
* Open and map the specified file.
* 以只讀方式開啟該zip檔案 */
fd = ::open(zipFileName, O_RDONLY | O_BINARY);
if (fd < 0) {
ALOGW("Unable to open zip '%s': %s\n", zipFileName, strerror(errno));
return NAME_NOT_FOUND;
}
/* 計算該zip檔案大小 */
mFileLength = lseek64(fd, 0, SEEK_END);
if (mFileLength < kEOCDLen) {
TEMP_FAILURE_RETRY(close(fd));
return UNKNOWN_ERROR;
}
if (mFileName != NULL) {
free(mFileName);
}
/* 初始化一個ZipFileRO類物件的成員變數mFileName和mFd */
mFileName = strdup(zipFileName);
mFd = fd;
/* 以下兩個函式主要用於解析一個ZIP檔案, 與ZIP壓縮相關,我們就不詳細
** 分析 */
/*
* Find the Central Directory and store its size and number of entries.
* mapCentralDirectory函式用於找到核心資料夾以及儲存它的大小和
* entry的個數
* 壓縮原始檔資料區+壓縮原始檔目錄區+壓縮原始檔目錄結束標誌
* 在這個資料區中每一個壓縮的原始檔/目錄都是一條記錄,記錄的格式如下:
* [檔案頭+ 檔案資料 + 資料描述符] */
if (!mapCentralDirectory()) {
goto bail;
}
/*
* Verify Central Directory and create data structures for fast access.
* 驗證核心目錄併為快速訪問建立資料結構 */
if (!parseZipArchive()) {
goto bail;
}
return OK;
bail:
free(mFileName);
mFileName = NULL;
TEMP_FAILURE_RETRY(close(fd));
return UNKNOWN_ERROR;
}
至此,我們就為我們使用-I選項指定的android.jar包建立了一個SharedZip類物件,下面我們就使用openNonAssetInPathLock函式來為該android.jar包建立一個Asset類物件,其具體實現如下所示:
路徑: frameworks/base/libs/androidfw/AssetManager.cpp
/*
* Open a non-asset file as if it were an asset, searching for it in the
* specified app.
*
* Pass in a NULL values for "appName" if the common app directory should
* be used.
*
* 如果尚未為我們使用-I選項指定的android.jar包建立過一個Asset物件和
* 和ResTable物件, 那麼我們呼叫openNonAssetInPathLocked函式為其建立一個
** Asset類物件 */
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) {
......
/* look inside the zip file */
} else {
String8 path(fileName);
/* check the appropriate Zip file */
ZipFileRO* pZip;
ZipEntryRO entry;
/* 返回與fileName對應的SharedZip物件的成員變數mZipFile */
pZip = getZipFileLocked(ap);
if (pZip != NULL) {
//printf("GOT zip, checking NA '%s'\n", (const char*) path);
/* 尋找一個與android.jar包對應的ZipEntryRO專案條目 */
entry = pZip->findEntryByName(path.string());
if (entry != NULL) {
/* 使用一個ZIP壓縮包的ZIPEntryRO專案條目建立一個新的
** Asset物件,如果這個條目沒有被壓縮,我們可能想要建立
** 或者共享一片共享記憶體 */
pAsset = openAssetFromZipLocked(pZip, entry, mode, path);
}
}
if (pAsset != NULL) {
/* create a "source" name, for debug/display
** 將pAsset的成員變數mAssetSource設定為android.jar:/resources.arsc
*/
pAsset->setAssetSource(
createZipSourceNameLocked(ZipSet::getPathName(
ap.path.string()), String8(""), String8(fileName)));
}
}
return pAsset;
}
(2). 收集當前應用程式包中的資原始檔資訊新增到ResourceTable中
至此,我們就將收集在AaptAsset中的資源儲存到了一個ResourceTable物件中了,下面我們繼續分析buildResource的過程,具體實現如下所示:
路徑:frameworks/base/tools/aapt/Resource.cpp
#define ASSIGN_IT(n) \
do { \
ssize_t index = resources->indexOfKey(String8(#n)); \
if (index >= 0) { \
n ## s = resources->valueAt(index); \
} \
} while (0)
status_t buildResources(Bundle* bundle, const sp<AaptAssets>& assets)
{
// Standard flags for compiled XML and optional UTF-8 encoding
// 設定編譯XML檔案的選項為標準和UTF-8的編碼方式
int xmlFlags = XML_COMPILE_STANDARD_RESOURCE;
/* Only enable UTF-8 if the caller of aapt didn't specifically
* request UTF-16 encoding and the parameters of this package
* allow UTF-8 to be used.
*/
if (!bundle->getUTF16StringsOption()) {
xmlFlags |= XML_COMPILE_UTF8;
}
// resType -> leafName -> group
KeyedVector<String8, sp<ResourceTypeSet> > *resources =
new KeyedVector<String8, sp<ResourceTypeSet> >;
/* 呼叫collect_files將前面收集到assets中的各類資原始檔重新收集到resources中來 */
collect_files(assets, resources);
/* 定義收集各類資原始檔的容器 */
sp<ResourceTypeSet> drawables;
sp<ResourceTypeSet> layouts;
sp<ResourceTypeSet> anims;
sp<ResourceTypeSet> animators;
sp<ResourceTypeSet> interpolators;
sp<ResourceTypeSet> xmls;
sp<ResourceTypeSet> raws;
sp<ResourceTypeSet> colors;
sp<ResourceTypeSet> menus;
sp<ResourceTypeSet> mipmaps;
/* 將儲存到resources中的各類檔案的Set儲存到我們上述定義的Set中去 */
ASSIGN_IT(drawable);
ASSIGN_IT(layout);
ASSIGN_IT(anim);
ASSIGN_IT(animator);
ASSIGN_IT(interpolator);
ASSIGN_IT(xml);
ASSIGN_IT(raw);
ASSIGN_IT(color);
ASSIGN_IT(menu);
ASSIGN_IT(mipmap);
// 設定assets的資源為resources中儲存的
assets->setResources(resources);
// now go through any resource overlays and collect their files
/* 判斷當前應用程式是否有overlay的資源,有的話將assets中儲存
** 的資源設定為overlay中的 */
sp<AaptAssets> current = assets->getOverlay();
while(current.get()) {
KeyedVector<String8, sp<ResourceTypeSet> > *resources =
new KeyedVector<String8, sp<ResourceTypeSet> >;
current->setResources(resources);
collect_files(current, resources);
current = current->getOverlay();
}
// apply the overlay files to the base set
// 如果有overlay資源則使用overlay資源替換現有資源
if (!applyFileOverlay(bundle, assets, &drawables, "drawable") ||
!applyFileOverlay(bundle, assets, &layouts, "layout") ||
!applyFileOverlay(bundle, assets, &anims, "anim") ||
!applyFileOverlay(bundle, assets, &animators, "animator") ||
!applyFileOverlay(bundle, assets, &interpolators, "interpolator") ||
!applyFileOverlay(bundle, assets, &xmls, "xml") ||
!applyFileOverlay(bundle, assets, &raws, "raw") ||
!applyFileOverlay(bundle, assets, &colors, "color") ||
!applyFileOverlay(bundle, assets, &menus, "menu") ||
!applyFileOverlay(bundle, assets, &mipmaps, "mipmap")) {
return UNKNOWN_ERROR;
}
bool hasErrors = false;
// 如果當前應用程式有drawables資源,則首先呼叫preProcessImages函式預處理
// 影象,然後呼叫makeFileResources函式處理drawables中的資源
if (drawables != NULL) {
if (bundle->getOutputAPKFile() != NULL) {
err = preProcessImages(bundle, assets, drawables, "drawable");
}
if (err == NO_ERROR) {
err = makeFileResources(bundle, assets, &table, drawables, "drawable");
if (err != NO_ERROR) {
hasErrors = true;
}
} else {
hasErrors = true;
}
}
......
// compile resources
current = assets;
while(current.get()) {
KeyedVector<String8, sp<ResourceTypeSet> > *resources =
current->getResources();
ssize_t index = resources->indexOfKey(String8("values"));
if (index >= 0) {
ResourceDirIterator it(resources->valueAt(index), String8("values"));
ssize_t res;
while ((res=it.next()) == NO_ERROR) {
sp<AaptFile> file = it.getFile();
res = compileResourceFile(bundle, assets, file, it.getParams(),
(current!=assets), &table);
if (res != NO_ERROR) {
hasErrors = true;
}
}
}
current = current->getOverlay();
}
......
}
按類別將AaptAssets類物件中收集的資源儲存到ResourceTypeSet為元素的Vector中具體實現如下所示:
路徑:frameworks/base/tools/aapt/Resource.cpp
/* 按類別將儲存在assets中的資原始檔進行歸類處理 */
static void collect_files(const sp<AaptDir>& dir,
KeyedVector<String8, sp<ResourceTypeSet> >* resources)
{
const DefaultKeyedVector<String8, sp<AaptGroup> >& groups = dir->getFiles();
int N = groups.size(); // 獲取資原始檔夾下的資原始檔個數,逐個掃描
for (int i=0; i<N; i++) {
String8 leafName = groups.keyAt(i);
const sp<AaptGroup>& group = groups.valueAt(i);
const DefaultKeyedVector<AaptGroupEntry, sp<AaptFile> >& files
= group->getFiles();
if (files.size() == 0) {
continue;
}
/* 按照資原始檔的型別新建一個對應的ResourceTypeSet容器儲存各類
** 檔案, 最終儲存到resource中去 */
String8 resType = files.valueAt(0)->getResourceType();
ssize_t index = resources->indexOfKey(resType);
/* 如果index小於0表示還未為resType所描述的型別資原始檔建立一個對應的
** Set物件,於是為其新建一個 */
if (index < 0) {
sp<ResourceTypeSet> set = new ResourceTypeSet();
set->add(leafName, group);
resources->add(resType, set);
} else {
sp<ResourceTypeSet> set = resources->valueAt(index);
index = set->indexOfKey(leafName);
if (index < 0) {
set->add(leafName, group);
} else {
sp<AaptGroup> existingGroup = set->valueAt(index);
for (size_t j=0; j<files.size(); j++) {
status_t err = existingGroup->addFile(files.valueAt(j));
}
}
}
}
}
static void collect_files(const sp<AaptAssets>& ass,
KeyedVector<String8, sp<ResourceTypeSet> >* resources)
{
const Vector<sp<AaptDir> >& dirs = ass->resDirs();
int N = dirs.size();
/* 逐個收集assets資原始檔夾下的各類資原始檔 */
for (int i=0; i<N; i++) {
sp<AaptDir> d = dirs.itemAt(i);
collect_files(d, resources);
// don't try to include the res dir
// 不打算包含資原始檔夾本身
ass->removeDir(d->getLeaf());
}
}
/* 預處理影象, 目前只支援處理png格式影象 */
static status_t preProcessImages(const Bundle* bundle, const sp<AaptAssets>& assets,
const sp<ResourceTypeSet>& set, const char* type)
{
volatile bool hasErrors = false;
ssize_t res = NO_ERROR;
if (bundle->getUseCrunchCache() == false) {
/* 建立一個工作佇列來預處理影象 */
WorkQueue wq(MAX_THREADS, false);
ResourceDirIterator it(set, String8(type));
while ((res=it.next()) == NO_ERROR) {
/* 建立一個工作單元執行緒然後馬上使其工作預處理影象 */
PreProcessImageWorkUnit* w = new PreProcessImageWorkUnit(
bundle, assets, it.getFile(), &hasErrors);
status_t status = wq.schedule(w);
if (status) {
fprintf(stderr, "preProcessImages failed: schedule() returned %d\n", status);
hasErrors = true;
delete w;
break;
}
}
status_t status = wq.finish(); // 處理完後釋放資源
if (status) {
fprintf(stderr, "preProcessImages failed: finish() returned %d\n", status);
hasErrors = true;
}
}
return (hasErrors || (res < NO_ERROR)) ? UNKNOWN_ERROR : NO_ERROR;
}
/* 這是用於預處理影象的工作單元 */
class PreProcessImageWorkUnit : public WorkQueue::WorkUnit {
public:
PreProcessImageWorkUnit(const Bundle* bundle, const sp<AaptAssets>& assets,
const sp<AaptFile>& file, volatile bool* hasErrors) :
mBundle(bundle), mAssets(assets), mFile(file), mHasErrors(hasErrors) {
}
// 真正的處理工作是在run方法中的preProcessImage中進行
virtual bool run() {
status_t status = preProcessImage(mBundle, mAssets, mFile, NULL);
if (status) {
*mHasErrors = true;
}
return true; // continue even if there are errors
}
private:
const Bundle* mBundle;
sp<AaptAssets> mAssets;
sp<AaptFile> mFile;
volatile bool* mHasErrors;
};
/* 呼叫png影象處理庫對png影象進行打包處理 */
status_t preProcessImage(const Bundle* bundle, const sp<AaptAssets>& assets,
const sp<AaptFile>& file, String8* outNewLeafName)
{
String8 ext(file->getPath().getPathExtension()); // 獲取副檔名
// We currently only process PNG images. 我們僅僅處理png格式的影象
if (strcmp(ext.string(), ".png") != 0) {
return NO_ERROR;
}
String8 printableName(file->getPrintableSource()); // 獲取檔名
if (bundle->getVerbose()) {
printf("Processing image: %s\n", printableName.string());
}
png_structp read_ptr = NULL;
png_infop read_info = NULL;
FILE* fp;
image_info imageInfo;
png_structp write_ptr = NULL;
png_infop write_info = NULL;
status_t error = UNKNOWN_ERROR;
const size_t nameLen = file->getPath().length();
/* 以只讀方式開啟該檔案 */
fp = fopen(file->getSourceFile().string(), "rb");
if (fp == NULL) {
fprintf(stderr, "%s: ERROR: Unable to open PNG file\n", printableName.string());
goto bail;
}
/* 以下使用png庫打包處理png格式的影象 */
read_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING,
0, (png_error_ptr)NULL, (png_error_ptr)NULL);
if (!read_ptr) {
goto bail;
}
read_info = png_create_info_struct(read_ptr);
if (!read_info) {
goto bail;
}
if (setjmp(png_jmpbuf(read_ptr))) {
goto bail;
}
png_init_io(read_ptr, fp);
read_png(printableName.string(), read_ptr, read_info, &imageInfo);
if (nameLen > 6) {
const char* name = file->getPath().string();
if (name[nameLen-5] == '9' && name[nameLen-6] == '.') {
if (do_9patch(printableName.string(), &imageInfo) != NO_ERROR) {
goto bail;
}
}
}
write_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING,
0, (png_error_ptr)NULL, (png_error_ptr)NULL);
if (!write_ptr)
{
goto bail;
}
write_info = png_create_info_struct(write_ptr);
if (!write_info)
{
goto bail;
}
png_set_write_fn(write_ptr, (void*)file.get(),
png_write_aapt_file, png_flush_aapt_file);
if (setjmp(png_jmpbuf(write_ptr)))
{
goto bail;
}
write_png(printableName.string(), write_ptr, write_info, imageInfo,
bundle->getGrayscaleTolerance());
error = NO_ERROR;
if (bundle->getVerbose()) {
fseek(fp, 0, SEEK_END);
size_t oldSize = (size_t)ftell(fp);
size_t newSize = file->getSize();
float factor = ((float)newSize)/oldSize;
int percent = (int)(factor*100);
}
bail:
if (read_ptr) {
png_destroy_read_struct(&read_ptr, &read_info, (png_infopp)NULL);
}
if (fp) {
fclose(fp);
}
if (write_ptr) {
png_destroy_write_struct(&write_ptr, &write_info);
}
if (error != NO_ERROR) {
fprintf(stderr, "ERROR: Failure processing PNG image %s\n",
file->getPrintableSource().string());
}
return error;
}
static status_t makeFileResources(Bundle* bundle, const sp<AaptAssets>& assets,
ResourceTable* table,
const sp<ResourceTypeSet>& set,
const char* resType)
{
String8 type8(resType);
String16 type16(resType);
bool hasErrors = false;
ResourceDirIterator it(set, String8(resType));
ssize_t res;
/* 下列for迴圈逐個取出set中儲存的檔案進行處理 */
while ((res=it.next()) == NO_ERROR) {
if (bundle->getVerbose()) {
printf(" (new resource id %s from %s)\n",
it.getBaseName().string(), it.getFile()->getPrintableSource().string());
}
String16 baseName(it.getBaseName());
const char16_t* str = baseName.string();
const char16_t* const end = str + baseName.size();
/* 判斷命名是否規範 */
while (str < end) {
if (!((*str >= 'a' && *str <= 'z')
|| (*str >= '0' && *str <= '9')
|| *str == '_' || *str == '.')) {
fprintf(stderr, "%s: Invalid file name: must contain only [a-z0-9_.]\n",
it.getPath().string());
hasErrors = true;
}
str++;
}
String8 resPath = it.getPath(); // 獲取資原始檔名稱
resPath.convertToResPath();
/* 將一個資原始檔封裝為一個Entry新增到ResourceTable中去 */
table->addEntry(SourcePos(it.getPath(), 0), String16(assets->getPackage()),
type16,
baseName,
String16(resPath),
NULL,
&it.getParams());
/* 將該資原始檔資訊新增到AaptAsset物件assets中去 */
assets->addResource(it.getLeafName(), resPath, it.getFile(), type8);
}
return hasErrors ? UNKNOWN_ERROR : NO_ERROR;
}
將一個名稱為name,型別為type,所屬包為package的資原始檔組織成一個Entry具體實現如下所示:
路徑:frameworks/base/tools/aapt/ResourceTable.cpp
status_t ResourceTable::addEntry(const SourcePos& sourcePos,
const String16& package,
const String16& type,
const String16& name,
const String16& value,
const Vector<StringPool::entry_style_span>* style,
const ResTable_config* params,
const bool doSetIndex,
const int32_t format,
const bool overwrite)
{
// Check for adding entries in other packages... for now we do
// nothing. We need to do the right thing here to support skinning.
uint32_t rid = mAssets->getIncludedResources()
.identifierForName(name.string(), name.size(),
type.string(), type.size(),
package.string(), package.size());
if (rid != 0) {
return NO_ERROR;
}
/* Entry類用來描述一個資源項,它的重要成員變數的含義如下所示:
--mName:表示資源名稱。
--mItem:表示資源資料,用一個Item物件來描述。
Item類用來描述一個資源項資料,它的重要成員變數的含義如下所示:
--value:表示資源項的原始值,它是一個字串。
--parsedValue:表示資源項原始值經過解析後得到的結構化的資源值,使用一個Res_Value物件來描述。例如,一個整數型別的資源項的原始值為“12345”,經過解析後,就得到一個大小為12345的整數型別的資源項。*/
/* 首先呼叫getEntry函式獲取一個描述名稱為name的資原始檔的Entry類物件
** 其所在包為package, 其型別使用type描述 */
sp<Entry> e = getEntry(package, type, name, sourcePos, overwrite,
params, doSetIndex);
if (e == NULL) {
return UNKNOWN_ERROR;
}
status_t err = e->setItem(sourcePos, value, style, format, overwrite);
if (err == NO_ERROR) {
mNumLocal++;
}
return err;
}
sp<ResourceTable::Entry> ResourceTable::getEntry(const String16& package,
const String16& type,
const String16& name,
const SourcePos& sourcePos,
bool overlay,
const ResTable_config* config,
bool doSetIndex)
{
/* Type類用來描述一個資源型別,它的重要成員變數的含義如下所示:
--mName:表示資源型別名稱。
--mConfigs:表示包含的資源配置項列表,每一個配置項列表都包含了一系列同名的資源,使用一個ConfigList來描述。例如,假設有main.xml和sub.xml兩個layout型別的資源,那麼main.xml和sub.xml都分別對應有一個ConfigList。
--mOrderedConfigs:和mConfigs一樣,也是表示包含的資源配置項,不過它們是以Entry ID從小到大的順序儲存在一個Vector裡面的,而mConfigs是以Entry Name來Key的DefaultKeyedVector。
--mUniqueConfigs:表示包含的不同資源配置資訊的個數。我們可以將mConfigs和mOrderedConfigs看作是按照名稱的不同來劃分資源項,而將mUniqueConfigs看作是按照配置資訊的不同來劃分資源項。
*/
/* ConfigList用來描述一個資源配置項列表,它的重要成員變數的含義如下所示:
--mName:表示資源項名稱,也稱為Entry Name。
--mEntries: 表示包含的資源項,每一個資源項都用一個Entry物件來描述,並且以一個對應的ConfigDescription為Key儲存在一個 DefaultKeyedVector中。例如,假設有一個名稱為icon.png的drawable資源,有三種不同的配置,分別是ldpi、mdpi 和hdpi,那麼以icon.png為名稱的資源就對應有三個項。
*/
/* 呼叫getType函式來獲取一個Type物件t用來描述資源型別 */
sp<Type> t = getType(package, type, sourcePos, doSetIndex);
if (t == NULL) {
return NULL;
}
/* 從獲取對應的Type物件中取出名稱為name的Entry的物件用來描述名稱為
** name的資原始檔 */
return t->getEntry(
name, sourcePos, config, doSetIndex, overlay, mBundle->getAutoAddOverlay());
}
sp<ResourceTable::Type> ResourceTable::getType(const String16& package,
const String16& type,
const SourcePos& sourcePos,
bool doSetIndex)
{
/* Package類用來描述一個包,這個包可以是一個被引用的包,即一個預先編譯好的包,也可以是一個正在編譯的包,它的重要成員變數的含義如下所示:
--mName:表示包的名稱。
--mTypes:表示包含的資源的型別,每一個型別都用一個Type物件來描述。資源的型別就是指animimator、anim、color、drawable、layout、menu和values等。
--mOrderedTypes:和mTypes一樣,也是表示包含的資源的型別,不過它們是Type ID從小到大的順序儲存在一個Vector裡面的,而mTypes是一個以Type Name為Key的DefaultKeyedVector。
*/
/* 獲取一個Package物件用來描述當前正在編譯的應用程式包 */
sp<Package> p = getPackage(package);
if (p == NULL) {
return NULL;
}
/* 從獲取的Package中獲取其成員變數mTypes中名稱為type的一個Type物件
** 如果名稱為type的Type物件不存在則新建一個並新增到mTypes成員變數中
*/
return p->getType(type, sourcePos, doSetIndex);
}
sp<ResourceTable::Package> ResourceTable::getPackage(const String16& package)
{
/* 通過包名判斷是否已經為當前正在編譯的應用程式建立過一個與其
** 名稱相對應的包名,如果建立過則直接從mPackages中取出,若
** 沒有則根據不同情況建立一個新的包並新增到mPackages中去 */
sp<Package> p = mPackages.valueFor(package);
if (p == NULL) {
if (mBundle->getIsOverlayPackage()) {
p = new Package(package, 0x00);
} else if (mIsAppPackage) {
if (mHaveAppPackage) {
return NULL;
}
mHaveAppPackage = true;
p = new Package(package, 127);
} else {
p = new Package(package, mNextPackageId);
}
mPackages.add(package, p);
mOrderedPackages.add(p);
mNextPackageId++;
}
return p;
}
上述獲取了一個描述名稱為name資原始檔的Entry物件之後,把其相關資訊組織成一個Item物件然後新增到Entry中,其具體實現如下所示:
status_t ResourceTable::Entry::setItem(const SourcePos& sourcePos,
const String16& value,
const Vector<StringPool::entry_style_span>* style,
int32_t format,
const bool overwrite)
{
Item item(sourcePos, false, value, style); // 新建一個Item物件
if (mType == TYPE_BAG) {
const Item& item(mBag.valueAt(0));
sourcePos.error("Resource entry %s is already defined as a bag.\n"
"%s:%d: Originally defined here.\n",
String8(mName).string(),
item.sourcePos.file.string(), item.sourcePos.line);
return UNKNOWN_ERROR;
}
if ( (mType != TYPE_UNKNOWN) && (overwrite == false) ) {
sourcePos.error("Resource entry %s is already defined.\n"
"%s:%d: Originally defined here.\n",
String8(mName).string(),
mItem.sourcePos.file.string(), mItem.sourcePos.line);
return UNKNOWN_ERROR;
}
mType = TYPE_ITEM; // 指定型別
mItem = item;
mItemFormat = format;
return NO_ERROR;
}
至此,我們分析如何將收集到一個AaptAsset中的資原始檔資訊分類重新由函式makeFileResources組織到一個ResourceTable物件中去,這些資原始檔的資訊最終組織在Package, Type, Entry, Item中,Package代表當前編譯APK的包資訊,Type類儲存資源型別資訊, Entry代表儲存資原始檔,Item儲存檔案中屬性資訊. Package包含Type, Type包含Entry, Entry包含Item.
baseName=ic_launcher
before convert resPath=res/drawable-hdpi/ic_launcher.png
Adding entry left: file=res/drawable-hdpi/ic_launcher.png, line=0, type=drawable, value=res/drawable-hdpi/ic_launcher.png
baseName=ic_launcher
before convert resPath=res/drawable-mdpi/ic_launcher.png
Adding entry left: file=res/drawable-mdpi/ic_launcher.png, line=0, type=drawable, value=res/drawable-mdpi/ic_launcher.png
baseName=ic_launcher
before convert resPath=res/drawable-xhdpi/ic_launcher.png
Adding entry left: file=res/drawable-xhdpi/ic_launcher.png, line=0, type=drawable,
value=res/drawable-xhdpi/ic_launcher.png
baseName=ic_launcher
before convert resPath=res/drawable-xxhdpi/ic_launcher.png
Adding entry left: file=res/drawable-xxhdpi/ic_launcher.png, line=0, type=drawable, value=res/drawable-xxhdpi/ic_launcher.png
baseName=activity_main
before convert resPath=res/layout/activity_main.xml
Adding entry left: file=res/layout/activity_main.xml, line=0, type=layout,
value=res/layout/activity_main.xml
Adding entry left: file=res/values/dimens.xml, line=4, type=dimen, value=16dp
Adding entry left: file=res/values/dimens.xml, line=5, type=dimen, value=16dp
Adding entry left: file=res/values-sw720dp-land/dimens.xml, line=7, type=dimen,
value=128dp
Adding entry left: file=res/values/strings.xml, line=4, type=string, value=HelloWorldActivity
Adding entry left: file=res/values/strings.xml, line=5, type=string, value=Settings
Adding entry left: file=res/values/strings.xml, line=6, type=string, value=Show
Adding entry left: file=res/values/strings.xml, line=7, type=string, value=Hello world!
Adding entry left: file=res/layout/activity_main.xml, line=7, type=id, value=false
Adding entry left: file=res/layout/activity_main.xml, line=13, type=id, value=false
對於drawable, mipmap, layout, anim, animator, iterpolator, xml, raw, color, menu中的資原始檔,我們由函式makeFileResources就能將其組織成Package, Type, Entry, Item這樣的資料結構形式最終儲存到一個ResourceTable中去,而對於values中的資原始檔卻是由一個獨立函式compileResourceFile進行組織的,該函式所要實現的功能如下所示:
原型:
status_t compileResourceFile(Bundle* bundle,
const sp<AaptAssets>& assets,
const sp<AaptFile>& in,
const ResTable_config& defParams,
const bool overwrite,
ResourceTable* outTable)
A. 呼叫函式parseXMLResource解析values目錄下的每一個資原始檔in,將解析的資訊首先儲存到一個型別為ResXMLTree的資料結構block中.
B. 按照如下類別對block中的每一個資原始檔資訊詳細解析:
// Top-level tag.
const String16 resources16("resources");
// Identifier declaration tags.
const String16 declare_styleable16("declare-styleable");
const String16 attr16("attr");
// Data creation organizational tags.
const String16 string16("string");
const String16 drawable16("drawable");
const String16 color16("color");
const String16 bool16("bool");
const String16 integer16("integer");
const String16 dimen16("dimen");
const String16 fraction16("fraction");
const String16 style16("style");
const String16 plurals16("plurals");
const String16 array16("array");
const String16 string_array16("string-array");
const String16 integer_array16("integer-array");
const String16 public16("public");
const String16 public_padding16("public-padding");
const String16 private_symbols16("private-symbols");
const String16 java_symbol16("java-symbol");
const String16 add_resource16("add-resource");
const String16 skip16("skip");
const String16 eat_comment16("eat-comment");
// Data creation tags.
const String16 bag16("bag");
const String16 item16("item");
// Attribute type constants.
const String16 enum16("enum");
// plural values
const String16 other16("other");
const String16 quantityOther16("^other");
const String16 zero16("zero");
const String16 quantityZero16("^zero");
const String16 one16("one");
const String16 quantityOne16("^one");
const String16 two16("two");
const String16 quantityTwo16("^two");
const String16 few16("few");
const String16 quantityFew16("^few");
const String16 many16("many");
const String16 quantityMany16("^many");
// useful attribute names and special values
const String16 name16("name");
const String16 translatable16("translatable");
const String16 formatted16("formatted");
const String16 false16("false");
將解析完的資訊儲存在一個臨時變數中通過呼叫outTable對應的型別資訊的成員函式新增到ResourceTable物件outTable中去,至此我們就就將values資料夾下資原始檔資訊收集到了一個ResourceTable中去了.
綜述,上面的工作我們將當前正在編譯的應用程式所依賴的所有資原始檔資訊(包括系統android.jar中的和應用程式自身的被收集到一個AaptAsset類物件中的)都收集到了一個ResourceTable物件中去了,接下來buildResources函式的工作是為這些資原始檔中的各種屬性分配資源ID,具體實現如下所示:
3. 為資原始檔中Bag資源分配資源ID
在繼續編譯其它非values的資源之前,我們需要給之前收集到的Bag資源分配資源ID,因為它們可能會被其它非values類資源引用到
路徑:frameworks/base/tools/aapt/Resource.cpp
status_t buildResources(Bundle* bundle, const sp<AaptAssets>& assets)
{
......
// -----------------------------------------------------------
// Assignment of resource IDs and initial generation of resource table.
// -----------------------------------------------------------
// 分配資源ID和初始化生成資源表
if (table.hasResources()) {
// 首先建立一個名為resources.arsc的資源表檔案
sp<AaptFile> resFile(getResourceFile(assets));
if (resFile == NULL) {
fprintf(stderr, "Error: unable to generate entry for resource data\n");
return UNKNOWN_ERROR;
}
/* 呼叫ResourceTable類的成員函式assignResourceIds分配資源ID資訊 */
err = table.assignResourceIds();
if (err < NO_ERROR) {
return err;
}
}
......
}
ResourceTable分配資源ID的成員函式assignResourceIds的具體實現如下所示:
路徑:frameworks/base/tools/aapt/ResourceTable.cpp
status_t ResourceTable::assignResourceIds()
{
const size_t N = mOrderedPackages.size();
size_t pi;
status_t firstError = NO_ERROR;
// First generate all bag attributes and assign indices.
// 首先取出當前編譯應用程式資源所依賴的的包個數,並分別為包中的資源分配資源
// ID, 在這裡這兩個包分別是: android.jar 和 com.example.helloworldactivity.
for (pi=0; pi<N; pi++) {
sp<Package> p = mOrderedPackages.itemAt(pi);
if (p == NULL || p->getTypes().size() == 0) {
// Empty, skip!
continue;
}
/* 如果為Package物件p中的Type設定了public屬性id,那麼呼叫
** applyPublicTypeOrder函式將p中成員變數mOrderedTypes中的Type按照id
** 由小到大的順序排列
**
** 例如, 我們在values/public.xml中如下定義:
** <?xml version="1.0" encoding="utf-8"?>
** <resources>
<public type="string" name="show" id="0x7f030001" />
<public type="style" name="AppTheme" id="0x7f040001" />
** </resources>
** 那麼type為string和style的在mOrderedTypes中的位置是在2,3
** 位置處,就是將3和4進行減1操作而,第0,1兩個位置保留.
*/
status_t err = p->applyPublicTypeOrder();
if (err != NO_ERROR && firstError == NO_ERROR) {
firstError = err;
}
// Generate attributes...
/* 按照Type-->ConfigList-->Entry的順序依次將所有的Entry呼叫函式
** generateAttributes生成一個屬性資訊 */
const size_t N = p->getOrderedTypes().size();
size_t ti;
for (ti=0; ti<N; ti++) {
sp<Type> t = p->getOrderedTypes().itemAt(ti);
if (t == NULL) {
continue;
}
const size_t N = t->getOrderedConfigs().size();
for (size_t ci=0; ci<N; ci++) {
sp<ConfigList> c = t->getOrderedConfigs().itemAt(ci);
if (c == NULL) {
continue;
}
const size_t N = c->getEntries().size();
for (size_t ei=0; ei<N; ei++) {
sp<Entry> e = c->getEntries().valueAt(ei);
if (e == NULL) {
continue;
}
/* generateAttributes函式用於將儲存到mBag中的資訊取出,如果
** 其是一個id屬性,並且在table中沒有對應的bag或者entry則
** 建立一個entry新增進table中 */
status_t err = e->generateAttributes(this, p->getName());
if (err != NO_ERROR && firstError == NO_ERROR) {
firstError = err;
}
}
}
}
const SourcePos unknown(String8("????"), 0);
sp<Type> attr = p->getType(String16("attr"), unknown);
// Assign indices...
for (ti=0; ti<N; ti++) {
sp<Type> t = p->getOrderedTypes().itemAt(ti);
if (t == NULL) {
continue;
}
// 類似的,我們如果為某類Type物件指定了public的IDS資訊,我們就同上
// 將Type中的ConfigList物件按照id值從小到大排列在mOrderedConfigs
// 中去
err = t->applyPublicEntryOrder();
if (err != NO_ERROR && firstError == NO_ERROR) {
firstError = err;
}
const size_t N = t->getOrderedConfigs().size();
t->setIndex(ti+1); // 為每一種Type設定索引值
/* 為當前Type中的ConfigList設定索引值 */
for (size_t ei=0; ei<N; ei++) {
sp<ConfigList> c = t->getOrderedConfigs().itemAt(ei);
if (c == NULL) {
continue;
}
c->setEntryIndex(ei);
}
}
// Assign resource IDs to keys in bags...
// 按照Package-->ConfigList-->Entry處理順序
// 逐個取出每一個資源屬性呼叫Entry的assignResourceIds為其分配屬性ID
for (ti=0; ti<N; ti++) {
sp<Type> t = p->getOrderedTypes().itemAt(ti);
if (t == NULL) {
continue;
}
const size_t N = t->getOrderedConfigs().size();
for (size_t ci=0; ci<N; ci++) {
sp<ConfigList> c = t->getOrderedConfigs().itemAt(ci);
//printf("Ordered config #%d: %p\n", ci, c.get());
const size_t N = c->getEntries().size();
for (size_t ei=0; ei<N; ei++) {
sp<Entry> e = c->getEntries().valueAt(ei);
if (e == NULL) {
continue;
}
status_t err = e->assignResourceIds(this, p->getName());
if (err != NO_ERROR && firstError == NO_ERROR) {
firstError = err;
}
}
}
}
}
return firstError;
}
status_t ResourceTable::Entry::assignResourceIds(ResourceTable* table,
const String16& package)
{
bool hasErrors = false;
/* Type為values的資源除了是string之外,還有其它很多型別的資源,其中
** 有一些比較特殊,如bag、style、plurals和 array類的資源。這些資源會給
** 自己定義一些專用的值,這些帶有專用值的資源就統稱為Bag資源。
** 例如,Android系統提供的 android:orientation屬性的取值範圍
** 為{“vertical”、“horizontal”},就相當於是定義了vertical和 horizontal
** 兩個Bag。在繼續編譯其它非values的資源之前,我們需要給之前收集到的Bag
** 資源分配資源ID
*/
if (mType == TYPE_BAG) {
const char* errorMsg;
const String16 style16("style");
const String16 attr16("attr");
const String16 id16("id");
mParentId = 0;
/* 在當前正在編譯的應用程式中, Bag型別的資源在values/styles.xml:
** AppBaseTheme 其 mParent 分別為: android:Theme.Light,
** android:Theme.Holo.Light, android:Theme.Holo.Light.DarkActionBar
** AppTheme其mParent為AppBaseTheme,在這裡只為父類Bag資源分配
** 資源ID */
if (mParent.size() > 0) {
mParentId = table->getResId(mParent, &style16, NULL, &errorMsg);
if (mParentId == 0) {
mPos.error("Error retrieving parent for item: %s '%s'.\n",
errorMsg, String8(mParent).string());
hasErrors = true;
}
}
const size_t N = mBag.size();
for (size_t i=0; i<N; i++) {
const String16& key = mBag.keyAt(i);
Item& it = mBag.editValueAt(i);
it.bagKeyId = table->getResId(key,
it.isId ? &id16 : &attr16, NULL, &errorMsg);
if (it.bagKeyId == 0) {
it.sourcePos.error("Error: %s: %s '%s'.\n", errorMsg,
String8(it.isId ? id16 : attr16).string(),
String8(key).string());
hasErrors = true;
}
}
}
return hasErrors ? UNKNOWN_ERROR : NO_ERROR;
}
uint32_t ResourceTable::getResId(const String16& package,
const String16& type,
const String16& name,
bool onlyPublic) const
{
sp<Package> p = mPackages.valueFor(package);
if (p == NULL) return 0;
........
sp<Type> t = p->getTypes().valueFor(type);
if (t == NULL) return 0;
sp<ConfigList> c = t->getConfigs().valueFor(name);
if (c == NULL) return 0;
int32_t ei = c->getEntryIndex();
if (ei < 0) return 0;
/* 最終通過getResId分配到一個資源ID */
return getResId(p, t, ei);
}
inline uint32_t ResourceTable::getResId(const sp<Package>& p,
const sp<Type>& t,
uint32_t nameId)
{
return makeResId(p->getAssignedId(), t->getIndex(), nameId);
}
/* 建立資源ID函式
** Package ID相當於是一個名稱空間,限定資源的來源。Android系統當前定義了兩個資源命令空間,其中一個系統資源命令空間,它的Package ID等於0x01,另外一個是應用程式資源命令空間,它的Package ID等於0x7f。所有位於[0x01, 0x7f]之間的Package ID都是合法的,而在這個範圍之外的都是非法的Package ID。前面提到的系統資源包package-export.apk的Package ID就等於0x01,而我們在應用程式中定義的資源的Package ID的值都等於0x7f,這一點可以通過生成的R.java檔案來驗證。
Type ID是指資源的型別ID。資源的型別有animator、anim、color、drawable、layout、menu、raw、string和xml等等若干種,每一種都會被賦予一個ID。
Entry ID是指每一個資源在其所屬的資源型別中所出現的次序。注意,不同型別的資源的Entry ID有可能是相同的,但是由於它們的型別不同,我們仍然可以通過其資源ID來區別開來。
*/
static inline uint32_t makeResId(uint32_t packageId,
uint32_t typeId,
uint32_t nameId)
{
/* 使用Package ID | type ID | name ID就為名為name的
** 資源屬性建立了一個資源ID號 */
return nameId | (typeId<<16) | (packageId<<24);
}
4. 編譯XML檔案
在前面的所有工作我們都是為編譯一個XML檔案做準備的,接下來我們將要在buildResources函式中呼叫compileXmlFile編譯一個xml檔案.具體實現如下所示:
路徑:frameworks/base/tools/aapt/Resource.cpp
status_t buildResources(Bundle* bundle, const sp<AaptAssets>& assets)
{
......
// ------------------------------------------------------
// Finally, we can now we can compile XML files, which may reference
// resources.
// ------------------------------------------------------
// 最後我們將要編譯XML檔案,這樣我們就能引用資源
if (layouts != NULL) {
ResourceDirIterator it(layouts, String8("layout"));
while ((err=it.next()) == NO_ERROR) {
String8 src = it.getFile()->getPrintableSource();
err = compileXmlFile(assets, it.getFile(), &table, xmlFlags);
if (err == NO_ERROR) {
ResXMLTree block;
/* 將編譯後的資訊組織到ResXMLTree中去 */
block.setTo(it.getFile()->getData(), it.getFile()->getSize(), true);
/* 檢驗分配的ID是否正確 */
checkForIds(src, block);
} else {
hasErrors = true;
}
}
if (err < NO_ERROR) {
hasErrors = true;
}
err = NO_ERROR;
}
// 對於anim, animator, interpolator, xml, color, menu, drawable中的xml檔案
// 都是通過compileXmlFile函式進行編譯的.
......
/* 取出AndroidManifest.xml檔案 */
const sp<AaptFile> manifestFile(androidManifestFile->getFiles().valueAt(0));
String8 manifestPath(manifestFile->getPrintableSource());
// Generate final compiled manifest file.
// 清空manifestFile所指向的AndroidManfiest.xml的資訊,然後重新解析
manifestFile->clearData();
sp<XMLNode> manifestTree = XMLNode::parse(manifestFile);
if (manifestTree == NULL) {
return UNKNOWN_ERROR;
}
// 檢測是否AndroidManifest.xml中是否有overlay資源,如果有就將現有資源替換
err = massageManifest(bundle, manifestTree);
if (err < NO_ERROR) {
return err;
}
// 編譯AndroidManifest.xml檔案
err = compileXmlFile(assets, manifestTree, manifestFile, &table);
if (err < NO_ERROR) {
return err;
}
........
}
路徑:frameworks/base/tools/aapt/ResourceTable.cpp
status_t compileXmlFile(const sp<AaptAssets>& assets,
const sp<AaptFile>& target,
ResourceTable* table,
int options)
{
/* 首先呼叫XMLNode的成員函式解析target指定的xml檔案收集其屬性資訊
** 到root所指向的資料結構中 */
sp<XMLNode> root = XMLNode::parse(target);
if (root == NULL) {
return UNKNOWN_ERROR;
}
/* 呼叫過載的compileXmlFile函式編譯XML檔案 */
return compileXmlFile(assets, root, target, table, options);
}
status_t compileXmlFile(const sp<AaptAssets>& assets,
const sp<XMLNode>& root,
const sp<AaptFile>& target,
ResourceTable* table,
int options)
{
/* 首先去除空格 */
if ((options&XML_COMPILE_STRIP_WHITESPACE) != 0) {
root->removeWhitespace(true, NULL);
} else if ((options&XML_COMPILE_COMPACT_WHITESPACE) != 0) {
root->removeWhitespace(false, NULL);
}
/* 設定編碼格式 */
if ((options&XML_COMPILE_UTF8) != 0) {
root->setUTF8(true);
}
bool hasErrors = false;
/* 如果尚未對解析到root資料結構中的屬性分配資源ID則呼叫
** root的成員函式分配資源id, 給屬性分配資源ID原理類似於上
** 述給Bag資源分配ID */
if ((options&XML_COMPILE_ASSIGN_ATTRIBUTE_IDS) != 0) {
status_t err = root->assignResourceIds(assets, table);
if (err != NO_ERROR) {
hasErrors = true;
}
}
/* parseValues函式用於獲取當前資源屬性所在的行號等資訊將其儲存到table
** 中,並將字串資源資訊替換成對應的型別值 */
status_t err = root->parseValues(assets, table);
if (err != NO_ERROR) {
hasErrors = true;
}
if (hasErrors) {
return UNKNOWN_ERROR;
}
/* 壓平XML檔案, 組織格式我們已經在上面詳細分析過 */
err = root->flatten(target,
(options&XML_COMPILE_STRIP_COMMENTS) != 0,
(options&XML_COMPILE_STRIP_RAW_VALUES) != 0);
if (err != NO_ERROR) {
return err;
}
target->setCompressionMethod(ZipEntry::kCompressDeflated);
return err;
}
5. 生成資源符號和生成資源索引表
這裡生成資源符號為後面生成R.java檔案做好準備的。從前面的操作可以知道,所有收集到的資源項都按照型別來儲存在一個資源表中,即儲存在一個 ResourceTable物件。因此,Android資源打包工具aapt只要遍歷每一個Package裡面的每一個Type,然後取出每一個 Entry的名稱,並且根據這個Entry在自己的Type裡面出現的次序來計算得到它的資源ID,那麼就可以生成一個資源符號了,這個資源符號由名稱以 及資源ID所組成。
例如,對於strings.xml檔案中名稱為“show”的Entry來說,它是一個型別為string的資源項,假設它出現 的次序為第3,那麼它的資源符號就等於R.string.show,對應的資源ID就為0x7f050002,其中,高位元組 0x7f表示Package ID,次高位元組0x05表示string的Type ID,而低兩位元組0x02就表示“show”是第三個出現的字串。
經過前面的工作我們就獲得了所有資源的資訊並儲存到了ResourceTable物件table中去,接著就要呼叫ResourceTable的成員函式flatten來生成資源索引表resources.arsc
路徑:frameworks/base/tools/aapt/Resource.cpp
status_t buildResources(Bundle* bundle, const sp<AaptAssets>& assets)
{
......
// --------------------------------------------------------
// Generate the final resource table.
// Re-flatten because we may have added new resource IDs
// --------------------------------------------------------
ResTable finalResTable;
sp<AaptFile> resFile;
if (table.hasResources()) {
/* 生成資源符號表 */
sp<AaptSymbols> symbols = assets->getSymbolsFor(String8("R"));
err = table.addSymbols(symbols);
if (err < NO_ERROR) {
return err;
}
/* 生成資源索引表 */
resFile = getResourceFile(assets);
if (resFile == NULL) {
fprintf(stderr, "Error: unable to generate entry for resource data\n");
return UNKNOWN_ERROR;
}
err = table.flatten(bundle, resFile);
if (err < NO_ERROR) {
return err;
}
if (bundle->getPublicOutputFile()) {
FILE* fp = fopen(bundle->getPublicOutputFile(), "w+");
if (fp == NULL) {
return UNKNOWN_ERROR;
}
if (bundle->getVerbose()) {
}
table.writePublicDefinitions(String16(assets->getPackage()), fp);
fclose(fp);
}
// Read resources back in,
finalResTable.add(resFile->getData(), resFile->getSize(), NULL);
......
}
}
ResourceTable的flatten函式用於生成資源索引表resources.arsc,其具體實現如下所示,我們一一解析這個函式的具體實現:
1). 收集型別字串,資源項名稱字串和資源項值字串
注意,這些字串不是按Package來收集的,也就是說,當前所有參與編譯的Package的資源項值字串都會被統一收集在一起。
路徑:frameworks/base/tools/aapt/Resource.cpp
status_t ResourceTable::flatten(Bundle* bundle, const sp<AaptFile>& dest)
{
......
const size_t N = mOrderedPackages.size();
size_t pi;
const static String16 mipmap16("mipmap");
bool useUTF8 = !bundle->getUTF16StringsOption();
// Iterate through all data, collecting all values (strings,
// references, etc).
/* 建立一個字串資源池用於儲存資源項值字串 */
StringPool valueStrings(useUTF8);
Vector<sp<Entry> > allEntries;
for (pi=0; pi<N; pi++) {
sp<Package> p = mOrderedPackages.itemAt(pi);
if (p->getTypes().size() == 0) {
// Empty, skip!
continue;
}
/* 建立一個字串資源池用於儲存資源型別字串 */
StringPool typeStrings(useUTF8);
/* 建立一個字串資源池用於儲存資源項名稱字串 */
StringPool keyStrings(useUTF8);
const size_t N = p->getOrderedTypes().size();
for (size_t ti=0; ti<N; ti++) {
sp<Type> t = p->getOrderedTypes().itemAt(ti);
if (t == NULL) {
typeStrings.add(String16("<empty>"), false);
continue;
}
const String16 typeName(t->getName());
typeStrings.add(typeName, false); // 收集資源型別
// This is a hack to tweak the sorting order of the final strings,
// to put stuff that is generally not language-specific first.
String8 configTypeName(typeName);
if (configTypeName == "drawable" || configTypeName == "layout"
|| configTypeName == "color" || configTypeName == "anim"
|| configTypeName == "interpolator"
|| configTypeName == "animator"
|| configTypeName == "xml" || configTypeName == "menu"
|| configTypeName == "mipmap" || configTypeName == "raw") {
configTypeName = "1complex";
} else {
configTypeName = "2value";
}
const bool filterable = (typeName != mipmap16);
const size_t N = t->getOrderedConfigs().size();
for (size_t ci=0; ci<N; ci++) {
sp<ConfigList> c = t->getOrderedConfigs().itemAt(ci);
if (c == NULL) {
continue;
}
const size_t N = c->getEntries().size();
for (size_t ei=0; ei<N; ei++) {
ConfigDescription config = c->getEntries().keyAt(ei);
if (filterable && !filter.match(config)) {
continue;
}
sp<Entry> e = c->getEntries().valueAt(ei);
if (e == NULL) {
continue;
}
/* 收集資源項名稱字串 */
e->setNameIndex(keyStrings.add(e->getName(), true));
// If this entry has no values for other configs,
// and is the default config, then it is special. Otherwise
// we want to add it with the config info.
ConfigDescription* valueConfig = NULL;
if (N != 1 || config == nullConfig) {
valueConfig = &config;
}
/* 收集資源項值,並將字串型別的值轉換成特定的型別 */
status_t err = e->prepareFlatten(&valueStrings, this,
&configTypeName, &config);
if (err != NO_ERROR) {
return err;
}
allEntries.add(e);
}
}
}
/* 將上述收集到的資訊新增到Package中去 */
p->setTypeStrings(typeStrings.createStringBlock());
p->setKeyStrings(keyStrings.createStringBlock());
}
......
}
2). 生成Package資料塊
參與編譯的每一個Package的資源項元資訊都寫在一塊獨立的資料上,這個資料塊使用一個型別為ResTable_package的頭部來描述。
路徑:frameworks/base/tools/aapt/Resource.cpp
status_t ResourceTable::flatten(Bundle* bundle, const sp<AaptFile>& dest)
{
......
// Now build the array of package chunks.
Vector<sp<AaptFile> > flatPackages;
for (pi=0; pi<N; pi++) {
/* 取出一個Package */
sp<Package> p = mOrderedPackages.itemAt(pi);
if (p->getTypes().size() == 0) {
// Empty, skip!
continue;
}
/* 獲取型別資源字串的個數 */
const size_t N = p->getTypeStrings().size();
const size_t baseSize = sizeof(ResTable_package); // 計算頭部大小
// Start the package data.
// 建立一個data的匿名AaptFile類來分配儲存資料的空間
sp<AaptFile> data = new AaptFile(String8(), AaptGroupEntry(), String8());
/* 分配一個ResTable_package的頭部大小資料空間 */
ResTable_package* header = (ResTable_package*)data->editData(baseSize);
if (header == NULL) {
fprintf(stderr, "ERROR: out of memory creating ResTable_package\n");
return NO_MEMORY;
}
/* 初始化這個頭部 */
memset(header, 0, sizeof(*header));
header->header.type = htods(RES_TABLE_PACKAGE_TYPE);
header->header.headerSize = htods(sizeof(*header));
header->id = htodl(p->getAssignedId());
strcpy16_htod(header->name, p->getName().string());
// Write the string blocks.
// 寫入字串型別字串資源池中的資料
const size_t typeStringsStart = data->getSize();
sp<AaptFile> strFile = p->getTypeStringsData();
ssize_t amt = data->writeData(strFile->getData(), strFile->getSize());
strAmt += amt;
if (amt < 0) {
return amt;
}
// 寫入字串名稱字串資源池中的資料
const size_t keyStringsStart = data->getSize();
strFile = p->getKeyStringsData();
amt = data->writeData(strFile->getData(), strFile->getSize());
strAmt += amt;
if (amt < 0) {
return amt;
}
// Build the type chunks inside of this package.
// 在Package內部構建一個typeSpec塊
for (size_t ti=0; ti<N; ti++) {
// Retrieve them in the same order as the type string block.
size_t len;
// 獲取型別名稱
String16 typeName(p->getTypeStrings().stringAt(ti, &len));
sp<Type> t = p->getTypes().valueFor(typeName);
const bool filterable = (typeName != mipmap16);
const size_t N = t != NULL ? t->getOrderedConfigs().size() : 0;
// First write the typeSpec chunk, containing information about
// each resource entry in this type.
// 首先寫入typeSpec塊,其中包含了關於在這個型別中的每個資源entry
{
/* 為這個typeSpec塊分配空間 */
const size_t typeSpecSize =
sizeof(ResTable_typeSpec) + sizeof(uint32_t)*N;
const size_t typeSpecStart = data->getSize();
ResTable_typeSpec* tsHeader = (ResTable_typeSpec*)
(((uint8_t*)data->editData(typeSpecStart+typeSpecSize)) +
typeSpecStart);
if (tsHeader == NULL) {
return NO_MEMORY;
}
/* 在這個typeSpec塊前面建立一個型別規範資料塊ResTable_typeSpec
** 型別規範資料塊用來描述資源項的配置差異性。通過這個差異性描述,
** 我們就可以知道每一個資源項的配置狀況。知道了一個資源項的配置
** 狀況之後,Android資源管理框架在檢測到裝置的配置資訊發生變化
** 之後,就可以知道是否需要重新載入該資源項。型別規範資料塊是按
** 照型別來組織的,也就是說,每一種型別都對應有一個型別規範資料
** 塊。*/
memset(tsHeader, 0, sizeof(*tsHeader));
tsHeader->header.type = htods(RES_TABLE_TYPE_SPEC_TYPE);
tsHeader->header.headerSize = htods(sizeof(*tsHeader));
tsHeader->header.size = htodl(typeSpecSize);
tsHeader->id = ti+1;
tsHeader->entryCount = htodl(N);
/* ResTable_typeSpec後面緊跟著的是一個大小為entryCount的uint32_t陣列,每一個陣列元素,即每一個uint32_t, 都是用來描述一個資源項的配置差異性的。例如,名稱為icon的drawable資源項有三種不同的
螢幕配置ldpi、mdpi和hdpi, 於是用來描述它的配置差異性的uint32_t的第CONFIG_DENSITY位就等於1,而其餘位都等於0。又如,名稱為main_activity的 layout資源項只有一種配置default,於是用來描述它的配置差異性的uint32_t的值就等於0。此外,如果一個資源項是匯出的,即它的資源 ID是通過public.xml來固定的,那麼用來描述它的配置差異性的uint32_t的第 ResTable_typeSpec::SPEC_PUBLIC位也會被設定為1。
*/
uint32_t* typeSpecFlags = (uint32_t*)
(((uint8_t*)data->editData())
+ typeSpecStart + sizeof(ResTable_typeSpec));
memset(typeSpecFlags, 0, sizeof(uint32_t)*N);
for (size_t ei=0; ei<N; ei++) {
sp<ConfigList> cl = t->getOrderedConfigs().itemAt(ei);
/* 對應每一個ConfigList物件的位置處寫入一個
** RestTable_typeSpec::SPEC_PUBLIC標記 */
if (cl->getPublic()) {
typeSpecFlags[ei] |= htodl(ResTable_typeSpec::SPEC_PUBLIC);
}
const size_t CN = cl->getEntries().size();
for (size_t ci=0; ci<CN; ci++) {
if (filterable && !filter.match(cl->getEntries().keyAt(ci))) {
continue;
}
for (size_t cj=ci+1; cj<CN; cj++) {
if (filterable && !filter.match(cl->getEntries().keyAt(cj))) {
continue;
}
typeSpecFlags[ei] |= htodl(
cl->getEntries().keyAt(ci).diff(cl->getEntries().keyAt(cj)));
}
}
}
}
// We need to write one type chunk for each configuration for
// which we have entries in this type.
const size_t NC = t->getUniqueConfigs().size();
const size_t typeSize = sizeof(ResTable_type) + sizeof(uint32_t)*N;
for (size_t ci=0; ci<NC; ci++) {
ConfigDescription config = t->getUniqueConfigs().itemAt(ci);
if (filterable && !filter.match(config)) {
continue;
}
const size_t typeStart = data->getSize();
/* 型別資源項資料塊用來描述資源項的具體資訊,這樣我們就可以知道
** 每一個資源項名稱、值和配置等資訊。型別資源項資料同樣是按照
** 型別和配置來組織的,也就是說,一個具有N個配置的型別一共對應
** 有N個型別資源項資料塊。
** 型別資源項資料塊的頭部是用一個ResTable_type來定義的
*/
ResTable_type* tHeader = (ResTable_type*)
(((uint8_t*)data->editData(typeStart+typeSize)) + typeStart);
if (tHeader == NULL) {
fprintf(stderr, "ERROR: out of memory creating ResTable_type\n");
return NO_MEMORY;
}
memset(tHeader, 0, sizeof(*tHeader));
tHeader->header.type = htods(RES_TABLE_TYPE_TYPE);
tHeader->header.headerSize = htods(sizeof(*tHeader));
tHeader->id = ti+1;
tHeader->entryCount = htodl(N);
tHeader->entriesStart = htodl(typeSize);
tHeader->config = config;
// Build the entries inside of this type.
for (size_t ei=0; ei<N; ei++) {
sp<ConfigList> cl = t->getOrderedConfigs().itemAt(ei);
sp<Entry> e = cl->getEntries().valueFor(config);
// Set the offset for this entry in its type.
/* ResTable_type緊跟著的是一個大小為entryCount的uint32_t
** 陣列,每一個陣列元素,即每一個uint32_t,都是用來描述一個
** 資源項資料塊的偏移位置。緊跟在這個uint32_t陣列後面的是
** 一個大小為entryCount的ResTable_entry陣列,每一個陣列
** 元 素,即每一個ResTable_entry,都是用來描述一個資源項的
** 具體資訊。*/
uint32_t* index = (uint32_t*)
(((uint8_t*)data->editData())
+ typeStart + sizeof(ResTable_type));
if (e != NULL) {
index[ei] = htodl(data->getSize()-typeStart-typeSize);
// Create the entry.
ssize_t amt = e->flatten(bundle, data, cl->getPublic());
if (amt < 0) {
return amt;
}
} else {
index[ei] = htodl(ResTable_type::NO_ENTRY);
}
}
// Fill in the rest of the type information.
tHeader = (ResTable_type*)
(((uint8_t*)data->editData()) + typeStart);
tHeader->header.size = htodl(data->getSize()-typeStart);
}
}
// Fill in the rest of the package information.
// 將描述Package的ResTable_package型別的header各資料項填滿
header = (ResTable_package*)data->editData();
header->header.size = htodl(data->getSize());
header->typeStrings = htodl(typeStringsStart);
header->lastPublicType = htodl(p->getTypeStrings().size());
header->keyStrings = htodl(keyStringsStart);
header->lastPublicKey = htodl(p->getKeyStrings().size());
flatPackages.add(data);
}
// And now write out the final chunks.
const size_t dataStart = dest->getSize();
/* 資源索引表頭部使用一個ResTable_header來表示 */
{
// blah
ResTable_header header;
memset(&header, 0, sizeof(header));
header.header.type = htods(RES_TABLE_TYPE);
header.header.headerSize = htods(sizeof(header));
header.packageCount = htodl(flatPackages.size());
status_t err = dest->writeData(&header, sizeof(header));
if (err != NO_ERROR) {
fprintf(stderr, "ERROR: out of memory creating ResTable_header\n");
return err;
}
}
ssize_t strStart = dest->getSize();
/* 我們已經將所有的資源項的值字串都收集起來了,因此,這裡直接它們寫入到
** 資源索引表去就可以了。注意,這個字串資源池包含了在所有的資源包裡面所
** 定義的資源項的值字串,並且是緊跟在資源索引表頭部的後面。*/
err = valueStrings.writeStringBlock(dest);
if (err != NO_ERROR) {
return err;
}
ssize_t amt = (dest->getSize()-strStart);
strAmt += amt;
/* 我們已經所有的Package資料塊都收集起來了,因此,這裡直接將它們寫入到
** 資源索引表去就可以了。這些Package資料塊是依次寫入到資源索引表去的,
** 並且是緊跟在資源項的值字串資源池的後面。*/
for (pi=0; pi<flatPackages.size(); pi++) {
err = dest->writeData(flatPackages[pi]->getData(),
flatPackages[pi]->getSize());
if (err != NO_ERROR) {
return err;
}
}
ResTable_header* header = (ResTable_header*)
(((uint8_t*)dest->getData()) + dataStart);
header->header.size = htodl(dest->getSize() - dataStart);
return NO_ERROR;
}
/* 組織各資料項的資料 */
ssize_t ResourceTable::Entry::flatten(Bundle* bundle,
const sp<AaptFile>& data, bool isPublic)
{
size_t amt = 0;
ResTable_entry header;
memset(&header, 0, sizeof(header));
header.size = htods(sizeof(header));
const type ty = this != NULL ? mType : TYPE_ITEM;
if (this != NULL) {
if (ty == TYPE_BAG) {
header.flags |= htods(header.FLAG_COMPLEX);
}
if (isPublic) {
header.flags |= htods(header.FLAG_PUBLIC);
}
header.key.index = htodl(mNameIndex);
}
/* 接下來我們就分兩種情況來討論資源項資訊寫入到資源索引表的過程。
首先看一個普通的資源項,即一個非Bag資源項的寫入過程。每一個資源項的
資料都是用一個Item來描述的。在這個Item中,有一個型別為Res_value的
成員變數parsedValue,它表示一個資源項經過解析後得到值。
*/
if (ty != TYPE_BAG) {
/* 寫入一個型別為ResTable_entry的header */
status_t err = data->writeData(&header, sizeof(header));
if (err != NO_ERROR) {
fprintf(stderr, "ERROR: out of memory creating ResTable_entry\n");
return err;
}
const Item& it = mItem;
Res_value par;
memset(&par, 0, sizeof(par));
par.size = htods(it.parsedValue.size);
par.dataType = it.parsedValue.dataType;
par.res0 = it.parsedValue.res0;
par.data = htodl(it.parsedValue.data);
err = data->writeData(&par, it.parsedValue.size);
if (err != NO_ERROR) {
fprintf(stderr, "ERROR: out of memory creating Res_value\n");
return err;
}
amt += it.parsedValue.size;
/* 以下是Bag資源的寫入過程 */
} else {
size_t N = mBag.size();
size_t i;
// Create correct ordering of items.
KeyedVector<uint32_t, const Item*> items;
for (i=0; i<N; i++) {
const Item& it = mBag.valueAt(i);
items.add(it.bagKeyId, &it);
}
N = items.size();
/* 緊跟在ResTable_entry後面的是一個ResTable_map_entry,用來描述後面要
** 寫入到的ResTable_map的資訊。假設一個Bag資源項有N個bag,那麼
** 在ResTable_map_entry就有N個ResTable_map
*/
ResTable_map_entry mapHeader;
memcpy(&mapHeader, &header, sizeof(header));
mapHeader.size = htods(sizeof(mapHeader));
mapHeader.parent.ident = htodl(mParentId);
mapHeader.count = htodl(N);
status_t err = data->writeData(&mapHeader, sizeof(mapHeader));
if (err != NO_ERROR) {
fprintf(stderr, "ERROR: out of memory creating ResTable_entry\n");
return err;
}
for (i=0; i<N; i++) {
const Item& it = *items.valueAt(i);
ResTable_map map;
map.name.ident = htodl(it.bagKeyId);
map.value.size = htods(it.parsedValue.size);
map.value.dataType = it.parsedValue.dataType;
map.value.res0 = it.parsedValue.res0;
map.value.data = htodl(it.parsedValue.data);
err = data->writeData(&map, sizeof(map));
if (err != NO_ERROR) {
fprintf(stderr, "ERROR: out of memory creating Res_value\n");
return err;
}
amt += sizeof(map);
}
}
return amt;
}
6. 再編譯AndroidManifest.xml檔案
完成了上述所有工作後,我們在buildResources函式中將再次編譯AndroidManifest.xml檔案, 應用程式的所有資源項就編譯完成了,這時候就開始將應用程式的配置檔案AndroidManifest.xml也編譯成二進位制格式的Xml檔案。之所以要在應用程式的所有資源項都編譯完成之後,再編譯應用程式的配置檔案,是因為後者可能會引用到前者, 其跟普通xml檔案的編譯過程類似,在此我們就不再贅述。
到這裡,我們就分析完成了呼叫函式buildResources編譯AndroidManifest.xml檔案和res資料夾下面的資原始檔的過程,接下來我們返回到doPackage函式中,分析後續的工作:
三. 將上述編譯完成的資源生成R.java檔案和APK
路徑:frameworks/base/tools/aapt/Command.cpp
/*
* Package up an asset directory and associated application files.
*/
int doPackage(Bundle* bundle)
{
.......
// At this point we've read everything and processed everything. From here
// on out it's just writing output files.
// 我們在此已經完成了所有的準備工作,現在我們要將編譯儲存在快取中的資料
// 輸出到輸出檔案中去
if (SourcePos::hasErrors()) {
goto bail;
}
// Update symbols with information about which ones are needed as Java symbols.
// 更新資源符號
assets->applyJavaSymbols();
if (SourcePos::hasErrors()) {
goto bail;
}
// If we've been asked to generate a dependency file, do that here
// 在這裡生成依賴檔案, 就是在指定的APK名稱後新增.d
if (bundle->getGenDependencies()) {
// If this is the packaging step, generate the dependency file next to
// the output apk (e.g. bin/resources.ap_.d)
if (outputAPKFile) {
dependencyFile = String8(outputAPKFile);
// Add the .d extension to the dependency file.
dependencyFile.append(".d");
} else {
// Else if this is the R.java dependency generation step,
// generate the dependency file in the R.java package subdirectory
// e.g. gen/com/foo/app/R.java.d
dependencyFile = String8(bundle->getRClassDir());
dependencyFile.appendPath("R.java.d");
}
// Make sure we have a clean dependency file to start with
fp = fopen(dependencyFile, "w");
fclose(fp);
}
// Write out R.java constants
// 生成R.java檔案
if (!assets->havePrivateSymbols()) {
if (bundle->getCustomPackage() == NULL) {
// Write the R.java file into the appropriate class directory
// e.g. gen/com/foo/app/R.java
err = writeResourceSymbols(bundle, assets, assets->getPackage(), true);
} else {
const String8 customPkg(bundle->getCustomPackage());
err = writeResourceSymbols(bundle, assets, customPkg, true);
}
if (err < 0) {
goto bail;
}
// If we have library files, we're going to write our R.java file into
// the appropriate class directory for those libraries as well.
// e.g. gen/com/foo/app/lib/R.java
if (bundle->getExtraPackages() != NULL) {
// Split on colon
String8 libs(bundle->getExtraPackages());
char* packageString = strtok(libs.lockBuffer(libs.length()), ":");
while (packageString != NULL) {
// Write the R.java file out with the correct package name
err = writeResourceSymbols(bundle, assets, String8(packageString), true);
if (err < 0) {
goto bail;
}
packageString = strtok(NULL, ":");
}
libs.unlockBuffer();
}
} else {
err = writeResourceSymbols(bundle, assets, assets->getPackage(), false);
if (err < 0) {
goto bail;
}
err = writeResourceSymbols(bundle,
assets, assets->getSymbolsPrivatePackage(), true);
if (err < 0) {
goto bail;
}
}
// Write out the ProGuard file
// 後續工作是打包APK檔案
err = writeProguardFile(bundle, assets);
if (err < 0) {
goto bail;
}
// Write the apk
if (outputAPKFile) {
err = writeAPK(bundle, assets, String8(outputAPKFile));
if (err != NO_ERROR) {
fprintf(stderr, "ERROR: packaging of '%s' failed\n", outputAPKFile);
goto bail;
}
}
// If we've been asked to generate a dependency file, we need to finish up here.
// the writeResourceSymbols and writeAPK functions have already written the target
// half of the dependency file, now we need to write the prerequisites. (files that
// the R.java file or .ap_ file depend on)
if (bundle->getGenDependencies()) {
// Now that writeResourceSymbols or writeAPK has taken care of writing
// the targets to our dependency file, we'll write the prereqs
fp = fopen(dependencyFile, "a+");
fprintf(fp, " : ");
bool includeRaw = (outputAPKFile != NULL);
err = writeDependencyPreReqs(bundle, assets, fp, includeRaw);
// Also manually add the AndroidManifeset since it's not under res/ or assets/
// and therefore was not added to our pathstores during slurping
fprintf(fp, "%s \\\n", bundle->getAndroidManifestFile());
fclose(fp);
}
retVal = 0;
bail:
if (SourcePos::hasErrors()) {
SourcePos::printErrors(stderr);
}
return retVal;
}
/***************END******2013/6/19*************LeeMH****************/
相關文章
- C程式編譯過程淺析C程式編譯
- vlc-android 的編譯過程Android編譯
- Android 專案編譯過程Android編譯
- Android Makefile 編譯過程分析Android編譯
- 淺談交叉編譯編譯
- 編譯器的編譯基本過程編譯
- 編譯過程編譯
- 淺談資料庫中的儲存過程資料庫儲存過程
- 方舟編譯器開源,華為自家開源平臺面世!(附編譯過程)編譯
- 【小生雜談】ASP.Net_Compiler的編譯過程ASP.NETCompile編譯
- 淺談編譯器優化編譯優化
- Javac編譯過程Java編譯
- 編譯核心過程編譯
- 淺談彙編器、編譯器和直譯器編譯
- 編譯器的工作過程編譯
- EVC編譯TCPMP的過程編譯TCP
- 淺談Kotlin語法篇之lambda編譯成位元組碼過程完全解析(七)Kotlin編譯
- 2.深入一點理解C源程式的編譯過程編譯
- 編譯連結過程編譯
- 編譯過程簡介編譯
- C++ 編譯過程C++編譯
- Windows 下 Laravel Mix 資源編譯過程以及產生的錯誤解決WindowsLaravel編譯
- JavaScript的預編譯過程分析JavaScript編譯
- 編譯C++ 程式的過程編譯C++
- 淺談IAT加密原理及過程加密
- 淺談SQL語句的執行過程SQL
- Android的資源管理器的建立過程Android
- 阿里開源COLA 4.0原始碼編譯和部署過程阿里原始碼編譯
- 【Golang】淺談協程併發競爭資源問題Golang
- Android Apk 檔案反編譯和重新打包的過程分析AndroidAPK編譯
- ios底層 編譯過程iOS編譯
- .NET 程式碼編譯過程編譯
- glade 編譯過程 (轉)編譯
- 編譯器的工作過程和原理編譯
- 淺談 iOS 應用啟動過程iOS
- 淺談Web架構之演化過程Web架構
- 淺談移動端 View 的顯示過程View
- GCC編譯過程(預處理->編譯->彙編->連結)GC編譯