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:
xmlns:tools="http://schemas.android.com/tools" android:orientation="vertical" android:layout_width="match_parent" android:layout_height="match_parent" >
res/values/strings.xml:
AndroidManifest.xml:
package="com.example.helloworldactivity" android:versionCode="1" android:versionName="1.0" > android:minSdkVersion="8" android:targetSdkVersion="17" /> android:allowBackup="true" android:icon="@drawable/ic_launcher" android:label="@string/app_name" android:theme="@style/AppTheme" > android:name="com.example.helloworldactivity.MainActivity" android:label="@string/app_name" >
我们前面创建的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
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
// 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
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 size_t dirCount =resDirs.size();
sp
// 获取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
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 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 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 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 const String8& filePath, const AaptGroupEntry& entry, const String8& srcDir, sp const String8& resType) { sp sp sp 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 ** 是否包含了名称为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 // 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 ** 是否包含了名称为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 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 { /* 接着调用父类中的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 { Vector { 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 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 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 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 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 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 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 { // 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 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 const sp { // 以下代码确保只有一个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 // 定义一个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 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: 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 bool stripAll, bool keepComments, const char** cDataTags) { /* 接着调用XMLNode的成员函数parse来解析AndroidManifest.xml文件 */ sp if (root == NULL) { return UNKNOWN_ERROR; } root->removeWhitespace(stripAll, cDataTags); /* 新建一个AaptFile作为输出文件 */ sp /* 调用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 { 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); /* 函数是设置处理一个<>和>之间的字段的回调 ** ** 那么字符串“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 /* 设置标签开始的行号 */ 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 /* 设置标签结束行号 */ 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 bool stripComments, bool stripRawValues) const { /* 创建一个字符串池StringPool变量strings 保存属性名称字符串 */ StringPool strings(mUTF8); Vector // 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 { /* 真正收集XML文件中一个标签的属性名称和其ID号的工作 ** 在函数collect_attr_strings中完成, 这里不工作 */ collect_attr_strings(outPool, outResIds, false); const int NC = mChildren.size(); for (int i=0; i mChildren.itemAt(i)->collect_resid_strings(outPool, outResIds); } return NO_ERROR; } status_t XMLNode::collect_strings(StringPool* dest, Vector 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 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 mChildren.itemAt(i)->collect_strings(dest, outResIds, stripComments, stripRawValues); } return NO_ERROR; } /* 收集XML文件中一个标签中的属性名称及其ID分别保存到字符串资源池和ID池中 */ status_t XMLNode::collect_attr_strings(StringPool* outPool, Vector /* 获取属性个数 */ const int NA = mAttributes.size(); for (int i=0; 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 ssize_t idx = -1; if (indices != NULL) { const int NJ = indices->size(); const size_t NR = outResIds->size(); for (int j=0; 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 // 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 // Optional style span information associated with each index of // mEntryArray. // mEntryStyleArray中保存着mValues中的字符串的样式,其跟 // mValues中的字符串是一一对应的 Vector // 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 下面我们详细介绍是如何为一个StringPool创建一个StringBlock来组织存储在其中的字符串信息的,具体实现如下所示: 路径:frameworks/base/tools/aapt/XMLNode.cpp status_t XMLNode::flatten(const sp bool stripComments, bool stripRawValues) const { StringPool strings(mUTF8); Vector ...... /* 收集到的属性信息都保存在strings所指向的字符串资源池 ** 现在为这些字符串资源池中的属性信息分配字符串块 */ sp ...... } 路径:frameworks/base/tools/aapt/StringPool.cpp sp { /* 首先创建一个匿名AaptFile类对象 */ sp String8()); /* 调用writeStringBlock来创建字符串块 */ status_t err = writeStringBlock(pool); return err == NO_ERROR ? pool : NULL; } status_t StringPool::writeStringBlock(const sp { ...... // 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 entry_style& style = mEntryStyleArray.editItemAt(i); const size_t N = style.spans.size(); for (size_t i=0; 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 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 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 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 entry& ent = mEntries.editItemAt(mEntryArray[i]); *index++ = htodl(ent.offset); } // Write style index array. // 写入字符串样式偏移数组写入 for (i=0; 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 bool stripComments, bool stripRawValues) const { StringPool strings(mUTF8); Vector ...... /* 收集到的属性信息都保存在strings所指向的字符串资源池 ** 现在为这些字符串资源池中的属性信息分配字符串块 */ sp /* 接着创建一个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 *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 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 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 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 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 const sp { // 以下代码确保只有一个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 // 定义一个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 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: 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 { ...... /* 根据包名创建一个对应的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的成员函数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 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 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 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 size_t N=incl.size(); /* 将指定的所有android.jar的路径信息添加到当前对象成员变量 ** mIncludedAssets中, mIncludedAssets的成员变量是一个AssetManager ** 对象 */ for (size_t i=0; 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 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 /* 逐个扫描所包含的android.jar文件路径 */ const size_t N = mAssetPaths.size(); for (size_t i=0; 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 mZipSet.getZipResourceTable(ap.path); } // 在这里返回NULL,表示尚未为该android.jar创建过一个ResTable对象 if (sharedRes == NULL) { /* 返回对应android.jar包的SharedZip中的mResourceTableAsset对象 */ ass = const_cast mZipSet.getZipResourceTableAsset(ap.path); // 在这里返回的ass对象为NULL,表示尚未为该android.jar创建过一个 // Asset对象 if (ass == NULL) { ass = const_cast openNonAssetInPathLocked("resources.arsc", Asset::ACCESS_BUFFER, ap); if (ass != NULL && ass != kExcludedAsset) { /* 到这里我们就为一个android.jar包创建了一个Asset对象 ** 并将其保存到与之对应的SharedZip类对象的 ** mResourceTableAsset中 */ ass = const_cast 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 mZipSet.setZipResourceTable(ap.path, sharedRes); } } } else { // 如果ap指向的是一个目录文件,则执行如下代码 Asset* ass = const_cast 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 { AutoMutex _l(gLock); time_t modWhen = getFileModDate(path); sp 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 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 // 如果尚未为对应的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 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 { // 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 new KeyedVector /* 调用collect_files将前面收集到assets中的各类资源文件重新收集到resources中来 */ collect_files(assets, resources); /* 定义收集各类资源文件的容器 */ sp sp sp sp sp sp sp sp sp sp /* 将保存到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 while(current.get()) { KeyedVector new KeyedVector 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 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 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 KeyedVector { const DefaultKeyedVector int N = groups.size(); // 获取资源文件夹下的资源文件个数,逐个扫描 for (int i=0; i String8 leafName = groups.keyAt(i); const sp const DefaultKeyedVector = 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 set->add(leafName, group); resources->add(resType, set); } else { sp index = set->indexOfKey(leafName); if (index < 0) { set->add(leafName, group); } else { sp for (size_t j=0; j status_t err = existingGroup->addFile(files.valueAt(j)); } } } } } static void collect_files(const sp KeyedVector { const Vector int N = dirs.size(); /* 逐个收集assets资源文件夹下的各类资源文件 */ for (int i=0; i sp 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 const sp { 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 const sp 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 sp volatile bool* mHasErrors; }; /* 调用png图像处理库对png图像进行打包处理 */ status_t preProcessImage(const Bundle* bundle, const sp const sp { 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 ResourceTable* table, const sp 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 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 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 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 if (t == NULL) { return NULL; } /* 从获取对应的Type对象中取出名称为name的Entry的对象用来描述名称为 ** name的资源文件 */ return t->getEntry( name, sourcePos, config, doSetIndex, overlay, mBundle->getAutoAddOverlay()); } sp 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 if (p == NULL) { return NULL; } /* 从获取的Package中获取其成员变量mTypes中名称为type的一个Type对象 ** 如果名称为type的Type对象不存在则新建一个并添加到mTypes成员变量中 */ return p->getType(type, sourcePos, doSetIndex); } sp { /* 通过包名判断是否已经为当前正在编译的应用程序创建过一个与其 ** 名称相对应的包名,如果创建过则直接从mPackages中取出,若 ** 没有则根据不同情况创建一个新的包并添加到mPackages中去 */ sp 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 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 const sp 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 { ...... // ----------------------------------------------------------- // Assignment of resource IDs and initial generation of resource table. // ----------------------------------------------------------- // 分配资源ID和初始化生成资源表 if (table.hasResources()) { // 首先创建一个名为resources.arsc的资源表文件 sp 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 sp if (p == NULL || p->getTypes().size() == 0) { // Empty, skip! continue; } /* 如果为Package对象p中的Type设定了public属性id,那么调用 ** applyPublicTypeOrder函数将p中成员变量mOrderedTypes中的Type按照id ** 由小到大的顺序排列 ** ** 例如, 我们在values/public.xml中如下定义: ** ** ** ** 那么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 sp if (t == NULL) { continue; } const size_t N = t->getOrderedConfigs().size(); for (size_t ci=0; ci sp if (c == NULL) { continue; } const size_t N = c->getEntries().size(); for (size_t ei=0; ei sp 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 // Assign indices... for (ti=0; ti sp 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 sp if (c == NULL) { continue; } c->setEntryIndex(ei); } } // Assign resource IDs to keys in bags... // 按照Package-->ConfigList-->Entry处理顺序 // 逐个取出每一个资源属性调用Entry的assignResourceIds为其分配属性ID for (ti=0; ti sp if (t == NULL) { continue; } const size_t N = t->getOrderedConfigs().size(); for (size_t ci=0; ci sp //printf("Ordered config #%d: %p\n", ci, c.get()); const size_t N = c->getEntries().size(); for (size_t ei=0; ei sp 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 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 if (p == NULL) return 0; ........ sp if (t == NULL) return 0; sp 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 const sp 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 { ...... // ------------------------------------------------------ // 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 String8 manifestPath(manifestFile->getPrintableSource()); // Generate final compiled manifest file. // 清空manifestFile所指向的AndroidManfiest.xml的信息,然后重新解析 manifestFile->clearData(); sp 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 const sp ResourceTable* table, int options) { /* 首先调用XMLNode的成员函数解析target指定的xml文件收集其属性信息 ** 到root所指向的数据结构中 */ sp if (root == NULL) { return UNKNOWN_ERROR; } /* 调用重载的compileXmlFile函数编译XML文件 */ return compileXmlFile(assets, root, target, table, options); } status_t compileXmlFile(const sp const sp const sp 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 { ...... // -------------------------------------------------------- // Generate the final resource table. // Re-flatten because we may have added new resource IDs // -------------------------------------------------------- ResTable finalResTable; sp if (table.hasResources()) { /* 生成资源符号表 */ sp 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 { ...... 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 for (pi=0; pi sp 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 sp if (t == NULL) { typeStrings.add(String16(" 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 sp if (c == NULL) { continue; } const size_t N = c->getEntries().size(); for (size_t ei=0; ei ConfigDescription config = c->getEntries().keyAt(ei); if (filterable && !filter.match(config)) { continue; } sp 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 { ...... // Now build the array of package chunks. Vector for (pi=0; pi /* 取出一个Package */ sp 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 /* 分配一个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 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 // Retrieve them in the same order as the type string block. size_t len; // 获取类型名称 String16 typeName(p->getTypeStrings().stringAt(ti, &len)); sp 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 sp /* 对应每一个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 if (filterable && !filter.match(cl->getEntries().keyAt(ci))) { continue; } for (size_t cj=ci+1; 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 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 sp sp // 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 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 { 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 for (i=0; 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 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****************/