apk资源打包过程分析
来源:互联网 发布:qt tcp 端口监听 编辑:程序博客网 时间:2024/05/21 01:55
本文参考了罗大仙还有一个未知名网友
Android应用资源的分类
- assets
- res
- animator
- anim
- color
- drawable
- layout
- menu
- raw:它们和assets类资源一样,都是原装不动地打包在apk文件中的,不过它们会被赋予资源ID
java
Resources res = getResources();
InputStream is = res .openRawResource(R.raw.filename); - values
- xml
这些文件都会被打包到apk中
中间 02 所在位置值代表资源ID对应的资源的类型,分别是:
- 02:drawable
- 03:layout
- 04:values
- 05:xml
- 06:raw
- 07:color
- 08:menu
如果要访问assets中的文件则需要如下写法:
AssetManager am= getAssets(); InputStream is = assset.open("filename");
上面文件夹中的文件除了raw类型资源以及Bitmap文件的drawable类型资源之外其他都是xml格式文件,打包过程中都会被编译成二进制格式的XML文件这些二进制格式的XML文件分别有一个字符串资源池,用来保存文件中引用到的每一个字符串,包括XML元素标签、属性名称、属性值,以及其它的一切文本值所使用到的字符串。这样原来在文本格式的XML文件中的每一个放置字符串的地方在二进制格式的XML文件中都被替换成一个索引到字符串资源池的整数值。
Android资源打包工具aapt在编译和打包资源的过程中,会执行以下两个额外的操作:
- 1. 赋予每一个非assets资源一个ID值,这些ID值以常量的形式定义在一个R.java文件中。
- 2. 生成一个resources.arsc文件,用来描述那些具有ID值的资源的配置信息,它的内容就相当于是一个资源索引表。
首先Android资源打包工具在编译应用程序资源之前,会创建一个资源表。资源表对应ResourceTable对象描述,当应用程序资源编译完成之后,它就会包含所有资源的信息。Android资源打包工具就可以根据它的内容来生成资源索引表文件resources.arsc。
之后就可以根据资源赋值的内容生成resources.arsc。
理解ResourceTable
ResourceTable用来总体描述一个资源表
- mAssetsPackage : 当前正在编译的资源的包名称
- mPackages : 表示当前正在编译的资源包,每一个包都用一个Package对象来描述。
- mOrderedPackages : 有序包
- mAssets : 表示当前编译的资源目录,它指向的是一个AaptAssets对象
- mName : 表示包的名称
- mTypes : 表示包含的资源的类型,每一个类型都用一个Type对象来描述。资源的类型就是指animimator、anim、color、drawable、layout、menu和values等
- mOrderedTypes : 有序的mTypes
Type类用来描述一个资源类型
- mName : 表示资源类型名称。
- mConfigs : 表示包含的资源配置项列表,每一个配置项列表都包含了一系列同名的资源,使用一个ConfigList来描述。
- mOrderedConfigs : 有序的ConfigList
- mUniqueConfigs : 表示包含的不同资源配置信息的个数
ConfigList用来描述一个资源配置项列表
- mName : 表示资源项名称,也称为Entry Name。
- mEntries : 表示包含的资源项,每一个资源项都用一个Entry对象来描述,并且以一个对应的ConfigDescription为Key保存在一个DefaultKeyedVector中。
Entry类用来描述一个资源项
- mName : 表示资源名称。
- mItem : 表示资源数据,用一个Item对象来描述Item类用来描述一个资源项数据
- value : 表示资源项的原始值,它是一个字符串。
- parsedValue : 表示资源项原始值经过解析后得到的结构化的资源值,使用一个Res_Value对象来描述。
AaptAssets
- mPackage : 表示当前正在编译的资源的包名称
- mRes : 表示所包含的资源类型集,每一个资源类型都使用一个ResourceTypeSet来描述,并且以Type Name为Key保存在一个KeyedVector中。
- mHaveIncludedAssets : 表示是否有引用包。
- mIncludedAssets : 指向的是一个AssetManager,用来解析引用包。引用包都是一些预编译好的资源包,它们需要通过AssetManager来解析。
- mOverlay : 表示当前正在编译的资源的重叠包。ResourceTypeSet描述的是一个类型为AaptGroup的KeyedVector
AaptFile每一个资源文件都是用一个AaptFile对象来描述的
- mPath : 表示资源文件路径
- mGroupEntry : 表示资源文件对应的配置信息,使用一个AaptGroupEntry对象来描述
- mResourceType : 表示资源类型名称。
- mData : 表示资源文件编译后得到的二进制数据
- mDataSize : 表示资源文件编译后得到的二进制数据的大小。
AaptGroupEntry其中成员变量对应十八维度
资源打包过程
举例项目结构如下:
project --AndroidManifest.xml --res --drawable-ldpi --icon.png --drawable-mdpi --icon.png --drawable-hdpi --icon.png --layout --main.xml --sub.xml --values --strings.xml
核心项目是由doPackage此方法开始位置在Main.cpp中
前期的准备工作:
- 检查aapt打包时的参数是否都存在
- 得到最终将资源打包输出到的APK名称
- 检查该文件是否存在不存在则创建,并确定其实常规文件
- 创建一个AaptAssets对象
assets = new AaptAssets();
然后正式进入打包过程
打包过程如下:
收录AndroidManifest.xml文件目录assets和res下的资源目录和资源文件
1. 解析AndroidManifest.xml
解析目的:获得要编译资源的应用程序的包名称,拿到包名就可以创建ResourceTable对象
int doPackage(Bundle* bundle){ sp<AaptAssets> assets; ... assets = new AaptAssets(); ... //将AndroidManifest.xml文件目录assets和res下的资源目录和资源文件收录起来保存到AaptAssets中的成员变量中 err = assets->slurpFromArgs(bundle); ...}
ssize_t AaptAssets::slurpFromArgs(Bundle* bundle){ int count; int totalCount = 0; FileType type; // 获取res目录的路径 const Vector<const char *>& resDirs = bundle->getResourceSourceDirs(); const size_t dirCount =resDirs.size(); sp<AaptAssets> current = this; //获取bundle内所保存的aapt的命令选项个数,即要完成的功能个数 const int N = bundle->getFileSpecCount(); /* * 如果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文件夹,如果指定则解析它 */ const Vector<const char*>& assetDirs = bundle->getAssetSourceDirs();//获取目录名称 const int AN = assetDirs.size(); for (int i = 0; i < AN; i++) { FileType type = getFileType(assetDirs[i]);//获取目录类型 if (type == kFileTypeNonexistent) { fprintf(stderr, "ERROR: asset directory '%s' does not exist\n", assetDirs[i]); return UNKNOWN_ERROR; } if (type != kFileTypeDirectory) { fprintf(stderr, "ERROR: '%s' is not a directory\n", assetDirs[i]); return UNKNOWN_ERROR; } String8 assetRoot(assetDirs[i]); //创建一个名为”assets”的AaptDir对象 sp<AaptDir> assetAaptDir = makeDir(String8(kAssetDir));//assets AaptGroupEntry group; //收录目录“assets”下的资源文件,并返回资源文件个数 count = assetAaptDir->slurpFullTree(bundle, assetRoot, group, String8(), mFullAssetPaths, true); 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", assetDirs[i]); } } /* * If a directory of resource-specific assets was supplied, slurp 'em up. * 收录指定的res资源目录下的资源文件 */ for (size_t i=0; i<dirCount; i++) { const char *res = resDirs[i]; if (res) { type = getFileType(res);//获取文件类型 if (type == kFileTypeNonexistent) { fprintf(stderr, "ERROR: resource directory '%s' does not exist\n", res); return UNKNOWN_ERROR; } if (type == kFileTypeDirectory) { //如果指定了多个res资源目录文件, 则为其创建多个AaptAssets //类来分别收录这些目录中的信息,并将其设置赋值给当前AaptAssets对象的成员变量mOverlay if (i>0) { sp<AaptAssets> nextOverlay = new AaptAssets(); current->setOverlay(nextOverlay); current = nextOverlay; current->setFullResPaths(mFullResPaths); } //调用成员函数slurpResourceTree来收录res目录下的资源文件 count = current->slurpResourceTree(bundle, String8(res)); if (i > 0 && count > 0) { count = current->filter(bundle); } if (count < 0) { totalCount = count; goto bail; } totalCount += count;//统计资源文件个数 } else { fprintf(stderr, "ERROR: '%s' is not a directory\n", res); return UNKNOWN_ERROR; } } } /* * Now do any additional raw files. * 接着收录剩余的指定的资源文件 */ for (int arg=0; arg<N; arg++) { const char* assetDir = bundle->getFileSpecEntry(arg); FileType type = getFileType(assetDir); if (type == kFileTypeNonexistent) { fprintf(stderr, "ERROR: input directory '%s' does not exist\n", assetDir); return UNKNOWN_ERROR; } if (type != kFileTypeDirectory) { fprintf(stderr, "ERROR: '%s' is not a directory\n", assetDir); return UNKNOWN_ERROR; } String8 assetRoot(assetDir); if (bundle->getVerbose()) printf("Processing raw dir '%s'\n", (const char*) assetDir); /* * Do a recursive traversal of subdir tree. We don't make any * guarantees about ordering, so we're okay with an inorder search * using whatever order the OS happens to hand back to us. */ count = slurpFullTree(bundle, assetRoot, AaptGroupEntry(), String8(), mFullAssetPaths); if (count < 0) { /* failure; report error and remove archive */ totalCount = count; goto bail; } totalCount += count; if (bundle->getVerbose()) printf("Found %d asset file%s in %s\n", count, (count==1) ? "" : "s", assetDir); } count = validate(); if (count != NO_ERROR) { totalCount = count; goto bail; } count = filter(bundle); if (count != NO_ERROR) { totalCount = count; goto bail; }bail: return totalCount;}
这里面的逻辑是:
- 获取res目录的路径
- 获取AndroidManifest.xml文件路径
- 将AndroidManifest.xml文件添加到AaptAssets对象的成员变量mFiles中&添加到AaptGroupEntry的空对象并将其添加到一个类型为SortedVector的AaptAssets的成员变量mGroupEntries中
- 判断是否指定了assets文件夹,如果指定则解析它
- 收录指定的res资源目录下的资源文件
- 接着收录剩余的指定的资源文件
sp<AaptFile> AaptAssets::addFile( const String8& filePath, const AaptGroupEntry& entry, const String8& srcDir, sp<AaptGroup>* outGroup, const String8& resType){ sp<AaptDir> dir = this;//AaptAssets类继承了一个AaptDir类 sp<AaptGroup> group; sp<AaptFile> file; String8 root, remain(filePath), partialPath; while (remain.length() > 0) { //获取remain所描述文件的工作目录,如果其仅仅指定了文件名则返回文件名,如果文件名前添加了路径,则返回最上层的目录名 //例如,remain = “AndroidManifest.xml”,则root=“AndroidManifest.xml”, remain = “”; //如果remain=“/rootpath/subpath/AndroidManifest.xml”,则,root=“rootpath”, remain=”subpath/AndroidManifest.xml” root = remain.walkPath(&remain); partialPath.appendPath(root); const String8 rootStr(root); //在这里remain.length()返回0 if (remain.length() == 0) { //添加资源文件到mFiles中去 //dir指向当前AaptAssets对象,其调用getFiles返回类型为 //DefaultKeyVector<String8, sp<AaptGroup>>成员变量mFiles,判断其内部 //是否包含了名称为rootStr的AaptGroup对象,并返回其位置值 ssize_t i = dir->getFiles().indexOfKey(rootStr); //如果返回的位置值>=0表示mFiles中已经包含了这个名为rootStr的 //AaptGroup对象,则将group指向该对象, 否则新建一个名称为rootStr //的AaptGroup对象并添加到mFiles中去 if (i >= 0) { group = dir->getFiles().valueAt(i); } else { group = new AaptGroup(rootStr, filePath); status_t res = dir->addFile(rootStr, group); if (res != NO_ERROR) { return NULL; } } // 新建一个AaptFile对象指向需要添加的源文件, 并将该AaptFile对象 // 添加到类型为DefaultKeyedVector<AaptGroupEntry, sp<AaptFile> >的 // AaptGroup的成员变量 mFiles中去 file = new AaptFile(srcDir.appendPathCopy(filePath), entry, resType); status_t res = group->addFile(file); if (res != NO_ERROR) { return NULL; } break; } else { //添加资源目录到mDirs中去 //dir指向当前AaptAssets对象,其调用getDirs返回类型为 //DefaultKeyVector<String8, sp<AaptDir>>成员变量mDirs,判断其内部 //是否包含了名称为rootStr的AaptDir对象,并返回其位置值 ssize_t i = dir->getDirs().indexOfKey(rootStr); //如果返回的位置值>=0表示mDirs中已经包含了这个名为rootStr的 //AaptDir对象,则将dir指向该对象,否则新建一个名称为rootStr的AaptDir对象并添加到mDirs中去 if (i >= 0) { dir = dir->getDirs().valueAt(i); } else { sp<AaptDir> subdir = new AaptDir(rootStr, partialPath); status_t res = dir->addDir(rootStr, subdir); if (res != NO_ERROR) { return NULL; } dir = subdir; } } } //将一个空的AaptGroupEntry对象添加到mGroupEntries中去,其是一个SortedVector mGroupEntries.add(entry); if (outGroup) *outGroup = group; return file;}
这里面核心的逻辑是:
- 传入AndroidManifest.xml相关路径
- 将相关路径进行拆分
- 封装成一个group = new AaptGroup(rootStr, filePath);对象
- 将AaptGroup对象添加到AaptAssets的成员变量mFiles中
- 新建一个AaptFile对象,参数指向入AndroidManifest.xml相关路径
- 将AaptFile添加到AaptGroup的mFiles中
解析assets中的文件
String8 assetRoot(assetDirs[i]);//创建一个名为”assets”的AaptDir对象sp<AaptDir> assetAaptDir = makeDir(String8(kAssetDir));//assetsAaptGroupEntry group;//收录目录“assets”下的资源文件,并返回资源文件个数 count = assetAaptDir->slurpFullTree(bundle, assetRoot, group, String8(), mFullAssetPaths, true);
//收录路径名为srcDir目录下的所有资源文件,并将对应目录下的文件名都保存到fullResPaths中去ssize_t AaptAssets::slurpFullTree(Bundle* bundle, const String8& srcDir, const AaptGroupEntry& kind, const String8& resType, sp<FilePathStore>& fullResPaths, const bool overwrite){ //接着调用父类中的AaptDir的成员函数slurpFullTree收录srcDir中的资源文件 ssize_t res = AaptDir::slurpFullTree(bundle, srcDir, kind, resType, fullResPaths, overwrite); if (res > 0) { //如果收录的资源个数>0,则将其归为一类,为这类资源文件创建一个对应 //AaptGroupEntry对象并添加到对应的成员变量mGroupEntries中去 mGroupEntries.add(kind); } return res;}
最后都要添加到AaptAssets成员变量mGroupEntries中去。
ssize_t AaptDir::slurpFullTree(Bundle* bundle, const String8& srcDir, const AaptGroupEntry& kind, const String8& resType, sp<FilePathStore>& fullResPaths, const bool overwrite){ Vector<String8> fileNames; { DIR* dir = NULL; // 首先打开将要收录的资源文件所在的源目录 dir = opendir(srcDir.string()); if (dir == NULL) { fprintf(stderr, "ERROR: opendir(%s): %s\n", srcDir.string(), strerror(errno)); return UNKNOWN_ERROR; } /* * Slurp the filenames out of the directory. * 遍历srcDir目录下的每一个资源文件,将其添加到AaptAssets的成员变量 * mFullAssetPaths中,其继承了一个Vector<String8> */ while (1) { struct dirent* entry; entry = readdir(dir); if (entry == NULL) break; if (isHidden(srcDir.string(), entry->d_name)) continue; String8 name(entry->d_name); fileNames.add(name); // Add fully qualified path for dependency purposes // if we're collecting them // 按照全部路径将资源文件添加到fullResPaths中去 if (fullResPaths != NULL) { fullResPaths->add(srcDir.appendPathCopy(name)); } } closedir(dir); } ssize_t count = 0; /* * Stash away the files and recursively descend into subdirectories. * 递归解析srcDir下的子目录中的资源文件,直到收录完所有的目录中的资源文件为止 */ const size_t N = fileNames.size(); size_t i; for (i = 0; i < N; i++) { String8 pathName(srcDir); FileType type; pathName.appendPath(fileNames[i].string()); type = getFileType(pathName.string()); //如果是资源子目录,并且其尚未收录在mDirs中,则为其创建一个 //AaptDir对象,继续递归遍历其中的资源文件及目录 if (type == kFileTypeDirectory) { sp<AaptDir> subdir; bool notAdded = false; if (mDirs.indexOfKey(fileNames[i]) >= 0) { subdir = mDirs.valueFor(fileNames[i]); } else { subdir = new AaptDir(fileNames[i], mPath.appendPathCopy(fileNames[i])); notAdded = true; } ssize_t res = subdir->slurpFullTree(bundle, pathName, kind, resType, fullResPaths, overwrite); if (res < NO_ERROR) { return res; } if (res > 0 && notAdded) { mDirs.add(fileNames[i], subdir);//将资源目录添加到mDirs变量中 } count += res; // 如果其为一个资源文件,则为其创建一个指定的AaptFile变量 //并为其创建一个对应的AaptGroup变量, 将这个AaptGroup变量添加 //到mFiles变量中,然后将AaptFile变量添加到AaptGroup中去 } else if (type == kFileTypeRegular) { sp<AaptFile> file = new AaptFile(pathName, kind, resType); status_t err = addLeafFile(fileNames[i], file, overwrite); if (err != NO_ERROR) { return err; } //返回总的资源文件个数 count++; } else { if (bundle->getVerbose()) printf(" (ignoring non-file/dir '%s')\n", pathName.string()); } } return count;}
- 遍历assets中的所有文件和目录将所有绝对路径添加到fullResPaths中这个对应的是AaptAssets的成员变量mFullAssetPaths
- 将资源目录添加到成员变量mDirs中
DefaultKeyedVector<String8, sp<AaptDir> > mDirs;
- 如果是一个资源文件则为其创建一个指定的AaptFile变量并为其创建一个对应AaptGroup变量,将这个AaptGroup对象添加到mFiles变量中,然后将AaptFile变量添加到AaptGroup中去
收录res下文件:
/* * If a directory of resource-specific assets was supplied, slurp 'em up. * 收录指定的res资源目录下的资源文件 */for (size_t i=0; i<dirCount; i++) { const char *res = resDirs[i]; if (res) { type = getFileType(res);//获取文件类型 if (type == kFileTypeNonexistent) { fprintf(stderr, "ERROR: resource directory '%s' does not exist\n", res); return UNKNOWN_ERROR; } if (type == kFileTypeDirectory) { //如果指定了多个res资源目录文件, 则为其创建多个AaptAssets //类来分别收录这些目录中的信息,并将其设置赋值给当前AaptAssets对象的成员变量mOverlay if (i>0) { sp<AaptAssets> nextOverlay = new AaptAssets(); current->setOverlay(nextOverlay); current = nextOverlay; current->setFullResPaths(mFullResPaths); } //调用成员函数slurpResourceTree来收录res目录下的资源文件 count = current->slurpResourceTree(bundle, String8(res)); if (i > 0 && count > 0) { count = current->filter(bundle); } if (count < 0) { totalCount = count; goto bail; } totalCount += count;//统计资源文件个数 } else { fprintf(stderr, "ERROR: '%s' is not a directory\n", res); return UNKNOWN_ERROR; } }}
- 如果有多个res目录就遍历多个,为每一个res目录都产生一个AaptAssets并将这个对象的引用设置给第一次创建的AaptAssets的setOverlay方法中。挨个调用slurpResourceTree进行添加路径
这样就形成了一个AaptAssets的链表结构。
到目前为止我们就将AndroidManifest.xml文件目录assets和res下的资源目录和资源文件收录起来保存到AaptAssets中的成员变量中并且形成了一个AaptAssets链表(如果有多个res目录的话)
编译res目录下资源文件以及AndroidManifest.xml文件
err = buildResources(bundle, assets, builder);
status_t buildResources(Bundle* bundle, const sp<AaptAssets>& assets, sp<ApkBuilder>& builder){ // First, look for a package file to parse. This is required to // be able to generate the resource information. //首先从assets中获取AndroidManifest.xml文件的信息 //AndroidManifest.xml文件信息是保存在assets的成员变量mFiles中的, //但是其被封装成一个AaptFile类对象保存在AaptGroup对象最终再保存到mFiles中的 sp<AaptGroup> androidManifestFile = assets->getFiles().valueFor(String8("AndroidManifest.xml")); if (androidManifestFile == NULL) { fprintf(stderr, "ERROR: No AndroidManifest.xml file found.\n"); return UNKNOWN_ERROR; } //解析AndroidManifest.xml文件 status_t err = parsePackage(bundle, assets, androidManifestFile); if (err != NO_ERROR) { return err; } if (kIsDebug) { printf("Creating resources for package %s\n", assets->getPackage().string()); } ResourceTable::PackageType packageType = ResourceTable::App; if (bundle->getBuildSharedLibrary()) { packageType = ResourceTable::SharedLibrary; } else if (bundle->getExtending()) { packageType = ResourceTable::System; } else if (!bundle->getFeatureOfPackage().isEmpty()) { packageType = ResourceTable::AppFeature; } //根据包名创建一个对应的ResourceTable ,在上面解析AndroidManifest.xml中解析的包名 ResourceTable table(bundle, String16(assets->getPackage()), packageType); //添加被引用资源包,比如系统的那些android:命名空间下的资源也就是android.jar err = table.addIncludedResources(bundle, assets); if (err != NO_ERROR) { return err; } if (kIsDebug) { printf("Found %d included resource packages\n", (int)table.size()); } // 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; } // -------------------------------------------------------------- // First, gather all resource information. // -------------------------------------------------------------- // resType -> leafName -> group KeyedVector<String8, sp<ResourceTypeSet> > *resources = new KeyedVector<String8, sp<ResourceTypeSet> >; //调用collect_files将前面收集到assets中的各类资源文件重新收集到resources中来 collect_files(assets, resources); //定义收集各类资源文件的容器 sp<ResourceTypeSet> drawables; sp<ResourceTypeSet> layouts; sp<ResourceTypeSet> anims; sp<ResourceTypeSet> animators; sp<ResourceTypeSet> interpolators; sp<ResourceTypeSet> transitions; sp<ResourceTypeSet> xmls; sp<ResourceTypeSet> raws; sp<ResourceTypeSet> colors; sp<ResourceTypeSet> menus; sp<ResourceTypeSet> mipmaps; //将保存到resources中的各类文件的Set保存到我们上述定义的Set中去 ASSIGN_IT(drawable); ASSIGN_IT(layout); ASSIGN_IT(anim); ASSIGN_IT(animator); ASSIGN_IT(interpolator); ASSIGN_IT(transition); ASSIGN_IT(xml); ASSIGN_IT(raw); ASSIGN_IT(color); ASSIGN_IT(menu); ASSIGN_IT(mipmap); //设置assets的资源为resources中保存的 assets->setResources(resources); // now go through any resource overlays and collect their files //判断当前应用程序是否有overlay的资源,有的话将assets中保存的资源设置为overlay中 sp<AaptAssets> current = assets->getOverlay(); while(current.get()) { KeyedVector<String8, sp<ResourceTypeSet> > *resources = new KeyedVector<String8, sp<ResourceTypeSet> >; current->setResources(resources); collect_files(current, resources); current = current->getOverlay(); } // apply the overlay files to the base set //如果有overlay资源则使用overlay资源替换现有资源 if (!applyFileOverlay(bundle, assets, &drawables, "drawable") || !applyFileOverlay(bundle, assets, &layouts, "layout") || !applyFileOverlay(bundle, assets, &anims, "anim") || !applyFileOverlay(bundle, assets, &animators, "animator") || !applyFileOverlay(bundle, assets, &interpolators, "interpolator") || !applyFileOverlay(bundle, assets, &transitions, "transition") || !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) { //预处理图像, 目前只支持处理png格式图像 err = preProcessImages(bundle, assets, drawables, "drawable"); } if (err == NO_ERROR) { //处理drawables中的资源 //我们分析如何将收集到一个AaptAsset中的资源文件信息分类重新由函数makeFileResources组织到一个ResourceTable对象 //中去,这些资源文件的信息最终组织在Package, Type, Entry, Item中,Package代表当前编译APK的包信息, //Type类保存资源类型信息, Entry代表保存资源文件,Item保存文件中属性信息. Package包含Type, Type包含Entry, //Entry包含Item. err = makeFileResources(bundle, assets, &table, drawables, "drawable"); if (err != NO_ERROR) { hasErrors = true; } } else { hasErrors = true; } } if (mipmaps != NULL) { if (bundle->getOutputAPKFile() != NULL) { err = preProcessImages(bundle, assets, mipmaps, "mipmap"); } if (err == NO_ERROR) { err = makeFileResources(bundle, assets, &table, mipmaps, "mipmap"); if (err != NO_ERROR) { hasErrors = true; } } else { hasErrors = true; } } if (layouts != NULL) { err = makeFileResources(bundle, assets, &table, layouts, "layout"); if (err != NO_ERROR) { hasErrors = true; } } if (anims != NULL) { err = makeFileResources(bundle, assets, &table, anims, "anim"); if (err != NO_ERROR) { hasErrors = true; } } if (animators != NULL) { err = makeFileResources(bundle, assets, &table, animators, "animator"); if (err != NO_ERROR) { hasErrors = true; } } if (transitions != NULL) { err = makeFileResources(bundle, assets, &table, transitions, "transition"); if (err != NO_ERROR) { hasErrors = true; } } if (interpolators != NULL) { err = makeFileResources(bundle, assets, &table, interpolators, "interpolator"); if (err != NO_ERROR) { hasErrors = true; } } if (xmls != NULL) { err = makeFileResources(bundle, assets, &table, xmls, "xml"); if (err != NO_ERROR) { hasErrors = true; } } if (raws != NULL) { err = makeFileResources(bundle, assets, &table, raws, "raw"); if (err != NO_ERROR) { hasErrors = true; } } // compile resources current = assets; while(current.get()) { KeyedVector<String8, sp<ResourceTypeSet> > *resources = current->getResources(); ssize_t index = resources->indexOfKey(String8("values")); if (index >= 0) { ResourceDirIterator it(resources->valueAt(index), String8("values")); ssize_t res; while ((res=it.next()) == NO_ERROR) { sp<AaptFile> file = it.getFile(); //对于values则是由这个独立的函数进行组织的,将解析完的数据保存在变量table中 res = compileResourceFile(bundle, assets, file, it.getParams(), (current!=assets), &table); if (res != NO_ERROR) { hasErrors = true; } } } current = current->getOverlay(); } if (colors != NULL) { err = makeFileResources(bundle, assets, &table, colors, "color"); if (err != NO_ERROR) { hasErrors = true; } } if (menus != NULL) { err = makeFileResources(bundle, assets, &table, menus, "menu"); if (err != NO_ERROR) { hasErrors = true; } } // -------------------------------------------------------------------- // Assignment of resource IDs and initial generation of resource table. // -------------------------------------------------------------------- //到目前为止上面的工作我们将当前正在编译的应用程序所依赖的所有资源文件信息(包括系统android.jar中的和 //应用程序自身的被收集到一个AaptAsset类对象中的)都收集到了一个ResourceTable对象中去了, //接下来buildResources函数的工作是为这些资源文件中的各种属性分配资源ID //下面我们就开始分配Bag资源ID // 调用ResourceTable类的成员函数assignResourceIds分配bag资源ID信息 if (table.hasResources()) { err = table.assignResourceIds(); if (err < NO_ERROR) { return err; } } // -------------------------------------------------------------- // 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(); //对于对于anim, animator, interpolator, xml, color, menu, drawable中的xml文件都是通过compileXmlFile函数进行编译的. //在这里面用XMLNode::assignResourceIds里面给每个属性赋值 err = compileXmlFile(bundle, assets, String16(it.getBaseName()), 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; } if (anims != NULL) { ResourceDirIterator it(anims, String8("anim")); while ((err=it.next()) == NO_ERROR) { err = compileXmlFile(bundle, assets, String16(it.getBaseName()), it.getFile(), &table, xmlFlags); if (err != NO_ERROR) { hasErrors = true; } } if (err < NO_ERROR) { hasErrors = true; } err = NO_ERROR; } if (animators != NULL) { ResourceDirIterator it(animators, String8("animator")); while ((err=it.next()) == NO_ERROR) { err = compileXmlFile(bundle, assets, String16(it.getBaseName()), it.getFile(), &table, xmlFlags); if (err != NO_ERROR) { hasErrors = true; } } if (err < NO_ERROR) { hasErrors = true; } err = NO_ERROR; } if (interpolators != NULL) { ResourceDirIterator it(interpolators, String8("interpolator")); while ((err=it.next()) == NO_ERROR) { err = compileXmlFile(bundle, assets, String16(it.getBaseName()), it.getFile(), &table, xmlFlags); if (err != NO_ERROR) { hasErrors = true; } } if (err < NO_ERROR) { hasErrors = true; } err = NO_ERROR; } if (transitions != NULL) { ResourceDirIterator it(transitions, String8("transition")); while ((err=it.next()) == NO_ERROR) { err = compileXmlFile(bundle, assets, String16(it.getBaseName()), it.getFile(), &table, xmlFlags); if (err != NO_ERROR) { hasErrors = true; } } if (err < NO_ERROR) { hasErrors = true; } err = NO_ERROR; } if (xmls != NULL) { ResourceDirIterator it(xmls, String8("xml")); while ((err=it.next()) == NO_ERROR) { err = compileXmlFile(bundle, assets, String16(it.getBaseName()), it.getFile(), &table, xmlFlags); if (err != NO_ERROR) { hasErrors = true; } } if (err < NO_ERROR) { hasErrors = true; } err = NO_ERROR; } if (drawables != NULL) { ResourceDirIterator it(drawables, String8("drawable")); while ((err=it.next()) == NO_ERROR) { err = postProcessImage(bundle, assets, &table, it.getFile()); if (err != NO_ERROR) { hasErrors = true; } } if (err < NO_ERROR) { hasErrors = true; } err = NO_ERROR; } if (colors != NULL) { ResourceDirIterator it(colors, String8("color")); while ((err=it.next()) == NO_ERROR) { err = compileXmlFile(bundle, assets, String16(it.getBaseName()), it.getFile(), &table, xmlFlags); if (err != NO_ERROR) { hasErrors = true; } } if (err < NO_ERROR) { hasErrors = true; } err = NO_ERROR; } if (menus != NULL) { ResourceDirIterator it(menus, String8("menu")); while ((err=it.next()) == NO_ERROR) { String8 src = it.getFile()->getPrintableSource(); err = compileXmlFile(bundle, assets, String16(it.getBaseName()), it.getFile(), &table, xmlFlags); if (err == NO_ERROR) { ResXMLTree block; block.setTo(it.getFile()->getData(), it.getFile()->getSize(), true); checkForIds(src, block); } else { hasErrors = true; } } if (err < NO_ERROR) { hasErrors = true; } err = NO_ERROR; } // Now compile any generated resources. std::queue<CompileResourceWorkItem>& workQueue = table.getWorkQueue(); while (!workQueue.empty()) { CompileResourceWorkItem& workItem = workQueue.front(); err = compileXmlFile(bundle, assets, workItem.resourceName, workItem.file, &table, xmlFlags); if (err == NO_ERROR) { assets->addResource(workItem.resPath.getPathLeaf(), workItem.resPath, workItem.file, workItem.file->getResourceType()); } else { hasErrors = true; } workQueue.pop(); } if (table.validateLocalizations()) { hasErrors = true; } if (hasErrors) { return UNKNOWN_ERROR; } // If we're not overriding the platform build versions, // extract them from the platform APK. if (packageType != ResourceTable::System && (bundle->getPlatformBuildVersionCode() == "" || bundle->getPlatformBuildVersionName() == "")) { err = extractPlatformBuildVersion(assets->getAssetManager(), bundle); if (err != NO_ERROR) { return UNKNOWN_ERROR; } } //下面这些代码是产生经过flatten的AndroidManifest.xml文件 // 取出AndroidManifest.xml文件 const sp<AaptFile> manifestFile(androidManifestFile->getFiles().valueAt(0)); String8 manifestPath(manifestFile->getPrintableSource()); // Generate final compiled manifest file. //清空manifestFile所指向的AndroidManfiest.xml的信息,然后重新解析 manifestFile->clearData(); sp<XMLNode> manifestTree = XMLNode::parse(manifestFile); if (manifestTree == NULL) { return UNKNOWN_ERROR; } //检测是否AndroidManifest.xml中是否有overlay资源,如果有就将现有资源替换 err = massageManifest(bundle, manifestTree); if (err < NO_ERROR) { return err; } //编译AndroidManifest.xml文件 err = compileXmlFile(bundle, assets, String16(), manifestTree, manifestFile, &table); if (err < NO_ERROR) { return err; } if (table.modifyForCompat(bundle) != NO_ERROR) { return UNKNOWN_ERROR; } //block.restart(); //printXMLBlock(&block); // -------------------------------------------------------------- // Generate the final resource table. // Re-flatten because we may have added new resource IDs // -------------------------------------------------------------- ResTable finalResTable; sp<AaptFile> resFile; if (table.hasResources()) { //生成资源符号表 sp<AaptSymbols> symbols = assets->getSymbolsFor(String8("R")); err = table.addSymbols(symbols, bundle->getSkipSymbolsWithoutDefaultLocalization()); if (err < NO_ERROR) { return err; } KeyedVector<Symbol, Vector<SymbolDefinition> > densityVaryingResources; if (builder->getSplits().size() > 1) { // Only look for density varying resources if we're generating // splits. table.getDensityVaryingResources(densityVaryingResources); } Vector<sp<ApkSplit> >& splits = builder->getSplits(); const size_t numSplits = splits.size(); for (size_t i = 0; i < numSplits; i++) { sp<ApkSplit>& split = splits.editItemAt(i); // 生成资源索引表 sp<AaptFile> flattenedTable = new AaptFile(String8("resources.arsc"), AaptGroupEntry(), String8()); //ResourceTable::flatten用于生成资源索引表resources.arsc err = table.flatten(bundle, split->getResourceFilter(), flattenedTable, split->isBase()); if (err != NO_ERROR) { fprintf(stderr, "Failed to generate resource table for split '%s'\n", split->getPrintableName().string()); return err; } split->addEntry(String8("resources.arsc"), flattenedTable); if (split->isBase()) { resFile = flattenedTable; err = finalResTable.add(flattenedTable->getData(), flattenedTable->getSize()); if (err != NO_ERROR) { fprintf(stderr, "Generated resource table is corrupt.\n"); return err; } } else { ResTable resTable; err = resTable.add(flattenedTable->getData(), flattenedTable->getSize()); if (err != NO_ERROR) { fprintf(stderr, "Generated resource table for split '%s' is corrupt.\n", split->getPrintableName().string()); return err; } bool hasError = false; const std::set<ConfigDescription>& splitConfigs = split->getConfigs(); for (std::set<ConfigDescription>::const_iterator iter = splitConfigs.begin(); iter != splitConfigs.end(); ++iter) { const ConfigDescription& config = *iter; if (AaptConfig::isDensityOnly(config)) { // Each density only split must contain all // density only resources. Res_value val; resTable.setParameters(&config); const size_t densityVaryingResourceCount = densityVaryingResources.size(); for (size_t k = 0; k < densityVaryingResourceCount; k++) { const Symbol& symbol = densityVaryingResources.keyAt(k); ssize_t block = resTable.getResource(symbol.id, &val, true); if (block < 0) { // Maybe it's in the base? finalResTable.setParameters(&config); block = finalResTable.getResource(symbol.id, &val, true); } if (block < 0) { hasError = true; SourcePos().error("%s has no definition for density split '%s'", symbol.toString().string(), config.toString().string()); if (bundle->getVerbose()) { const Vector<SymbolDefinition>& defs = densityVaryingResources[k]; const size_t defCount = std::min(size_t(5), defs.size()); for (size_t d = 0; d < defCount; d++) { const SymbolDefinition& def = defs[d]; def.source.error("%s has definition for %s", symbol.toString().string(), def.config.toString().string()); } if (defCount < defs.size()) { SourcePos().error("and %d more ...", (int) (defs.size() - defCount)); } } } } } } if (hasError) { return UNKNOWN_ERROR; } // Generate the AndroidManifest for this split. sp<AaptFile> generatedManifest = new AaptFile(String8("AndroidManifest.xml"), AaptGroupEntry(), String8()); err = generateAndroidManifestForSplit(bundle, assets, split, generatedManifest, &table); if (err != NO_ERROR) { fprintf(stderr, "Failed to generate AndroidManifest.xml for split '%s'\n", split->getPrintableName().string()); return err; } split->addEntry(String8("AndroidManifest.xml"), generatedManifest); } } if (bundle->getPublicOutputFile()) { FILE* fp = fopen(bundle->getPublicOutputFile(), "w+"); if (fp == NULL) { fprintf(stderr, "ERROR: Unable to open public definitions output file %s: %s\n", (const char*)bundle->getPublicOutputFile(), strerror(errno)); return UNKNOWN_ERROR; } if (bundle->getVerbose()) { printf(" Writing public definitions to %s.\n", bundle->getPublicOutputFile()); } table.writePublicDefinitions(String16(assets->getPackage()), fp); fclose(fp); } if (finalResTable.getTableCount() == 0 || resFile == NULL) { fprintf(stderr, "No resource table was generated.\n"); return UNKNOWN_ERROR; } } // Perform a basic validation of the manifest file. This time we // parse it with the comments intact, so that we can use them to // generate java docs... so we are not going to write this one // back out to the final manifest data. sp<AaptFile> outManifestFile = new AaptFile(manifestFile->getSourceFile(), manifestFile->getGroupEntry(), manifestFile->getResourceType()); err = compileXmlFile(bundle, assets, String16(), manifestFile, outManifestFile, &table, XML_COMPILE_ASSIGN_ATTRIBUTE_IDS | XML_COMPILE_STRIP_WHITESPACE | XML_COMPILE_STRIP_RAW_VALUES); if (err < NO_ERROR) { return err; } ResXMLTree block; block.setTo(outManifestFile->getData(), outManifestFile->getSize(), true); String16 manifest16("manifest"); String16 permission16("permission"); String16 permission_group16("permission-group"); String16 uses_permission16("uses-permission"); String16 instrumentation16("instrumentation"); String16 application16("application"); String16 provider16("provider"); String16 service16("service"); String16 receiver16("receiver"); String16 activity16("activity"); String16 action16("action"); String16 category16("category"); String16 data16("scheme"); String16 feature_group16("feature-group"); String16 uses_feature16("uses-feature"); const char* packageIdentChars = "abcdefghijklmnopqrstuvwxyz" "ABCDEFGHIJKLMNOPQRSTUVWXYZ._0123456789"; const char* packageIdentCharsWithTheStupid = "abcdefghijklmnopqrstuvwxyz" "ABCDEFGHIJKLMNOPQRSTUVWXYZ._0123456789-"; const char* classIdentChars = "abcdefghijklmnopqrstuvwxyz" "ABCDEFGHIJKLMNOPQRSTUVWXYZ._0123456789$"; const char* processIdentChars = "abcdefghijklmnopqrstuvwxyz" "ABCDEFGHIJKLMNOPQRSTUVWXYZ._0123456789:"; const char* authoritiesIdentChars = "abcdefghijklmnopqrstuvwxyz" "ABCDEFGHIJKLMNOPQRSTUVWXYZ._0123456789-:;"; const char* typeIdentChars = "abcdefghijklmnopqrstuvwxyz" "ABCDEFGHIJKLMNOPQRSTUVWXYZ._0123456789:-/*+"; const char* schemeIdentChars = "abcdefghijklmnopqrstuvwxyz" "ABCDEFGHIJKLMNOPQRSTUVWXYZ._0123456789-"; ResXMLTree::event_code_t code; sp<AaptSymbols> permissionSymbols; sp<AaptSymbols> permissionGroupSymbols; while ((code=block.next()) != ResXMLTree::END_DOCUMENT && code > ResXMLTree::BAD_DOCUMENT) { if (code == ResXMLTree::START_TAG) { size_t len; if (block.getElementNamespace(&len) != NULL) { continue; } if (strcmp16(block.getElementName(&len), manifest16.string()) == 0) { if (validateAttr(manifestPath, finalResTable, block, NULL, "package", packageIdentChars, true) != ATTR_OKAY) { hasErrors = true; } if (validateAttr(manifestPath, finalResTable, block, RESOURCES_ANDROID_NAMESPACE, "sharedUserId", packageIdentChars, false) != ATTR_OKAY) { hasErrors = true; } } else if (strcmp16(block.getElementName(&len), permission16.string()) == 0 || strcmp16(block.getElementName(&len), permission_group16.string()) == 0) { const bool isGroup = strcmp16(block.getElementName(&len), permission_group16.string()) == 0; if (validateAttr(manifestPath, finalResTable, block, RESOURCES_ANDROID_NAMESPACE, "name", isGroup ? packageIdentCharsWithTheStupid : packageIdentChars, true) != ATTR_OKAY) { hasErrors = true; } SourcePos srcPos(manifestPath, block.getLineNumber()); sp<AaptSymbols> syms; if (!isGroup) { syms = permissionSymbols; if (syms == NULL) { sp<AaptSymbols> symbols = assets->getSymbolsFor(String8("Manifest")); syms = permissionSymbols = symbols->addNestedSymbol( String8("permission"), srcPos); } } else { syms = permissionGroupSymbols; if (syms == NULL) { sp<AaptSymbols> symbols = assets->getSymbolsFor(String8("Manifest")); syms = permissionGroupSymbols = symbols->addNestedSymbol( String8("permission_group"), srcPos); } } size_t len; ssize_t index = block.indexOfAttribute(RESOURCES_ANDROID_NAMESPACE, "name"); const char16_t* id = block.getAttributeStringValue(index, &len); if (id == NULL) { fprintf(stderr, "%s:%d: missing name attribute in element <%s>.\n", manifestPath.string(), block.getLineNumber(), String8(block.getElementName(&len)).string()); hasErrors = true; break; } String8 idStr(id); char* p = idStr.lockBuffer(idStr.size()); char* e = p + idStr.size(); bool begins_with_digit = true; // init to true so an empty string fails while (e > p) { e--; if (*e >= '0' && *e <= '9') { begins_with_digit = true; continue; } if ((*e >= 'a' && *e <= 'z') || (*e >= 'A' && *e <= 'Z') || (*e == '_')) { begins_with_digit = false; continue; } if (isGroup && (*e == '-')) { *e = '_'; begins_with_digit = false; continue; } e++; break; } idStr.unlockBuffer(); // verify that we stopped because we hit a period or // the beginning of the string, and that the // identifier didn't begin with a digit. if (begins_with_digit || (e != p && *(e-1) != '.')) { fprintf(stderr, "%s:%d: Permission name <%s> is not a valid Java symbol\n", manifestPath.string(), block.getLineNumber(), idStr.string()); hasErrors = true; } syms->addStringSymbol(String8(e), idStr, srcPos); const char16_t* cmt = block.getComment(&len); if (cmt != NULL && *cmt != 0) { //printf("Comment of %s: %s\n", String8(e).string(), // String8(cmt).string()); syms->appendComment(String8(e), String16(cmt), srcPos); } else { //printf("No comment for %s\n", String8(e).string()); } syms->makeSymbolPublic(String8(e), srcPos); } else if (strcmp16(block.getElementName(&len), uses_permission16.string()) == 0) { if (validateAttr(manifestPath, finalResTable, block, RESOURCES_ANDROID_NAMESPACE, "name", packageIdentChars, true) != ATTR_OKAY) { hasErrors = true; } } else if (strcmp16(block.getElementName(&len), instrumentation16.string()) == 0) { if (validateAttr(manifestPath, finalResTable, block, RESOURCES_ANDROID_NAMESPACE, "name", classIdentChars, true) != ATTR_OKAY) { hasErrors = true; } if (validateAttr(manifestPath, finalResTable, block, RESOURCES_ANDROID_NAMESPACE, "targetPackage", packageIdentChars, true) != ATTR_OKAY) { hasErrors = true; } } else if (strcmp16(block.getElementName(&len), application16.string()) == 0) { if (validateAttr(manifestPath, finalResTable, block, RESOURCES_ANDROID_NAMESPACE, "name", classIdentChars, false) != ATTR_OKAY) { hasErrors = true; } if (validateAttr(manifestPath, finalResTable, block, RESOURCES_ANDROID_NAMESPACE, "permission", packageIdentChars, false) != ATTR_OKAY) { hasErrors = true; } if (validateAttr(manifestPath, finalResTable, block, RESOURCES_ANDROID_NAMESPACE, "process", processIdentChars, false) != ATTR_OKAY) { hasErrors = true; } if (validateAttr(manifestPath, finalResTable, block, RESOURCES_ANDROID_NAMESPACE, "taskAffinity", processIdentChars, false) != ATTR_OKAY) { hasErrors = true; } } else if (strcmp16(block.getElementName(&len), provider16.string()) == 0) { if (validateAttr(manifestPath, finalResTable, block, RESOURCES_ANDROID_NAMESPACE, "name", classIdentChars, true) != ATTR_OKAY) { hasErrors = true; } if (validateAttr(manifestPath, finalResTable, block, RESOURCES_ANDROID_NAMESPACE, "authorities", authoritiesIdentChars, true) != ATTR_OKAY) { hasErrors = true; } if (validateAttr(manifestPath, finalResTable, block, RESOURCES_ANDROID_NAMESPACE, "permission", packageIdentChars, false) != ATTR_OKAY) { hasErrors = true; } if (validateAttr(manifestPath, finalResTable, block, RESOURCES_ANDROID_NAMESPACE, "process", processIdentChars, false) != ATTR_OKAY) { hasErrors = true; } } else if (strcmp16(block.getElementName(&len), service16.string()) == 0 || strcmp16(block.getElementName(&len), receiver16.string()) == 0 || strcmp16(block.getElementName(&len), activity16.string()) == 0) { if (validateAttr(manifestPath, finalResTable, block, RESOURCES_ANDROID_NAMESPACE, "name", classIdentChars, true) != ATTR_OKAY) { hasErrors = true; } if (validateAttr(manifestPath, finalResTable, block, RESOURCES_ANDROID_NAMESPACE, "permission", packageIdentChars, false) != ATTR_OKAY) { hasErrors = true; } if (validateAttr(manifestPath, finalResTable, block, RESOURCES_ANDROID_NAMESPACE, "process", processIdentChars, false) != ATTR_OKAY) { hasErrors = true; } if (validateAttr(manifestPath, finalResTable, block, RESOURCES_ANDROID_NAMESPACE, "taskAffinity", processIdentChars, false) != ATTR_OKAY) { hasErrors = true; } } else if (strcmp16(block.getElementName(&len), action16.string()) == 0 || strcmp16(block.getElementName(&len), category16.string()) == 0) { if (validateAttr(manifestPath, finalResTable, block, RESOURCES_ANDROID_NAMESPACE, "name", packageIdentChars, true) != ATTR_OKAY) { hasErrors = true; } } else if (strcmp16(block.getElementName(&len), data16.string()) == 0) { if (validateAttr(manifestPath, finalResTable, block, RESOURCES_ANDROID_NAMESPACE, "mimeType", typeIdentChars, true) != ATTR_OKAY) { hasErrors = true; } if (validateAttr(manifestPath, finalResTable, block, RESOURCES_ANDROID_NAMESPACE, "scheme", schemeIdentChars, true) != ATTR_OKAY) { hasErrors = true; } } else if (strcmp16(block.getElementName(&len), feature_group16.string()) == 0) { int depth = 1; while ((code=block.next()) != ResXMLTree::END_DOCUMENT && code > ResXMLTree::BAD_DOCUMENT) { if (code == ResXMLTree::START_TAG) { depth++; if (strcmp16(block.getElementName(&len), uses_feature16.string()) == 0) { ssize_t idx = block.indexOfAttribute( RESOURCES_ANDROID_NAMESPACE, "required"); if (idx < 0) { continue; } int32_t data = block.getAttributeData(idx); if (data == 0) { fprintf(stderr, "%s:%d: Tag <uses-feature> can not have " "android:required=\"false\" when inside a " "<feature-group> tag.\n", manifestPath.string(), block.getLineNumber()); hasErrors = true; } } } else if (code == ResXMLTree::END_TAG) { depth--; if (depth == 0) { break; } } } } } } if (hasErrors) { return UNKNOWN_ERROR; } if (resFile != NULL) { // These resources are now considered to be a part of the included // resources, for others to reference. err = assets->addIncludedResources(resFile); if (err < NO_ERROR) { fprintf(stderr, "ERROR: Unable to parse generated resources, aborting.\n"); return err; } } return err;}
- 首先从AaptAssets的成员变量mFiles中拿到AndroidManifest.xml路径
- 解析AndroidManifest.xml文件
- 创建一个ResourceTable对象构造中传入AndroidManifest.xml的包名
- 添加被引用的资源包,比如android.jar中那些系统提前编译好的资源文件
- 设置U8编码方式
- 将assets各类资源文件重新收集到resources中
KeyedVector<String8, sp<ResourceTypeSet> >*resources = new KeyedVector<String8, sp<ResourceTypeSet> >;
- 定义各种资源文件容器
- 将保存到resources中的各类文件set到我们的容器中
- 判断是不是Overlay链表存在,如果存在则遍历将所有资源进行收集到resources中,如果有这些资源就进行替换当前set到容器中的资源。
- 然后进行drawable,mipmap,layout等等这些文件中R文件的生成当R文件生成
- 由于values特殊性则需要使用compileResourceFile单独进行解析生成R文件
- 以上所有的内容均保存在ResourceTable对象中
- 然后我们就开始分配bag Id
table.assignResourceIds();
- 然后编译xml文件,由于前面收集到了资源,所以后面就可以用
- 将AndroidManifest.xml文件进行flatten检测AndroidManifest.xml文件是否有overlay资源如果有就覆盖
- 编译AndroidManifest.xml文件
- 生成资源符号表,生成资源索引表
new AaptFile(String8("resources.arsc")
- ResourceTable::flatten用于生成资源索引表resources.arsc
解析AndroidManifest.xml文件
status_t err = parsePackage(bundle, assets, androidManifestFile);
static status_t parsePackage(Bundle* bundle, const sp<AaptAssets>& assets, const sp<AaptGroup>& grp){ //以下代码确保只有一个AndroidManifest.xml文件 if (grp->getFiles().size() != 1) { fprintf(stderr, "warning: Multiple AndroidManifest.xml files found, using %s\n", grp->getFiles().valueAt(0)->getPrintableSource().string()); } // 取出存放AndroidManifest.xml文件信息的AaptFile对象 sp<AaptFile> file = grp->getFiles().valueAt(0); //定义一个ResXMLTree对象,然后调用parseXMLResource来详细解析AndroidManifest.xml文件 //这个函数主要完成三个工作: //1. 收集file文件指向的xml文件中字符串资源信息. //2. 压平该file文件只想的xml文件中资源信息 //3. 将前两步组织的到的资源信息最终组织到一个ResXMLTree所描述的数据结构中. ResXMLTree block; status_t err = parseXMLResource(file, &block); if (err != NO_ERROR) { return err; } //printXMLBlock(&block); ResXMLTree::event_code_t code; //下列while循环找到起开始位置 while ((code=block.next()) != ResXMLTree::START_TAG && code != ResXMLTree::END_DOCUMENT && code != ResXMLTree::BAD_DOCUMENT) { } size_t len; if (code != ResXMLTree::START_TAG) { fprintf(stderr, "%s:%d: No start tag found\n", file->getPrintableSource().string(), block.getLineNumber()); return UNKNOWN_ERROR; } //首先找到manifest标签 if (strcmp16(block.getElementName(&len), String16("manifest").string()) != 0) { fprintf(stderr, "%s:%d: Invalid start tag %s, expected <manifest>\n", file->getPrintableSource().string(), block.getLineNumber(), String8(block.getElementName(&len)).string()); return UNKNOWN_ERROR; } //再找到pacakge标签属性所在block中的索引位置 ssize_t nameIndex = block.indexOfAttribute(NULL, "package"); if (nameIndex < 0) { fprintf(stderr, "%s:%d: <manifest> does not have package attribute.\n", file->getPrintableSource().string(), block.getLineNumber()); return UNKNOWN_ERROR; } assets->setPackage(String8(block.getAttributeStringValue(nameIndex, &len))); ssize_t revisionCodeIndex = block.indexOfAttribute(RESOURCES_ANDROID_NAMESPACE, "revisionCode"); if (revisionCodeIndex >= 0) { bundle->setRevisionCode(String8(block.getAttributeStringValue(revisionCodeIndex, &len)).string()); } //获取正在编译的资源的包名,并设置将其保存到assets的成员变量mPackage中 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 char16_t* minSdk16 = block.getAttributeStringValue(minSdkIndex, &len); const char* minSdk8 = strdup(String8(minSdk16).string()); bundle->setManifestMinSdkVersion(minSdk8); } } } } return NO_ERROR;}
- 解析AndroidManifest.xml文件
- 首先找到manifest标签
- 再找到pacakge标签属性所在block中的索引位置
- 找到SDK版本并设置minSdkVersion
ResXMLTree block;status_t err = parseXMLResource(file, &block);
其中file就是AndroidManifest.xml文件信息的AaptFile对象
block是新建的ResXMLTree
status_t parseXMLResource(const sp<AaptFile>& file, ResXMLTree* outTree, bool stripAll, bool keepComments, const char** cDataTags){ //接着调用XMLNode的成员函数parse来解析AndroidManifest.xml文件 sp<XMLNode> root = XMLNode::parse(file); if (root == NULL) { return UNKNOWN_ERROR; } root->removeWhitespace(stripAll, cDataTags); if (kIsDebug) { printf("Input XML from %s:\n", (const char*)file->getPrintableSource()); root->print(); } //新建一个AaptFile作为输出文件 sp<AaptFile> rsc = new AaptFile(String8(), AaptGroupEntry(), String8()); //调用flatten函数压平AndroidManifest.xml文件, 将压平后的xml文件信息按指定 //格式组织在rsc的数据缓冲区中,我们在flatten函数中将各种属性信息组织成如下方式并保存在一个AdaptFile对象中 status_t err = root->flatten(rsc, !keepComments, false); if (err != NO_ERROR) { return err; } //保存在AdaptFile对象rsc中的数据组织到outTree中 err = outTree->setTo(rsc->getData(), rsc->getSize(), true); if (err != NO_ERROR) { return err; } if (kIsDebug) { printf("Output XML:\n"); printXMLBlock(outTree); } return NO_ERROR;}
- 使用XMLNode::parse(file);解析
- 新建一个AaptFile作为输出文件
- 调用flatten函数压平AndroidManifest.xml文件, 将压平后的xml文件信息按指定格式组织在rsc的数据缓冲区中,我们在flatten函数中将各种属性信息组织成如下方式并保存在一个AdaptFile对象中
- 将输出文件AaptFile保存在AdaptFile对象rsc中的数据组织到outTree中
sp<XMLNode> XMLNode::parse(const sp<AaptFile>& file){ char buf[16384]; //以只读方式打开AndroidManifest.xml文件 int fd = open(file->getSourceFile().string(), O_RDONLY | O_BINARY); if (fd < 0) { SourcePos(file->getSourceFile(), -1).error("Unable to open file for read: %s", strerror(errno)); return NULL; } //创建一个XML文件解析器, 该解析器是定义在expat库中的 //Expat 是一个用C语言开发的、用来解析XML文档的开发库,它最初是开源的、 //Mozilla 项目下的一个XML解析器。采用流的方式来解析XML文件,并且基于 //事件通知型来调用分析到的数据,并不需要把所有XML文件全部加载到内存里, //这样可以分析非常大的XML文件。 //1.创建一个XML分析器。 XML_Parser parser = XML_ParserCreateNS(NULL, 1); ParseState state; state.filename = file->getPrintableSource(); state.parser = parser; //设置用户数据 XML_SetUserData(parser, &state); //2.第一个参数是那个Parser句柄,第二个和第三个参数则是整个Parser的核心,类型为CallBack的函数 XML_SetElementHandler(parser, startElement, endElement); /* startNamespace: 解析xmlns:android开头的信息: ** 参数: prefix = android, uri= android右边的属性值信息"http://schemas.android.com/apk/res/android" ** endNamespace - 销毁ParseState中缓存的数据 ** 这个特殊的xmlns:android="http://schemas.android.com/apk/res/android" ** 属性名和属性值会创建一个XMLNode作为根节点, 其也叫做命名空间 ** 解析命名空间和标签元素类似,就不再赘述 */ XML_SetNamespaceDeclHandler(parser, startNamespace, endNamespace); /* 函数是设置处理一个<>和</>之间的字段的回调 ** <Item>This is a normal text</Item> ** 那么字符串“This is a normal text”就称为一个CDATA */ XML_SetCharacterDataHandler(parser, characterData); /* 处理注释的函数 */ XML_SetCommentHandler(parser, commentData); ssize_t len; bool done; do { len = read(fd, buf, sizeof(buf)); done = len < (ssize_t)sizeof(buf); if (len < 0) { SourcePos(file->getSourceFile(), -1).error("Error reading file: %s\n", strerror(errno)); close(fd); return NULL; } /* 第二个参数是用户指定的Buffer指针, ** 第三个是这块Buffer中实际内容的字节数 ** 最后参数代表是否这块Buffer已经结束。 ** 比如要解析的XML文件太大,但内存比较吃紧,Buffer比较小,则可以循环读取文件,然后丢给Parser, ** 在文件读取结束前,isFinal参数为FALSE,反之为TRUE。 */ if (XML_Parse(parser, buf, len, done) == XML_STATUS_ERROR) { SourcePos(file->getSourceFile(), (int)XML_GetCurrentLineNumber(parser)).error( "Error parsing XML: %s\n", XML_ErrorString(XML_GetErrorCode(parser))); 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;}
- 打开AndroidManifest.xml文件 &&只读
- 创建一个XML分析器。
- 设置解析回调
- 进行解析XML_Parse(parser, buf, len, done)
void XMLCALLXMLNode::startElement(void *userData, const char *name, const char **atts){ if (kIsDebugParse) { printf("Start Element: %s\n", name); } ParseState* st = (ParseState*)userData; String16 ns16, name16; splitName(name, &ns16, &name16); //为每一个名称为name的标签创建一个XMLNode对象 sp<XMLNode> node = XMLNode::newElement(st->filename, ns16, name16); //设置标签开始的行号 node->setStartLineNumber(XML_GetCurrentLineNumber(st->parser)); if (st->pendingComment.size() > 0) { node->appendComment(st->pendingComment); st->pendingComment = String16(); } if (st->stack.size() > 0) { //而name子标签作为name标签XMLNode对象的一个 //子对象保存到XMLNode的成员变量mChildren(Vector)中, 而ParseState //只是用于缓存XMLNode数据信息用,缓存完成之后随即在endElement函数中销毁. st->stack.itemAt(st->stack.size()-1)->addChild(node); } else { st->root = node;//根节点是命名空间节点 } st->stack.push(node);//缓存 for (int i = 0; atts[i]; i += 2) { splitName(atts[i], &ns16, &name16); node->addAttribute(ns16, name16, String16(atts[i+1])); }}
处理解析的数据
解析的规则就是:
/* 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作为根节点, 其也叫做命名空间 ** 解析命名空间和标签元素类似,就不再赘述 */
最后一个个XMLNode就挂载到了根节点。
我们现在将AndroidManifest.xml数据已经解析成一个个XMLNode了,现在要通过flatten进行处理,就看下面怎么处理。
status_t XMLNode::flatten(const sp<AaptFile>& dest, bool stripComments, bool stripRawValues) const{ //创建一个字符串池StringPool变量strings 保存属性名称字符串 StringPool strings(mUTF8); Vector<uint32_t> resids;//保存属性名的资源ID号 // First collect just the strings for attribute names that have a // resource ID assigned to them. This ensures that the resource ID // array is compact, and makes it easier to deal with attribute names // in different namespaces (and thus with different resource IDs). //首先收集属性名字符串,这些字符串有一个资源ID指向它们. //这确保资源ID数组紧凑的,并且不同于命名空间的资源ID, //这使得处理属性名变得更简单 //注意:在这里实际上什么工作都没有做!!FUCK collect_resid_strings(&strings, &resids); // Next collect all remainibng strings. //真正的收集工作在这里才工作,上面什么工作都没做还递归遍历半天 collect_strings(&strings, &resids, stripComments, stripRawValues); //收集到的属性信息都保存在strings所指向的字符串资源池,现在为这些字符串资源池中的属性信息分配字符串块 sp<AaptFile> stringPool = strings.createStringBlock(); //接着创建一个ResXMLTree_header ResXMLTree_header header; memset(&header, 0, sizeof(header)); header.header.type = htods(RES_XML_TYPE); header.header.headerSize = htods(sizeof(header)); const size_t basePos = dest->getSize(); //在匿名AdaptFile对象dest中先写入一个header对象用于记录信息,接着 //将我们上面组织好的二进制xml字符串信息内存缓冲块中数据写入这个 //匿名AdaptFile对象dest中的缓冲区去 dest->writeData(&header, sizeof(header)); dest->writeData(stringPool->getData(), stringPool->getSize()); // If we have resource IDs, write them. //如果已经分配了资源ID则先写入一个记录资源ID信息的ResChunk_header头, //然后将资源ID的信息写入,但是这里尚未对任何资源分配资源ID. if (resids.size() > 0) { const size_t resIdsPos = dest->getSize(); const size_t resIdsSize = sizeof(ResChunk_header)+(sizeof(uint32_t)*resids.size()); ResChunk_header* idsHeader = (ResChunk_header*) (((const uint8_t*)dest->editData(resIdsPos+resIdsSize))+resIdsPos); idsHeader->type = htods(RES_XML_RESOURCE_MAP_TYPE); idsHeader->headerSize = htods(sizeof(*idsHeader)); idsHeader->size = htodl(resIdsSize); uint32_t* ids = (uint32_t*)(idsHeader+1); for (size_t i=0; i<resids.size(); i++) { *ids++ = htodl(resids[i]); } } //调用flatten_node函数继续组织收集到的xml文件中的信息 flatten_node(strings, dest, stripComments, stripRawValues); //最后,再写入一个ResXMLTree_header标记写入工作完成并记录上次写入这类 //header到刚刚创建的header之间写入的数据信息 void* data = dest->editData(); ResXMLTree_header* hd = (ResXMLTree_header*)(((uint8_t*)data)+basePos); hd->header.size = htodl(dest->getSize()-basePos); if (kPrintStringMetrics) { fprintf(stderr, "**** total xml size: %zu / %zu%% strings (in %s)\n", dest->getSize(), (stringPool->getSize()*100)/dest->getSize(), dest->getPath().string()); } return NO_ERROR;}
这里首先要了解一下概念
class XMLNode : public RefBase { ...... private: ...... String16 mElementName; //表示Xml元素标签。 Vector<sp<XMLNode> > mChildren; //表示Xml元素的子元素。 Vector<attribute_entry> mAttributes; //表示Xml元素的属性列表。 ...... String16 mChars; //表示Xml元素的文本内容。 ...... };
Xml文件解析完成之后,就可以得到一个用来描述根节点的XMLNode,接下来就可以通过这个根节点来完成其它的编译操作。
这一步实际上就是给每一个Xml元素的属性名称都赋予资源ID。例如,对于main.xml文件的根节点LinearLayout来说,就是要分别给它的属性名称“android:orientation”、“android:layout_width”、“android:layout_height”和“android:gravity”赋予一个资源ID。注意,上述这些属性都是在系统资源包里面定义的,因此,Android资源打包工具首先是要在系统资源包里面找到这些名称所对应的资源ID,然后才能赋给main.xml文件的根节点LinearLayout。对于系统资源包来说,“android:orientation”、“android:layout_width”、“android:layout_height”和“android:gravity”等这些属性名称是它定义的一系列Bag资源,在它被编译的时候,就已经分配好资源ID了,每一个Xml文件都是从根节点开始给属性名称赋予资源ID,然后再给递归给每一个子节点的属性名称赋予资源ID,直到每一个节点的属性名称都获得了资源ID为止。
前面提到,android:orientation是在系统资源包定义的一个Bag资源,这个Bag资源分配有资源ID,而且会指定有元数据,也就是它可以取哪些值。对于android:orientation来说,它的合法取值就为“horizontal”或者“vertical”。在系统资源包中,“horizontal”或者“vertical”也同样是一个Bag资源,它们的值分别被定义为0和1。Android资源打包工具是如何找到main.xml文件的根节点LinearLayout的属性android:orientation的字符串值“vertical”所对应的整数值1的呢?假设在上一步中,从系统资源包找到“android:orientation”的资源ID为0x010100c4,那么Android资源打包工具就会通过这个资源ID找到它的元数据,也就是两个名称分别为“horizontal”和“vertical”的bag,接着就根据字符串匹配到名称“vertical”的bag,最后就可以将这个bag的值1作为解析结果了。
注意,对于引用类型的属性值,要进行一些额外的处理。例如,对于main.xml文件的第一个Button节点的android:id属性值“@+id/button_start_in_process”,其中,“@”表示后面描述的属性是引用类型的,“+”表示如果该引用不存在,那么就新建一个,“id”表示引用的资源类型是id,“button_start_in_process”表示引用的名称。实际上,在”id”前面,还可以指定一个包名,例如,将main.xml文件的第一个Button节点的android:id属性值指定为“@+[package:]id/button_start_in_process” 。如果没有指定包名的话,那么就会默认在当前编译的包里面查找button_start_in_process这个引用。由于前面指有“+”符号,因此,如果在指定的包里面找不到button_start_in_process这个引用的话,那么就会在该包里面创建一个新的。无论button_start_in_process在指定的包里面原来就存在的,还是新建的,最终Android资源打包工具都是将它的资源ID作为解析结果。在我们这个情景中,在解析main.xml文件的两个Button节点的android:id属性值“@+id/button_start_in_process”和“@+id/button_start_in_new_process”时,当前正在编译的资源包没有包含有相应的引用的,因此,Android资源打包工具就会在当前正在编译的资源包里面增加两个类型为id的Entry,
此外,对于main.xml文件的两个Button节点的android:text属性值“@string/start_in_process”和“@string/start_in_new_process”,它们分别表示引用的是当前正在编译的资源包的名称分别为“start_in_process”和“start_in_new_process”的string资源。这两个string资源在前面的第五步操作中已经编译过了,因此,这里就可以直接获得它们的资源ID。
注意,一个资源项一旦创建之后,要获得它的资源ID是很容易的,因为它的Package ID、Type ID和Entry ID都是已知的。
那么我们如何处理压平过程呢?
收集有资源ID的属性的名称字符串
这一步除了收集那些具有资源ID的Xml元素属性的名称字符串之外,还会将对应的资源ID收集起来放在一个数组中。这里收集到的属性名称字符串保存在一个字符串资源池中,它们与收集到的资源ID数组是一一对应的。
collect_strings(&strings, &resids, stripComments, stripRawValues);
if (idx < 0) { //尚未将指定的属性名添加到字符串资源池中,如果add函数后面跟随 //着描述字符串属性的entry_style_span的Vector则将字符串属性一并 //加入, 并返回其在字符串资源池中位置 idx = outPool->add(attr.name); if (kIsDebug) { printf("Adding attr %s (resid 0x%08x) to pool: idx=%zd\n", String8(attr.name).string(), id, SSIZE(idx)); } //判断是否为属性名分配过资源ID if (id != 0) { // 确保属性名资源ID与属性名对应 while ((ssize_t)outResIds->size() <= idx) { outResIds->add(0); } // 替换原有资源ID outResIds->replaceAt(id, idx); }}
对于main.xml文件来说,具有资源ID的Xml元素属性的名称字符串有“orientation”、“layout_width”、“layout_height”、“gravity”、“id”和“text”,假设它们对应的资源ID分别为0x010100c4、0x010100f4、0x010100f5、0x010100af、0x010100d0和0x0101014f,那么最终得到的字符串资源池的前6个位置和资源ID数组的对应关系如图
收集其它字符串
收集完成之后为这些字符串资源池中的属性信息分配字符串块
//接着创建一个ResXMLTree_headerResXMLTree_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());
上面这块创建一个头部,但是并没有写入头部,只是将空的头部记录在了AaptFile中下面就要写入头部信息
写入Xml文件头
最终编译出来的Xml二进制文件是一系列的chunk组成的,每一个chunk都有一个头部,用来描述chunk的元信息。同时,整个Xml二进制文件又可以看成一块总的chunk,它有一个类型为ResXMLTree_header的头部。
将头信息和块信息写入AaptFile中
// If we have resource IDs, write them.//如果已经分配了资源ID则先写入一个记录资源ID信息的ResChunk_header头,//然后将资源ID的信息写入,但是这里尚未对任何资源分配资源ID.if (resids.size() > 0) { const size_t resIdsPos = dest->getSize(); const size_t resIdsSize = sizeof(ResChunk_header)+(sizeof(uint32_t)*resids.size()); ResChunk_header* idsHeader = (ResChunk_header*) (((const uint8_t*)dest->editData(resIdsPos+resIdsSize))+resIdsPos); idsHeader->type = htods(RES_XML_RESOURCE_MAP_TYPE); idsHeader->headerSize = htods(sizeof(*idsHeader)); idsHeader->size = htodl(resIdsSize); uint32_t* ids = (uint32_t*)(idsHeader+1); for (size_t i=0; i<resids.size(); i++) { *ids++ = htodl(resids[i]); }}
//调用flatten_node函数继续组织收集到的xml文件中的信息 flatten_node(strings, dest, stripComments, stripRawValues);
status_t XMLNode::flatten_node(const StringPool& strings, const sp<AaptFile>& dest, bool stripComments, bool stripRawValues) const{ ResXMLTree_node node; ResXMLTree_cdataExt cdataExt; ResXMLTree_namespaceExt namespaceExt; ResXMLTree_attrExt attrExt; const void* extData = NULL; size_t extSize = 0; ResXMLTree_attribute attr; bool writeCurrentNode = true; //NA和NC分别记录属性个数和子标签个数 const size_t NA = mAttributes.size(); const size_t NC = mChildren.size(); size_t i; LOG_ALWAYS_FATAL_IF(NA != mAttributeOrder.size(), "Attributes messed up!"); 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); //if (mComment.size() > 0) { // printf("Flattening comment: %s\n", String8(mComment).string()); //} } else { node.comment.index = htodl((uint32_t)-1); } if (type == TYPE_ELEMENT) { //设置该node类型 node.header.type = htods(RES_XML_START_ELEMENT_TYPE); //使用attrExt记录一个attribute额外的信息 extData = &attrExt; extSize = sizeof(attrExt); memset(&attrExt, 0, sizeof(attrExt)); if (mNamespaceUri.size() > 0) { attrExt.ns.index = htodl(strings.offsetForString(mNamespaceUri)); } else { attrExt.ns.index = htodl((uint32_t)-1); } attrExt.name.index = htodl(strings.offsetForString(mElementName)); attrExt.attributeStart = htods(sizeof(attrExt)); attrExt.attributeSize = htods(sizeof(attr)); attrExt.attributeCount = htods(NA); attrExt.idIndex = htods(0); attrExt.classIndex = htods(0); attrExt.styleIndex = htods(0); for (i=0; i<NA; i++) { ssize_t idx = mAttributeOrder.valueAt(i); const attribute_entry& ae = mAttributes.itemAt(idx); if (ae.ns.size() == 0) { if (ae.name == id16) { attrExt.idIndex = htods(i+1); } else if (ae.name == class16) { attrExt.classIndex = htods(i+1); } else if (ae.name == style16) { attrExt.styleIndex = htods(i+1); } } } } else if (type == TYPE_NAMESPACE) { if (mNamespaceUri == RESOURCES_TOOLS_NAMESPACE) { writeCurrentNode = false; } else { node.header.type = htods(RES_XML_START_NAMESPACE_TYPE); extData = &namespaceExt; extSize = sizeof(namespaceExt); memset(&namespaceExt, 0, sizeof(namespaceExt)); if (mNamespacePrefix.size() > 0) { namespaceExt.prefix.index = htodl(strings.offsetForString(mNamespacePrefix)); } else { namespaceExt.prefix.index = htodl((uint32_t)-1); } namespaceExt.prefix.index = htodl(strings.offsetForString(mNamespacePrefix)); namespaceExt.uri.index = htodl(strings.offsetForString(mNamespaceUri)); } LOG_ALWAYS_FATAL_IF(NA != 0, "Namespace nodes can't have attributes!"); } else if (type == TYPE_CDATA) { node.header.type = htods(RES_XML_CDATA_TYPE); extData = &cdataExt; extSize = sizeof(cdataExt); memset(&cdataExt, 0, sizeof(cdataExt)); cdataExt.data.index = htodl(strings.offsetForString(mChars)); cdataExt.typedData.size = htods(sizeof(cdataExt.typedData)); cdataExt.typedData.res0 = 0; cdataExt.typedData.dataType = mCharsValue.dataType; cdataExt.typedData.data = htodl(mCharsValue.data); LOG_ALWAYS_FATAL_IF(NA != 0, "CDATA nodes can't have attributes!"); } node.header.size = htodl(sizeof(node) + extSize + (sizeof(attr)*NA)); //初始化完成后将这个node和extData写入dest所记录的缓冲区中 if (writeCurrentNode) { dest->writeData(&node, sizeof(node)); if (extSize > 0) { dest->writeData(extData, extSize); } } /* 将一个标签的没一个属性创建一个ResXMLAttribute变量然后按照指定的顺序 ** 组织在dest所描述的缓冲区中, 注意:字符串信息被替换成其在StringPool ** 中成员变量中的位置值 */ for (i=0; i<NA; i++) { ssize_t idx = mAttributeOrder.valueAt(i); const attribute_entry& ae = mAttributes.itemAt(idx); if (ae.ns.size() > 0) { attr.ns.index = htodl(strings.offsetForString(ae.ns)); } else { attr.ns.index = htodl((uint32_t)-1); } attr.name.index = htodl(ae.namePoolIdx); if (!stripRawValues || ae.needStringValue()) { attr.rawValue.index = htodl(strings.offsetForString(ae.string)); } else { attr.rawValue.index = htodl((uint32_t)-1); } attr.typedValue.size = htods(sizeof(attr.typedValue)); if (ae.value.dataType == Res_value::TYPE_NULL || ae.value.dataType == Res_value::TYPE_STRING) { attr.typedValue.res0 = 0; attr.typedValue.dataType = Res_value::TYPE_STRING; attr.typedValue.data = htodl(strings.offsetForString(ae.string)); } else { attr.typedValue.res0 = 0; attr.typedValue.dataType = ae.value.dataType; attr.typedValue.data = htodl(ae.value.data); } dest->writeData(&attr, sizeof(attr)); } for (i=0; i<NC; i++) { status_t err = mChildren.itemAt(i)->flatten_node(strings, dest, stripComments, stripRawValues); if (err != NO_ERROR) { return err; } } //写入标记数据写入完成header if (type == TYPE_ELEMENT) { ResXMLTree_endElementExt endElementExt; memset(&endElementExt, 0, sizeof(endElementExt)); node.header.type = htods(RES_XML_END_ELEMENT_TYPE); node.header.size = htodl(sizeof(node)+sizeof(endElementExt)); node.lineNumber = htodl(getEndLineNumber()); node.comment.index = htodl((uint32_t)-1); endElementExt.ns.index = attrExt.ns.index; endElementExt.name.index = attrExt.name.index; dest->writeData(&node, sizeof(node)); dest->writeData(&endElementExt, sizeof(endElementExt)); } else if (type == TYPE_NAMESPACE) { if (writeCurrentNode) { node.header.type = htods(RES_XML_END_NAMESPACE_TYPE); node.lineNumber = htodl(getEndLineNumber()); node.comment.index = htodl((uint32_t)-1); node.header.size = htodl(sizeof(node)+extSize); dest->writeData(&node, sizeof(node)); dest->writeData(extData, extSize); } } return NO_ERROR;}
最后,再写入一个ResXMLTree_header标记写入工作完成并记录上次写入这类
//header到刚刚创建的header之间写入的数据信息
void* data = dest->editData();
ResXMLTree_header* hd = (ResXMLTree_header*)(((uint8_t*)data)+basePos);
hd->header.size = htodl(dest->getSize()-basePos);
经过上面的步骤ResXMLTree就是已经得到了所有资源的数据。到此为止解析AndroidManifest.xml文件的工作就完成了。
下面就是创建ResourceTable,创建ResourceTable的目的是将AaptAssets中解析的数据进行转移。
ResourceTable::PackageType packageType = ResourceTable::App;if (bundle->getBuildSharedLibrary()) { packageType = ResourceTable::SharedLibrary;} else if (bundle->getExtending()) { packageType = ResourceTable::System;} else if (!bundle->getFeatureOfPackage().isEmpty()) { packageType = ResourceTable::AppFeature;}
这段代码是根据bundle的信息判断是系统的packageType还是app的还是库的。
创建一个ResourceTable
ResourceTable table(bundle, String16(assets->getPackage()), packageType);
然后就是添加资源包比如android.jar那些
status_t ResourceTable::addIncludedResources(Bundle* bundle, const sp<AaptAssets>& assets){ //将当前包所依赖系统的android.jar包路径信息添加到assets的成员变量mIncludedAssets中 status_t err = assets->buildIncludedResources(bundle);//AaptAssets.cpp if (err != NO_ERROR) { return err; } mAssets = assets;// 将ResourceTable类的成员变量mAssets指向assets //getIncludedResources:获取一个ResTable对象用于描述当前APK所引用的android.jar包中的资源信息 //getIncludedResources在AaptAssets中 mTypeIdOffset = findLargestTypeIdForPackage(assets->getIncludedResources(), mAssetsPackage); const String8& featureAfter = bundle->getFeatureAfterPackage(); if (!featureAfter.isEmpty()) { AssetManager featureAssetManager; if (!featureAssetManager.addAssetPath(featureAfter, NULL)) { fprintf(stderr, "ERROR: Feature package '%s' not found.\n", featureAfter.string()); return UNKNOWN_ERROR; } const ResTable& featureTable = featureAssetManager.getResources(false); mTypeIdOffset = std::max(mTypeIdOffset, findLargestTypeIdForPackage(featureTable, mAssetsPackage)); } return NO_ERROR;}
首先获取android.jar的路径
status_t AaptAssets::buildIncludedResources(Bundle* bundle){ if (mHaveIncludedAssets) { return NO_ERROR; } // Add in all includes. // 首先获取我们使用-I选项所制定的android.jar包路径信息 const Vector<String8>& includes = bundle->getPackageIncludes(); const size_t packageIncludeCount = includes.size(); //将指定的所有android.jar的路径信息添加到当前对象成员变量 //mIncludedAssets中, mIncludedAssets的成员变量是一个AssetManager对象 for (size_t i = 0; i < packageIncludeCount; i++) { if (bundle->getVerbose()) { printf("Including resources from package: %s\n", includes[i].string()); } //最终调用AssetManager对象的addAssetPath将路径添加到其成员变量mAssetPaths中 if (!mIncludedAssets.addAssetPath(includes[i], NULL)) {//AssetManager.cpp fprintf(stderr, "ERROR: Asset package include '%s' not found.\n", includes[i].string()); return UNKNOWN_ERROR; } } const String8& featureOfBase = bundle->getFeatureOfPackage(); if (!featureOfBase.isEmpty()) { if (bundle->getVerbose()) { printf("Including base feature resources from package: %s\n", featureOfBase.string()); } if (!mIncludedAssets.addAssetPath(featureOfBase, NULL)) { fprintf(stderr, "ERROR: base feature package '%s' not found.\n", featureOfBase.string()); return UNKNOWN_ERROR; } } mHaveIncludedAssets = true; return NO_ERROR;}
然后添加路径下的信息
const ResTable& AaptAssets::getIncludedResources() const{ //mIncludedAssets是一个AssetManager类 return mIncludedAssets.getResources(false);}
路径在成员变量mIncludedAsse中保存。
const ResTable& AssetManager::getResources(bool required) const{ const ResTable* rt = getResTable(required);//getResTable函数是一个Singleton return *rt;}
const ResTable* AssetManager::getResTable(bool required) const{ ResTable* rt = mResources; if (rt) { return rt; } // Iterate through all asset packages, collecting resources from each. AutoMutex _l(mLock); if (mResources != NULL) { return mResources; } if (required) { LOG_FATAL_IF(mAssetPaths.size() == 0, "No assets added to AssetManager"); } //加载一些chache文件 if (mCacheMode != CACHE_OFF && !mCacheValid) { const_cast<AssetManager*>(this)->loadFileNameCacheLocked(); } mResources = new ResTable(); updateResourceParamsLocked();//更新mResources的参数 bool onlyEmptyResources = true; //逐个扫描所包含的android.jar文件路径 const size_t N = mAssetPaths.size(); for (size_t i=0; i<N; i++) { bool empty = appendPathToResTable(mAssetPaths.itemAt(i)); onlyEmptyResources = onlyEmptyResources && empty; } if (required && onlyEmptyResources) { ALOGW("Unable to find resources file resources.arsc"); delete mResources; mResources = NULL; } return mResources;}
挨个扫描所android.jar包中的文件进行如下操作:
bool AssetManager::appendPathToResTable(const asset_path& ap) const { // skip those ap's that correspond to system overlays if (ap.isSystemOverlay) { return true; } Asset* ass = NULL; ResTable* sharedRes = NULL; bool shared = true; bool onlyEmptyResources = true; MY_TRACE_BEGIN(ap.path.string()); Asset* idmap = openIdmapLocked(ap); size_t nextEntryIdx = mResources->getTableCount(); ALOGV("Looking for resource asset in '%s'\n", ap.path.string()); //判断ap所指向的文件类型 if (ap.type != kFileTypeDirectory) { // 如果ap所指向的文件类型不是目录文件 if (nextEntryIdx == 0) { // The first item is typically the framework resources, // which we want to avoid parsing every time. //为第一个指向的android.jar包创建一个对应的SharedZip对象,并将其成员变量mResourceTable返回 sharedRes = const_cast<AssetManager*>(this)-> mZipSet.getZipResourceTable(ap.path); //在这里返回NULL,表示尚未为该android.jar创建过一个ResTable对象 if (sharedRes != NULL) { // skip ahead the number of system overlay packages preloaded //返回对应android.jar包的SharedZip中的mResourceTableAsset对象 nextEntryIdx = sharedRes->getTableCount(); } } if (sharedRes == NULL) { ass = const_cast<AssetManager*>(this)-> mZipSet.getZipResourceTableAsset(ap.path); // 在这里返回的ass对象为NULL,表示尚未为该android.jar创建过一个Asset对象 if (ass == NULL) { ALOGV("loading resource table %s\n", ap.path.string()); //为该android.jar包创建一个Asset类对象,AssetManager::openNonAssetInPathLocked ass = const_cast<AssetManager*>(this)-> openNonAssetInPathLocked("resources.arsc", Asset::ACCESS_BUFFER, ap); if (ass != NULL && ass != kExcludedAsset) { /* 到这里我们就为一个android.jar包创建了一个Asset对象 ** 并将其保存到与之对应的SharedZip类对象的 ** mResourceTableAsset中 */ ass = const_cast<AssetManager*>(this)-> mZipSet.setZipResourceTableAsset(ap.path, ass); } } //为android.jar包创建一个与之对应的ResTable类对象,并将其保存 //到与之对应的SharedZip的成员变量mResourceTable中 if (nextEntryIdx == 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. ALOGV("Creating shared resources for %s", ap.path.string()); sharedRes = new ResTable(); sharedRes->add(ass, idmap, nextEntryIdx + 1, false);#ifdef HAVE_ANDROID_OS const char* data = getenv("ANDROID_DATA"); LOG_ALWAYS_FATAL_IF(data == NULL, "ANDROID_DATA not set"); String8 overlaysListPath(data); overlaysListPath.appendPath(kResourceCache); overlaysListPath.appendPath("overlays.list"); addSystemOverlays(overlaysListPath.string(), ap.path, sharedRes, nextEntryIdx);#endif sharedRes = const_cast<AssetManager*>(this)-> mZipSet.setZipResourceTable(ap.path, sharedRes); } } } else { //如果ap指向的是一个目录文件,则执行如下代码 ALOGV("loading resource table %s\n", ap.path.string()); ass = const_cast<AssetManager*>(this)-> openNonAssetInPathLocked("resources.arsc", Asset::ACCESS_BUFFER, ap); shared = false; } //完成了为一个android.jar包创建一个与之对应的Asset和ResTable对象之后 //则新建一个ResTable对象用于初始化mResources成员变量 if ((ass != NULL || sharedRes != NULL) && ass != kExcludedAsset) { ALOGV("Installing resource asset %p in to table %p\n", ass, mResources); if (sharedRes != NULL) { ALOGV("Copying existing resources for %s", ap.path.string()); mResources->add(sharedRes); } else { ALOGV("Parsing resources for %s", ap.path.string()); mResources->add(ass, idmap, nextEntryIdx + 1, !shared); } onlyEmptyResources = false; if (!shared) { delete ass; } } else { ALOGV("Installing empty resources in to table %p\n", mResources); mResources->addEmpty(nextEntryIdx + 1); } if (idmap != NULL) { delete idmap; } MY_TRACE_END(); return onlyEmptyResources;}
- 为第一个指向的android.jar创建一个SharedZip对象
- 为该android.jar包创建一个Asset类对象并将其保存到与之对应的SharedZip类对象的mResourceTableAsset中
此时mResources(ResTable)就与android.jar对应起来,将ResTable进行返回
Asset* AssetManager::openNonAssetInPathLocked(const char* fileName, AccessMode mode, const asset_path& ap){ Asset* pAsset = NULL; /* look at the filesystem on disk */ if (ap.type == kFileTypeDirectory) { String8 path(ap.path); path.appendPath(fileName); pAsset = openAssetFromFileLocked(path, mode); if (pAsset == NULL) { /* try again, this time with ".gz" */ path.append(".gz"); pAsset = openAssetFromFileLocked(path, mode); } if (pAsset != NULL) { //printf("FOUND NA '%s' on disk\n", fileName); pAsset->setAssetSource(path); } /* look inside the zip file */ } else { String8 path(fileName); /* check the appropriate Zip file */ // 返回与fileName对应的SharedZip对象的成员变量mZipFile ZipFileRO* pZip = getZipFileLocked(ap); if (pZip != NULL) { //printf("GOT zip, checking NA '%s'\n", (const char*) path); //寻找一个与android.jar包对应的ZipEntryRO项目条目 ZipEntryRO entry = pZip->findEntryByName(path.string()); if (entry != NULL) { //printf("FOUND NA in Zip file for %s\n", appName ? appName : kAppCommon); //使用一个ZIP压缩包的ZIPEntryRO项目条目创建一个新的 //Asset对象,如果这个条目没有被压缩,我们可能想要创建 //或者共享一片共享内存 pAsset = openAssetFromZipLocked(pZip, entry, mode, path); pZip->releaseEntry(entry); } } 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;}
返回与ZipFileRO* pZip = getZipFileLocked(ap);对应的SharedZip,这里传入的名字是resources.arsc
使用一个ZIP压缩包的ZIPEntryRO项目条目创建一个新的Asset对象返回
下面我们就从引用资源包开始详细说明过程
2. 添加被引用资源包
Android系统定义了一套通用资源,这些资源可以被应用程序引用。比如LinearLayout的android:orientation属性的值为“vertical”时,这个“vertical”实际上就是在系统资源包里面定义的一个值。
Android系统编译时生成out/target/common/obj/APPS/framework-res_intermediates/package-export.apk文件这个资源会在应用程序中引用到。
所以我们应用程序会引用到两种包:
- 被引用的系统资源包
- 当前正在编译的应用程序资源包
我们通过资源ID引用包里面的资源,资源ID是四个字节的无符号整数
- 最高字节表示Package ID
- 次高字节表示Type ID
- 最低两字节表示Entry ID
Package ID:
所有位于[0x01, 0x7f]之间的Package ID都是合法的,其中系统资源用0x01表示,应用资源用0x7f表示
Type ID:
Type ID指资源的类型ID,资源的类型有animator、anim、color、drawable、layout、menu、raw、string和xml等等若干种,每一种都会被赋予一个ID
Entry ID:
Entry ID是指每一个资源在其所属的资源类型中所出现的次序。注意,不同类型的资源的Entry ID有可能是相同的,但是由于它们的类型不同,我们仍然可以通过其资源ID来区别开来。
3. 收集资源文件
编译应用程序之前创建一个AaptAssets对象,这些需要编译的资源文件就保存在AaptAssets类的成员变量mRes中。
class AaptAssets : public AaptDir { ...... private: ...... KeyedVector<String8, sp<ResourceTypeSet> >* mRes; };
其中KeyedVector:
- key : 资源的类型名称
- value : 描述的是一个类型为AaptGroup的KeyedVector这个KeyedVector是以AaptGroup Name为Key的AaptGroup类描述的是一组同名的资源,类似于前面所描述的ConfigList,它有一个重要的成员变量mFiles,里面保存的就是一系列同名的资源文件。每一个资源文件都是用一个AaptFile对象来描述的,并且以一个AaptGroupEntry为Key保存在一个DefaultKeyedVector中
现在情景说明:
说明一:
类型为drawable的ResourceTypeSet只有一个AaptGroup,它的名称为icon.png。
这个AaptGroup包含了三个文件,分别是
- res/drawable-ldpi/icon.png
- res/drawable-mdpi/icon.png
- res/drawable-hdpi/icon.png
每一个文件都用一个AaptFile来描述,并且都对应有一个AaptGroupEntry。
一个AaptGroupEntry描述的都是不同的资源配置信息,即它们所描述的屏幕密度分别是ldpi、mdpi和hdpi
KeyedVector<String8, sp<ResourceTypeSet> >* mRes; ResourceTypeSet AaptGroup name = icon.png value : AaptFile : res/drawable-ldpi/icon.png AaptGroupEntry : ldpi,mdpi,hdpi AaptFile : res/drawable-ldpi/icon.png AaptGroupEntry : ldpi,mdpi,hdpi AaptFile : res/drawable-ldpi/icon.png AaptGroupEntry : ldpi,mdpi,hdpi
说明二:
类型为layout的ResourceTypeSet有两个AaptGroup它们的名称分别为main.xml和sub.xml
这两个AaptGroup分别只包含一个AaptFile分别是res/layout/main.xml和res/layout/sub.xml。这两个AaptFile同样是分别对应有一个AaptGroupEntry,不过这两个AaptGroupEntry描述的资源配置信息都是属于default的。
KeyedVector<String8, sp<ResourceTypeSet> >* mRes; ResourceTypeSet AaptGroup name = sub.xml value : AaptFile : res/layout/sub.xml AaptGroupEntry : default AaptGroup name : main.xml value : AaptFile : res/layout/main.xml AaptGroupEntry : default
说明三:
类型为values的ResourceTypeSet只有一个AaptGroup它的名称为strings.xml这个AaptGroup只包含了一个AaptFile,即res/values/strings.xml。这个AaptFile也对应有一个AaptGroupEntry,这个AaptGroupEntry描述的资源配置信息也是属于default的。
KeyedVector<String8, sp<ResourceTypeSet> >* mRes; ResourceTypeSet AaptGroup name = strings.xml value : AaptFile : res/values/strings.xml AaptGroupEntry : default
// resType -> leafName -> group KeyedVector<String8, sp<ResourceTypeSet> > *resources = new KeyedVector<String8, sp<ResourceTypeSet> >; //调用collect_files将前面收集到assets中的各类资源文件重新收集到resources中来 collect_files(assets, resources); //定义收集各类资源文件的容器 sp<ResourceTypeSet> drawables; sp<ResourceTypeSet> layouts; sp<ResourceTypeSet> anims; sp<ResourceTypeSet> animators; sp<ResourceTypeSet> interpolators; sp<ResourceTypeSet> transitions; sp<ResourceTypeSet> xmls; sp<ResourceTypeSet> raws; sp<ResourceTypeSet> colors; sp<ResourceTypeSet> menus; sp<ResourceTypeSet> mipmaps; //将保存到resources中的各类文件的Set保存到我们上述定义的Set中去 ASSIGN_IT(drawable); ASSIGN_IT(layout); ASSIGN_IT(anim); ASSIGN_IT(animator); ASSIGN_IT(interpolator); ASSIGN_IT(transition); ASSIGN_IT(xml); ASSIGN_IT(raw); ASSIGN_IT(color); ASSIGN_IT(menu); ASSIGN_IT(mipmap); //设置assets的资源为resources中保存的 assets->setResources(resources);
这段代码就是收集到assets中的各类资源文件重新收集到resources中来
//按类别将保存在assets中的资源文件进行归类处理static void collect_files(const sp<AaptDir>& dir, KeyedVector<String8, sp<ResourceTypeSet> >* resources){ const DefaultKeyedVector<String8, sp<AaptGroup> >& groups = dir->getFiles(); int N = groups.size();//获取资源文件夹下的资源文件个数,逐个扫描 for (int i=0; i<N; i++) { String8 leafName = groups.keyAt(i); const sp<AaptGroup>& group = groups.valueAt(i); const DefaultKeyedVector<AaptGroupEntry, sp<AaptFile> >& files = group->getFiles(); if (files.size() == 0) { continue; } //按照资源文件的类型新建一个对应的ResourceTypeSet容器保存各类文件, 最终保存到resource中去 String8 resType = files.valueAt(0)->getResourceType(); ssize_t index = resources->indexOfKey(resType); //如果index小于0表示还未为resType所描述的类型资源文件创建一个对应的 // Set对象,于是为其新建一个 if (index < 0) { sp<ResourceTypeSet> set = new ResourceTypeSet(); if (kIsDebug) { printf("Creating new resource type set for leaf %s with group %s (%p)\n", leafName.string(), group->getPath().string(), group.get()); } set->add(leafName, group); resources->add(resType, set); } else { sp<ResourceTypeSet> set = resources->valueAt(index); index = set->indexOfKey(leafName); if (index < 0) { if (kIsDebug) { printf("Adding to resource type set for leaf %s group %s (%p)\n", leafName.string(), group->getPath().string(), group.get()); } set->add(leafName, group); } else { sp<AaptGroup> existingGroup = set->valueAt(index); if (kIsDebug) { printf("Extending to resource type set for leaf %s group %s (%p)\n", leafName.string(), group->getPath().string(), group.get()); } for (size_t j=0; j<files.size(); j++) { if (kIsDebug) { printf("Adding file %s in group %s resType %s\n", files.valueAt(j)->getSourceFile().string(), files.keyAt(j).toDirName(String8()).string(), resType.string()); } existingGroup->addFile(files.valueAt(j)); } } } }}
下面使用到了宏函数
#define ASSIGN_IT(n) \ do { \ ssize_t index = resources->indexOfKey(String8(#n)); \ if (index >= 0) { \ n ## s = resources->valueAt(index); \ } \ } while (0)
比如替换后:
#define ASSIGN_IT(drawable) do { ssize_t index = resources->indexOfKey(String8(drawable)); if (index >= 0) { drawables = resources->valueAt(index); } } while (0)
其中drawables是我们sp drawables;这里定义的。
也就是将resources中的资源进行分类到每个容器里面
下面就是单独处理了。
4. 将收集到的资源增加到资源表
对应前面那些资源全部收集到了AaptAssets对象中,此时需要将这些资源同时增加到一个资源表中去,就是增加到前面所创建的一个ResourceTable对象中去。最后根据ResourceTable来生成资源索引表,即生成resources.arsc文件。
这一步不包括values,这个需要经过编译之后才添加到资源表中。
根据前面在ResourceTable类中,每一个资源都是分别用一个Entry对象来描述的这些Entry分别按照下面这些进行保存
- Pacakge
- Type
- ConfigList
比如:
PacakgeName = “shy.luo.activity”
那么在ResourceTable类的成员变量mPackages和mOrderedPackages中就会分别保存有一个名称为“shy.luo.activity”的Package.
class ResourceTable : public ResTable::Accessor { ...... private: ...... DefaultKeyedVector<String16, sp<Package> > mPackages; Vector<sp<Package> > mOrderedPackages; ...... };
其中Package分别包含有drawable和layout两种类型的资源,每一种类型使用一个Type对象来描述。
Package Type : drawable ConfigList name : icon.png value: Entry : res/drawable-ldip/icon.png ConfigDescription : ldpi Entry : res/drawable-mdip/icon.png ConfigDescription : mdpi Entry : res/drawable-hdip/icon.png ConfigDescription : hdpi Type:layout ConfigList name : main.xml value: Entry : res/layout/main.xml ConfigDescription : default ConfigList name : sub.xml value: Entry : res/layout/main.xml ConfigDescription : default
5.编译values类资源
类型为values的资源描述的都是一些简单的值,如数组、颜色、尺寸、字符串和样式值等,这些资源是在编译的过程中进行收集的。
比如这个文件:
<?xml version="1.0" encoding="utf-8"?> <resources> <string name="app_name">Activity</string> <string name="sub_activity">Sub Activity</string> <string name="start_in_process">Start sub-activity in process</string> <string name="start_in_new_process">Start sub-activity in new process</string> <string name="finish">Finish activity</string> </resources>
这个文件经过编译之后,资源表就多了一个名称为string的Type,这个Type有五个ConfigList。这五个ConfigList的名称分别为“app_name”、“sub_activity”、“start_in_process”、“start_in_new_process”和“finish”,每一个ConfigList又分别含有一个Entry。
Package
Type : string
ConfigList
name : app_name.png
value:
Entry : Activity
ConfigDescription : default
ConfigList
name : sub_activity.png
value:
Entry : Sub Activity
ConfigDescription : default
ConfigList
name : start_in_process.png
value:
Entry : Start sub-activity in process
ConfigDescription : default
ConfigList
name : start_in_new_process.png
value:
Entry : Start sub-activity in new process
ConfigDescription : default
ConfigList
name : finish.png
value:
Entry : Finish activity
ConfigDescription : default
static status_t makeFileResources(Bundle* bundle, const sp<AaptAssets>& assets, ResourceTable* table, const sp<ResourceTypeSet>& set, const char* resType){ String8 type8(resType); String16 type16(resType); bool hasErrors = false; ResourceDirIterator it(set, String8(resType)); ssize_t res; //环逐个取出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 ? STATUST(UNKNOWN_ERROR) : NO_ERROR;}
//将一个名称为name,类型为type,所属包为package的资源文件组织成一个Entry具体实现如下所示:status_t ResourceTable::addEntry(const SourcePos& sourcePos, const String16& package, const String16& type, const String16& name, const String16& value, const Vector<StringPool::entry_style_span>* style, const ResTable_config* params, const bool doSetIndex, const int32_t format, const bool overwrite){ uint32_t rid = mAssets->getIncludedResources() .identifierForName(name.string(), name.size(), type.string(), type.size(), package.string(), package.size()); if (rid != 0) { sourcePos.error("Resource entry %s/%s is already defined in package %s.", String8(type).string(), String8(name).string(), String8(package).string()); return UNKNOWN_ERROR; } /* Entry类用来描述一个资源项,它的重要成员变量的含义如下所示: --mName:表示资源名称。 --mItem:表示资源数据,用一个Item对象来描述。 Item类用来描述一个资源项数据,它的重要成员变量的含义如下所示: --value:表示资源项的原始值,它是一个字符串。 --parsedValue:表示资源项原始值经过解析后得到的结构化的资源值,使用一个Res_Value对象来描述。例如,一个整数类型的资源项的原始值为“12345”,经过解析后,就得到一个大小为12345的整数类型的资源项。*/ /* 首先调用getEntry函数获取一个描述名称为name的资源文件的Entry类对象 ** 其所在包为package, 其类型使用type描述 */ sp<Entry> e = getEntry(package, type, name, sourcePos, overwrite, params, doSetIndex); if (e == NULL) { return UNKNOWN_ERROR; } //获取了一个描述名称为name资源文件的Entry对象之后,把其相关信息组织成一个Item对象然后添加到Entry中 status_t err = e->setItem(sourcePos, value, style, format, overwrite); if (err == NO_ERROR) { mNumLocal++; } return err;}
sp<ResourceTable::Entry> ResourceTable::getEntry(const String16& package, const String16& type, const String16& name, const SourcePos& sourcePos, bool overlay, const ResTable_config* config, bool doSetIndex){ /* Type类用来描述一个资源类型,它的重要成员变量的含义如下所示: --mName:表示资源类型名称。 --mConfigs:表示包含的资源配置项列表,每一个配置项列表都包含了一系列同名的资源,使用一个ConfigList来描述。例如,假设有main.xml和sub.xml两个layout类型的资源,那么main.xml和sub.xml都分别对应有一个ConfigList。 --mOrderedConfigs:和mConfigs一样,也是表示包含的资源配置项,不过它们是以Entry ID从小到大的顺序保存在一个Vector里面的,而mConfigs是以Entry Name来Key的DefaultKeyedVector。 --mUniqueConfigs:表示包含的不同资源配置信息的个数。我们可以将mConfigs和mOrderedConfigs看作是按照名称的不同来划分资源项,而将mUniqueConfigs看作是按照配置信息的不同来划分资源项。 */ /* ConfigList用来描述一个资源配置项列表,它的重要成员变量的含义如下所示: --mName:表示资源项名称,也称为Entry Name。 --mEntries: 表示包含的资源项,每一个资源项都用一个Entry对象来描述,并且以一个对应的ConfigDescription为Key保存在一个 DefaultKeyedVector中。例如,假设有一个名称为icon.png的drawable资源,有三种不同的配置,分别是ldpi、mdpi 和hdpi,那么以icon.png为名称的资源就对应有三个项。*/ //调用getType函数来获取一个Type对象t用来描述资源类型 sp<Type> t = getType(package, type, sourcePos, doSetIndex); if (t == NULL) { return NULL; } return t->getEntry(name, sourcePos, config, doSetIndex, overlay, mBundle->getAutoAddOverlay());}
将数据转移到了table之后,就开始分配id
6.给Bag资源分配ID
先分配bag id
err = table.assignResourceIds();
status_t ResourceTable::assignResourceIds(){ const size_t N = mOrderedPackages.size(); size_t pi; status_t firstError = NO_ERROR; // First generate all bag attributes and assign indices. // 首先取出当前编译应用程序资源所依赖的的包个数,并分别为包中的资源分配资源ID, //在这里这两个包分别是: android.jar 和 com.example.helloworldactivity. for (pi=0; pi<N; pi++) { sp<Package> p = mOrderedPackages.itemAt(pi); if (p == NULL || p->getTypes().size() == 0) { // Empty, skip! continue; } if (mPackageType == System) { p->movePrivateAttrs(); } // This has no sense for packages being built as AppFeature (aka with a non-zero offset). /* 如果为Package对象p中的Type设定了public属性id,那么调用 ** applyPublicTypeOrder函数将p中成员变量mOrderedTypes中的Type按照id ** 由小到大的顺序排列 ** ** 例如, 我们在values/public.xml中如下定义: ** <?xml version="1.0" encoding="utf-8"?> ** <resources> <public type="string" name="show" id="0x7f030001" /> <public type="style" name="AppTheme" id="0x7f040001" /> ** </resources> ** 那么type为string和style的在mOrderedTypes中的位置是在2,3 ** 位置处,就是将3和4进行减1操作而,第0,1两个位置保留. */ status_t err = p->applyPublicTypeOrder(); if (err != NO_ERROR && firstError == NO_ERROR) { firstError = err; } // Generate attributes... //按照Type-->ConfigList-->Entry的顺序依次将所有的Entry调用函数 generateAttributes生成一个属性信息 const size_t N = p->getOrderedTypes().size(); size_t ti; for (ti=0; ti<N; ti++) { sp<Type> t = p->getOrderedTypes().itemAt(ti); if (t == NULL) { continue; } const size_t N = t->getOrderedConfigs().size(); for (size_t ci=0; ci<N; ci++) { sp<ConfigList> c = t->getOrderedConfigs().itemAt(ci); if (c == NULL) { continue; } const size_t N = c->getEntries().size(); for (size_t ei=0; ei<N; ei++) { sp<Entry> e = c->getEntries().valueAt(ei); if (e == NULL) { continue; } //generateAttributes函数用于将保存到mBag中的信息取出,如果 //其是一个id属性,并且在table中没有对应的bag或者entry则 //创建一个entry添加进table中 status_t err = e->generateAttributes(this, p->getName()); if (err != NO_ERROR && firstError == NO_ERROR) { firstError = err; } } } } uint32_t typeIdOffset = 0; if (mPackageType == AppFeature && p->getName() == mAssetsPackage) { typeIdOffset = mTypeIdOffset; } const SourcePos unknown(String8("????"), 0); sp<Type> attr = p->getType(String16("attr"), unknown); // Assign indices... const size_t typeCount = p->getOrderedTypes().size(); for (size_t ti = 0; ti < typeCount; ti++) { sp<Type> t = p->getOrderedTypes().itemAt(ti); if (t == NULL) { continue; } // 类似的,我们如果为某类Type对象指定了public的IDS信息,我们就同上 // 将Type中的ConfigList对象按照id值从小到大排列在mOrderedConfigs中去 err = t->applyPublicEntryOrder(); if (err != NO_ERROR && firstError == NO_ERROR) { firstError = err; } const size_t N = t->getOrderedConfigs().size(); t->setIndex(ti + 1 + typeIdOffset); LOG_ALWAYS_FATAL_IF(ti == 0 && attr != t, "First type is not attr!"); for (size_t ei=0; ei<N; ei++) { sp<ConfigList> c = t->getOrderedConfigs().itemAt(ei); if (c == NULL) { continue; } c->setEntryIndex(ei); } } // Assign resource IDs to keys in bags... //分配bags id for (size_t ti = 0; ti < typeCount; ti++) { sp<Type> t = p->getOrderedTypes().itemAt(ti); if (t == NULL) { continue; } const size_t N = t->getOrderedConfigs().size(); for (size_t ci=0; ci<N; ci++) { sp<ConfigList> c = t->getOrderedConfigs().itemAt(ci); if (c == NULL) { continue; } //printf("Ordered config #%d: %p\n", ci, c.get()); const size_t N = c->getEntries().size(); for (size_t ei=0; ei<N; ei++) { sp<Entry> e = c->getEntries().valueAt(ei); if (e == NULL) { continue; } // 逐个取出每一个资源属性调用Entry的assignResourceIds为其分配属性ID //对应ResourceTable::Entry::assignResourceIds status_t err = e->assignResourceIds(this, p->getName()); if (err != NO_ERROR && firstError == NO_ERROR) { firstError = err; } } } } } return firstError;}
其他的一些资源引用比如style,array,bag等
这些资源会给自己定义一些专用的值,这些带有专用值的资源就统称为Bag资源。例如,Android系统提供的android:orientation属性的取值范围为{“vertical”、“horizontal”},就相当于是定义了vertical和horizontal两个Bag。
在继续编译其它非values的资源之前,我们需要给之前收集到的Bag资源分配资源ID,因为它们可能会被其它非values类资源引用到。假设在res/values目录下,有一个attrs.xml文件,它的内容如下所示:
<?xml version="1.0" encoding="utf-8"?> <resources> <attr name="custom_orientation"> <enum name="custom_vertical" value="0" /> <enum name="custom_horizontal" value="1" /> </attr> </resources>
7.编译Xml资源文件
前面的6部都是为了后面第七步做准备
比如:
<?xml version="1.0" encoding="utf-8"?><LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="fill_parent" android:layout_height="fill_parent" android:gravity="center"> <Button android:id="@+id/button_start_in_process" android:layout_width="wrap_content" android:layout_height="wrap_content" android:gravity="center" android:text="@string/start_in_process" > </Button> <Button android:id="@+id/button_start_in_new_process" android:layout_width="wrap_content" android:layout_height="wrap_content" android:gravity="center" android:text="@string/start_in_new_process" > </Button></LinearLayout>
过程如下:
(1).解析xml
解析Xml文件是为了可以在内存中用一系列树形结构的XMLNode来表示它。
class XMLNode : public RefBase { ...... private: ...... String16 mElementName;//表示Xml元素标签 Vector<sp<XMLNode> > mChildren;//表示Xml元素的文本内容 Vector<attribute_entry> mAttributes;//表示Xml元素的属性列表 ...... String16 mChars;//表示Xml元素的子元素 ...... };
xml解析完了之后就可以得到一个用来描述根节点的XMLNode,下来就是用这个节点完成其他编译工作。
(2).赋予属性名称资源ID
然后分配layout等等那些ID&解析
status_t compileXmlFile(const Bundle* bundle, const sp<AaptAssets>& assets, const String16& resourceName, const sp<XMLNode>& root, const sp<AaptFile>& target, ResourceTable* table, int options){ // 首先去除空格 if ((options&XML_COMPILE_STRIP_WHITESPACE) != 0) { root->removeWhitespace(true, NULL); } else if ((options&XML_COMPILE_COMPACT_WHITESPACE) != 0) { root->removeWhitespace(false, NULL); } ///* 设定编码格式 */ if ((options&XML_COMPILE_UTF8) != 0) { root->setUTF8(true); } bool hasErrors = false; /* 如果尚未对解析到root数据结构中的属性分配资源ID则调用 ** root的成员函数分配资源id, 给属性分配资源ID原理类似于上 ** 述给Bag资源分配ID */ if ((options&XML_COMPILE_ASSIGN_ATTRIBUTE_IDS) != 0) { status_t err = root->assignResourceIds(assets, table); if (err != NO_ERROR) { hasErrors = true; } } /* parseValues函数用于获取当前资源属性所在的行号等信息将其保存到table ** 中,并将字符串资源信息替换成对应的类型值 */ status_t err = root->parseValues(assets, table); if (err != NO_ERROR) { hasErrors = true; } if (hasErrors) { return UNKNOWN_ERROR; } if (table->modifyForCompat(bundle, resourceName, target, root) != NO_ERROR) { return UNKNOWN_ERROR; } if (kIsDebug) { printf("Input XML Resource:\n"); root->print(); } err = root->flatten(target, (options&XML_COMPILE_STRIP_COMMENTS) != 0, (options&XML_COMPILE_STRIP_RAW_VALUES) != 0); if (err != NO_ERROR) { return err; } if (kIsDebug) { printf("Output XML Resource:\n"); ResXMLTree tree; tree.setTo(target->getData(), target->getSize()); printXMLBlock(&tree); } target->setCompressionMethod(ZipEntry::kCompressDeflated); return err;}
其中核心代码是:
cpp
status_t err = root->assignResourceIds(assets, table);
给每一个xml元素属性名赋值资源ID,例如对于main.xml根节点LinearLayout它的属性名就是android:orientation
、android:layout_width
、android:layout_height
和android:gravity
每一个属性名都对应在系统资源包里面有对应的Bag资源,在编译好的时候就已经分配了ID
每一个Xml文件都是从根节点开始给属性名称赋予资源ID,然后再给递归给每一个子节点的属性名称赋予资源ID,直到每一个节点的属性名称都获得了资源ID为止
(3).解析属性值
前面给属性名赋予资源ID,对于LinearLayout来说现在已经有了android:orientation资源ID,但是没有值,这一步就是将值进行解析,在系统资源包中,“horizontal”或者“vertical”也同样是一个Bag资源,它们的值分别被定义为0和1。
现在综合说下2,3两个步骤。假设在上一步中,从系统资源包找到“android:orientation”的资源ID为0x010100c4,那么Android资源打包工具就会通过这个资源ID找到它的元数据,也就是两个名称分别为“horizontal”和“vertical”的bag,接着就根据字符串匹配到名称“vertical”的bag,最后就可以将这个bag的值1作为解析结果了。
对于引用类型特殊处理
对于main.xml文件的第一个Button节点的android:id属性值“@+id/button_start_in_process”,其中,“@”表示后面描述的属性是引用类型的,“+”表示如果该引用不存在,那么就新建一个,“id”表示引用的资源类型是id,“button_start_in_process”表示引用的名称。实际上,在”id”前面,还可以指定一个包名,例如,将main.xml文件的第一个Button节点的android:id属性值指定为“@+[package:]id/button_start_in_process” 。如果没有指定包名的话,那么就会默认在当前编译的包里面查找button_start_in_process这个引用。由于前面指有“+”符号,因此,如果在指定的包里面找不到button_start_in_process这个引用的话,那么就会在该包里面创建一个新的。无论button_start_in_process在指定的包里面原来就存在的,还是新建的,最终Android资源打包工具都是将它的资源ID作为解析结果。
在我们这个情景中,在解析main.xml文件的两个Button节点的android:id属性值“@+id/button_start_in_process”和“@+id/button_start_in_new_process”时,当前正在编译的资源包没有包含有相应的引用的,因此,Android资源打包工具就会在当前正在编译的资源包里面增加两个类型为id的Entry
对于对于main.xml文件的两个Button节点的android:text属性值“@string/start_in_process”和“@string/start_in_new_process”它们分别表示引用的是当前正在编译的资源包的名称分别为“start_in_process”和“start_in_new_process”的string资源。这两个string资源在前面的第五步操作中已经编译过了,因此,这里就可以直接获得它们的资源ID。
一个资源项一旦创建之后,要获得它的资源ID是很容易的,因为它的Package ID、Type ID和Entry ID都是已知的。
(4).压平Xml文件
下面就是将xml文件从文本格式转化成二进制
err = root->flatten(target, (options&XML_COMPILE_STRIP_COMMENTS) != 0, (options&XML_COMPILE_STRIP_RAW_VALUES) != 0);
Step 1. 收集有资源ID的属性的名称字符串:
这一步除了收集那些具有资源ID的Xml元素属性的名称字符串之外,还会将对应的资源ID收集起来放在一个数组中。这里收集到的属性名称字符串保存在一个字符串资源池中,它们与收集到的资源ID数组是一一对应的。
比如:
对于main.xml对应关系生成的结果就如下:
Step 2. 收集其它字符串
这一步不会重复收集第一步的那些字符串
对于main.xml来说:
- “android”是android命名空间前缀
- http://schemas.android.com/apk/res/android”是android命名空间uri
- “LinearLayout”是LinearLayout元素的标签
- “Button”是Button元素的标签
最后编译出来的结果是一个个chunk,每一个chunk都有一个头部用来描述chunk的元信息。同时,整个Xml二进制文件又可以看成一块总的chunk,它有一个类型为ResXMLTree_header的头部
ResXMLTree_header定义在文件frameworks/base/include/utils/ResourceTypes.h中
/** * Header that appears at the front of every data chunk in a resource. */struct ResChunk_header{ // Type identifier for this chunk. The meaning of this value depends // on the containing chunk. uint16_t type; // Size of the chunk header (in bytes). Adding this value to // the address of the chunk allows you to find its associated data // (if any). uint16_t headerSize; // Total size of this chunk (in bytes). This is the chunkSize plus // the size of any data associated with the chunk. Adding this value // to the chunk allows you to completely skip its contents (including // any child chunks). If this value is the same as chunkSize, there is // no data associated with the chunk. uint32_t size;};/** * XML tree header. This appears at the front of an XML tree, * describing its content. It is followed by a flat array of * ResXMLTree_node structures; the hierarchy of the XML document * is described by the occurrance of RES_XML_START_ELEMENT_TYPE * and corresponding RES_XML_END_ELEMENT_TYPE nodes in the array. */struct ResXMLTree_header{ struct ResChunk_header header;};
对于ResXMLTree_header头部来说,内嵌在它里面的ResChunk_header的成员变量的值如下所示:
- type:等于RES_XML_TYPE,描述这是一个Xml文件头部。
- headerSize:等于sizeof(ResXMLTree_header),表示头部的大小。
- size:等于整个二进制Xml文件的大小,包括头部headerSize的大小。
Step 4. 写入字符串资源池
对于main.xml来说,依次写入的字符串为
“orientation”、“layout_width”、“layout_height”、“gravity”、“id”、”text”、”android”、“http://schemas.android.com/apk/res/android”、“LinearLayout”和“Button”。之所以要严格按照这个顺序来写入,是因为接下来要将前面Step 1收集到的资源ID数组也写入到二进制格式的Xml文件中去,并且要保持这个资源ID数组与字符串资源池前六个字符串的对应关系。
写入的字符串池chunk同样也是具有一个头部的,这个头部的类型为ResStringPool_header
frameworks/base/include/utils/ResourceTypes.h
/** * Definition for a pool of strings. The data of this chunk is an * array of uint32_t providing indices into the pool, relative to * stringsStart. At stringsStart are all of the UTF-16 strings * concatenated together; each starts with a uint16_t of the string's * length and each ends with a 0x0000 terminator. If a string is > * 32767 characters, the high bit of the length is set meaning to take * those 15 bits as a high word and it will be followed by another * uint16_t containing the low word. * * If styleCount is not zero, then immediately following the array of * uint32_t indices into the string table is another array of indices * into a style table starting at stylesStart. Each entry in the * style table is an array of ResStringPool_span structures. */struct ResStringPool_header{ struct ResChunk_header header; // Number of strings in this pool (number of uint32_t indices that follow // in the data). uint32_t stringCount;//等于字符串的数量。 // Number of style span arrays in the pool (number of uint32_t indices // follow the string indices). uint32_t styleCount;//等于字符串的样式的数量。 // Flags. enum { // If set, the string index is sorted by the string values (based // on strcmp16()). SORTED_FLAG = 1<<0, // String pool is encoded in UTF-8 UTF8_FLAG = 1<<8 }; ////等于0、SORTED_FLAG、UTF8_FLAG或者它们的组合值,用来描述字符串资源串的属性。 //例如,SORTED_FLAG位等于1表示字符串是经过排序的,而UTF8_FLAG位等于1表示字符串是使用UTF8编码的,否则就是UTF16编码的。 uint32_t flags; // Index from header of the string data. uint32_t stringsStart;//等于字符串内容块相对于其头部的距离。 // Index from header of the style data. uint32_t stylesStart;//等于字符串样式块相对于其头部的距离。};
对应内嵌在ResStringPool_header中ResChunk_header对应值如下:
- type:等于RES_STRING_POOL_TYPE,描述这是一个字符串资源池。
- headerSize:等于sizeof(ResStringPool_header),表示头部的大小。
- size:整个字符串chunk的大小,包括头部headerSize的大小。
8.生成资源符号
从前面的操作可以知道,所有收集到的资源项都按照类型来保存在一个资源表中,即保存在一个ResourceTable对象。因此,Android资源打包工具aapt只要遍历每一个Package里面的每一个Type,然后取出每一个Entry的名称,并且根据这个Entry在自己的Type里面出现的次序来计算得到它的资源ID,那么就可以生成一个资源符号了,这个资源符号由名称以及资源ID所组成。
比如对于strings.xml文件中名称为“start_in_process”的Entry来说,它是一个类型为string的资源项,假设它出现的次序为第3,那么它的资源符号就等于R.string.start_in_process,对应的资源ID就为0x7f050002,其中,高字节0x7f表示Package ID,次高字节0x05表示string的Type ID,而低两字节0x02就表示“start_in_process”是第三个出现的字符串。
9.生成资源符号
经过前面步骤生成一个资源列表如下:
现在有了上面的表之后aapt就可以生成资源索引表resources.arsc
(1). 收集类型字符串
在上面的例子中有四种类型资源分别是drawable、layout、string和id,于是对应的类型字符串就为“drawable”、“layout”、“string”和“id”。
这些字符串是按照Package来收集的也就是说,当前被编译的应用程序资源有几个Package,就有几组对应的类型字符串,每一个组类型字符串都保存在其所属的Package中。
(2). 收集资源项名称字符串
在第9步的时候一共有12个资源项每一个资源项的名称分别为
“icon”、“icon”、“icon”、“main”、“sub”、“app_name”、“sub_activity”、“start_in_process”、“start_in_new_process”、“finish”、“button_start_in_process”
和“button_start_in_new_process”
于是收集到的资源项名称字符串就为
“icon”、“main”、“sub”、“app_name”、“sub_activity”、“start_in_process”、“start_in_new_process”、“finish”、“button_start_in_process”和“button_start_in_new_process”。
注意,这些字符串同样是按Package来收集的,也就是说,当前被编译的应用程序资源有几个Package,就有几组对应的资源项名称字符串,每一个组资源项名称字符串都保存在其所属的Package中。
(3). 收集资源项值字符串
一共有12个资源项,但是只有10项是具有值字符串的,它们分别是
“res/drawable-ldpi/icon.png”、“res/drawable-mdpi/icon.png”、“res/drawable-hdpi/icon.png”、“res/layout/main.xml”、“res/layout/sub.xml”、“Activity”、“Sub Activity”、“Start sub-activity in process”、“Start sub-activity in new process”,“Finish activity”。
注意,这些字符串不是按Package来收集的,也就是说,当前所有参与编译的Package的资源项值字符串都会被统一收集在一起。
(4). 生成Package数据块
参与编译的每一个Package的资源项元信息都写在一块独立的数据上,这个数据块使用一个类型为ResTable_package的头部来描述。
Step 1. 写入Package资源项元信息数据块头部
struct ResTable_package{ struct ResChunk_header header; uint32_t id;//等于Package ID char16_t name[128];//等于Package Name uint32_t typeStrings;//等于类型字符串资源池相对头部的偏移位置 uint32_t lastPublicType;//等于最后一个导出的Public类型字符串在类型字符串资源池中的索引,目前这个值设置为类型字符串资源池的大小。 uint32_t keyStrings;//等于资源项名称字符串相对头部的偏移位置 uint32_t lastPublicKey;//等于最后一个导出的Public资源项名称字符串在资源项名称字符串资源池中的索引,目前这个值设置为资源项名称字符串资源池的大小。};
嵌入在ResTable_package内部的ResChunk_header的各个成员变量的取值如下所示:
- type:等于RES_TABLE_PACKAGE_TYPE,表示这是一个Package资源项元信息数据块头部。
- headerSize:等于sizeof(ResTable_package),表示头部大小。
- size:等于sizeof(ResTable_package) + 类型字符串资源池大小 + 资源项名称字符串资源池大小 + 类型规范数据块大小 + 数据项信息数据块大小
Step 2. 写入类型字符串资源池
在前面的第1个操作中,我们已经将每一个Package用到的类型字符串收集起来了,因此,这里就可以直接将它们写入到Package资源项元信息数据块头部后面的那个数据块去。
Step 3. 写入资源项名称字符串资源池
在前面的第2个操作中,我们已经将每一个Package用到的资源项名称字符串收集起来了,这里就可以直接将它们写入到类型字符串资源池后面的那个数据块去。
Step 4. 写入类型规范数据块
与密度有关
Step 5. 写入类型资源项数据块
10. 编译AndroidManifest.xml文件
经过前面的九个步骤之后,应用程序的所有资源项就编译完成了,这时候就开始将应用程序的配置文件AndroidManifest.xml也编译成二进制格式的Xml文件。之所以要在应用程序的所有资源项都编译完成之后,再编译应用程序的配置文件,是因为后者可能会引用到前者。
应用程序配置文件AndroidManifest.xml的编译过程与其它的Xml资源文件的编译过程是一样的,可以参考前面的第七步。注意,应用程序配置文件AndroidManifest.xml编译完成之后,Android资源打包工具appt还会验证它的完整性和正确性,例如,验证AndroidManifest.xml的根节点mainfest必须定义有android:package属性。
// 取出AndroidManifest.xml文件 const sp<AaptFile> manifestFile(androidManifestFile->getFiles().valueAt(0)); String8 manifestPath(manifestFile->getPrintableSource()); // Generate final compiled manifest file. //清空manifestFile所指向的AndroidManfiest.xml的信息,然后重新解析 manifestFile->clearData(); sp<XMLNode> manifestTree = XMLNode::parse(manifestFile); if (manifestTree == NULL) { return UNKNOWN_ERROR; } //检测是否AndroidManifest.xml中是否有overlay资源,如果有就将现有资源替换 err = massageManifest(bundle, manifestTree); if (err < NO_ERROR) { return err; } //编译AndroidManifest.xml文件 err = compileXmlFile(bundle, assets, String16(), manifestTree, manifestFile, &table); if (err < NO_ERROR) { return err; }
11. 生成R.java文件
在前面的第八步中,我们已经将所有的资源项及其所对应的资源ID都收集起来了,因此,这里只要将直接将它们写入到指定的R.java文件去就可以了。例如,假设分配给类型为layout的资源项main和sub的ID为0x7f030000和0x7f030001,那么在R.java文件,就会分别有两个以main和sub为名称的常量
public final class R { ...... public static final class layout { public static final int main=0x7f030000; public static final int sub=0x7f030001; } ...... }
12. 打包APK文件
- apk资源打包过程分析
- APK打包过程分析
- apk打包过程图解
- apk打包过程
- APK打包过程
- Android apk打包过程
- APK 打包过程
- apk的打包过程
- apk打包过程
- Android Apk 文件反编译和重新打包的过程分析
- Android Apk 文件反编译和重新打包的过程分析
- Android应用程序资源的编译和打包过程分析
- Android应用程序资源的编译和打包过程分析
- Android应用程序资源的编译和打包过程分析
- Android应用程序资源的编译和打包过程分析
- Android应用程序资源的编译和打包过程分析
- Android_应用程序资源的编译和打包过程分析
- Android应用程序资源的编译和打包过程分析
- 清华大学冯珺:基于强化学习的关系抽取和文本分类 | 实录·PhD Talk
- 解密Airbnb的定价算法
- 学习笔记 什么是中断向量以及配置中断服务函数的原理
- 算法描述---伪代码
- Oracle查询某段日期内某个时间段的数据
- apk资源打包过程分析
- Markdown之表格的处理
- 和为S的连续正数序列
- JS获取URL中参数值的4种方法
- ServerSocketChannel
- vs平台如何查看汇编代码
- PaperWeekly 第52期 | 更别致的词向量模型:Simpler GloVe
- 4.6 对象的组合
- 利用Python进行数据分析(9) pandas基础: 汇总统计和计算