简述
在APP打包过程中 , 会通过AAPT编译资源以及生成R.java文件. 一般我们使用以下命令来调用aapt
命令 :
aapt package -f -M AndroidManifest.xml -S xxx -I TargetSdkPath -G /build/proguard/proguard.txt --auto-add-overlay

优化方向
- 尽量只让AAPT处理.9.png图片 , 其他图片使用Webp格式代替
- 原因 : 因为AAPT会预处理PNG图片 , 会读取图片结构信息 , 从而进行
压缩
(Compress)
- 原因 : 因为AAPT会预处理PNG图片 , 会读取图片结构信息 , 从而进行
- 修改
preProcessImages
中 ,WorkQueue
的最大线程数- 原因 : 系统自带的AAPT中 , 处理PNG图片的最大线程数是
4
个 , 可以根据打包机器修改成8
个或者10
个
- 原因 : 系统自带的AAPT中 , 处理PNG图片的最大线程数是
- 去掉生成Proguard文件的步骤 , 直接在
proguard.pro
中写- 原因 : 由于
aapt
会根据AndroidManifest.xml中的字段去生成Proguard
文件 , 需要读取该文件
- 原因 : 由于
- 尽量不要配置
SplitConfiguration
- 原因 : 因为会读取配置信息 , 并且进行分割
流程
- 在
frameworks/base/tools/aapt/Main.cpp
中
int main(int argc, char* const argv[])
{
char *prog = argv[0];
Bundle bundle;
bool wantUsage = false;
int result = 1; // pessimistically assume an error.
int tolerance = 0;
// 设置Bundle的压缩方法
bundle.setCompressionMethod(ZipEntry::kCompressDeflated);
...
// 检查命令行参数的第二个参数 , 按上面命令即`package`
...
if (argv[1][0] == 'r')
bundle.setCommand(kCommandRemove);
else if (argv[1][0] == 'p')
// 执行打包命令
bundle.setCommand(kCommandPackage);
else if (argv[1][0] == 'c')
bundle.setCommand(kCommandCrunch);
...
}
// 参数总数与参数index调整
argc -= 2;
argv += 2;
// 开始遍历参数列表以 - 开头的 , 也就是根据传入的命令行参数填充Bundle对象
while (argc && argv[0][0] == '-') {
const char* cp = argv[0] +1;
// 例如cp为 -I , -S , -G , -M
while (*cp != '\0') {
switch (*cp) {
...
case 'c':
// 更新参数index信息
argc--;
argv++;
...
// 添加配置参数
bundle.addConfigurations(argv[0]);
break;
case 'f':
// 强制打包
bundle.setForce(true);
break;
case 'g':
argc--;
argv++;
if (!argc) {
fprintf(stderr, "ERROR: No argument supplied for '-g' option\n");
wantUsage = true;
goto bail;
}
tolerance = atoi(argv[0]);
bundle.setGrayscaleTolerance(tolerance);
printf("%s: Images with deviation <= %d will be forced to grayscale.\n", prog, tolerance);
break;
...
#if 0
case 'p':
bundle.setPseudolocalize(true);
break;
#endif
...
case 'A':
argc--;
argv++;
...
convertPath(argv[0]);
bundle.addAssetSourceDir(argv[0]);
break;
case 'G':
argc--;
argv++;
...
convertPath(argv[0]);
bundle.setProguardFile(argv[0]);
break;
...
case 'I':
argc--;
argv++;
if (!argc) {
fprintf(stderr, "ERROR: No argument supplied for '-I' option\n");
wantUsage = true;
goto bail;
}
convertPath(argv[0]);
bundle.addPackageInclude(argv[0]);
break;
...
case 'M':
argc--;
argv++;
if (!argc) {
fprintf(stderr, "ERROR: No argument supplied for '-M' option\n");
wantUsage = true;
goto bail;
}
convertPath(argv[0]);
bundle.setAndroidManifestFile(argv[0]);
break;
...
case 'S':
argc--;
argv++;
if (!argc) {
fprintf(stderr, "ERROR: No argument supplied for '-S' option\n");
wantUsage = true;
goto bail;
}
convertPath(argv[0]);
bundle.addResourceSourceDir(argv[0]);
break;
}
}
// 设置参数的信息
bundle.setFileSpec(argv, argc);
// 开始执行命令 , 并且传递bundle参数 ,
result = handleCommand(&bundle);
bail:
if (wantUsage) {
usage();
result = 2;
}
return result;
}
int handleCommand(Bundle* bundle)
{
// 根据Bundle Command执行对应的函数 , 本次例子即为doPackage函数调用
switch (bundle->getCommand()) {
case kCommandVersion: return doVersion(bundle);
case kCommandList: return doList(bundle);
case kCommandDump: return doDump(bundle);
case kCommandAdd: return doAdd(bundle);
case kCommandRemove: return doRemove(bundle);
case kCommandPackage: return doPackage(bundle);
case kCommandCrunch: return doCrunch(bundle);
case kCommandSingleCrunch: return doSingleCrunch(bundle);
case kCommandDaemon: return runInDaemonMode(bundle);
default:
fprintf(stderr, "%s: requested command not yet supported\n", gProgName);
return 1;
}
}
- 在
framework/base/tools/aapt/Command.cpp
中 , 主要调用doPackage
函数
int doPackage(Bundle* bundle)
{
const char* outputAPKFile;
int retVal = 1;
status_t err;
sp<AaptAssets> assets;
int N;
FILE* fp;
String8 dependencyFile;
sp<ApkBuilder> builder;
...
// 获取命令行参数个数
N = bundle->getFileSpecCount();
// 如果命令行参数小于1 , 资源路径目录总数为0 , Jar文件为0 , AndroidManifest路径为空 ,
// Asset路径目录总数为空的话 , 认为不需要编译 , 直接返回错误
if (N < 1 && bundle->getResourceSourceDirs().size() == 0 && bundle->getJarFiles().size() == 0
&& bundle->getAndroidManifestFile() == NULL && bundle->getAssetSourceDirs().size() == 0) {
fprintf(stderr, "ERROR: no input files\n");
goto bail;
}
// 获取输出的APK文件
outputAPKFile = bundle->getOutputAPKFile();
// 创建AaptAsset对象用来加载Asset文件
assets = new AaptAssets();
...
// 解析Bundle中的AndroidManifest.xml路径、Resouce路径,并添加到Assets对象中
err = assets->slurpFromArgs(bundle);
if (err < 0) {
goto bail;
}
...
// 创建ApkBuilder对象,用于APK到编译
builder = new ApkBuilder(configFilter);
...
// 开始编译资源与AndroidManifest.xml文件
if (bundle->getResourceSourceDirs().size() || bundle->getAndroidManifestFile()) {
err = buildResources(bundle, assets, builder);
if (err != 0) {
goto bail;
}
}
...
// 更新Java中到符号
assets->applyJavaSymbols();
if (SourcePos::hasErrors()) {
goto bail;
}
// 如果编译时有依赖的话
if (bundle->getGenDependencies()) {
// 判断是否要输出APK文件
if (outputAPKFile) {
dependencyFile = String8(outputAPKFile);
// 添加.d的扩展符号
dependencyFile.append(".d");
} else {
// 如果是生成R.java文件 , e.g. gen/com/foo/app/R.java.d
dependencyFile = String8(bundle->getRClassDir());
dependencyFile.appendPath("R.java.d");
}
}
// Write out R.java constants
if (!assets->havePrivateSymbols()) {
// 如果通过--custom-pacakge来生成对应包名的资源的话
if (bundle->getCustomPackage() == NULL) {
// 写入R.java文件e.g. gen/com/foo/app/R.java
err = writeResourceSymbols(bundle, assets, assets->getPackage(), true,
bundle->getBuildSharedLibrary() || bundle->getBuildAppAsSharedLibrary());
} else {
// 如果自定义了包名 , 就使用自定义包名
const String8 customPkg(bundle->getCustomPackage());
err = writeResourceSymbols(bundle, assets, customPkg, true,
bundle->getBuildSharedLibrary() || bundle->getBuildAppAsSharedLibrary());
}
...
// 如果有额外的lib需要编译 , 则开始编译Lib的R文件 , 例如e.g. gen/com/foo/app/lib/R.java
if (bundle->getExtraPackages() != NULL) {
// Split on colon
String8 libs(bundle->getExtraPackages());
char* packageString = strtok(libs.lockBuffer(libs.length()), ":");
while (packageString != NULL) {
// Write the R.java file out with the correct package name
err = writeResourceSymbols(bundle, assets, String8(packageString), true,
bundle->getBuildSharedLibrary() || bundle->getBuildAppAsSharedLibrary());
if (err < 0) {
goto bail;
}
packageString = strtok(NULL, ":");
}
libs.unlockBuffer();
}
} else {
// 生成package对应的R.java文件
err = writeResourceSymbols(bundle, assets, assets->getPackage(), false, false);
if (err < 0) {
goto bail;
}
err = writeResourceSymbols(bundle, assets, assets->getSymbolsPrivatePackage(), true, false);
if (err < 0) {
goto bail;
}
}
// 输出Proguard文件
err = writeProguardFile(bundle, assets);
if (err < 0) {
goto bail;
}
// 输出主Dex的Proguard文件
err = writeMainDexProguardFile(bundle, assets);
if (err < 0) {
goto bail;
}
// Write the apk
if (outputAPKFile) {
// 如果是要生成APK的话 , 就会将资源添加到ApkBuilder中
err = addResourcesToBuilder(assets, builder);
if (err != NO_ERROR) {
goto bail;
}
const Vector<sp<ApkSplit> >& splits = builder->getSplits();
const size_t numSplits = splits.size();
for (size_t i = 0; i < numSplits; i++) {
const sp<ApkSplit>& split = splits[i];
String8 outputPath = buildApkName(String8(outputAPKFile), split);
// 生成APK
err = writeAPK(bundle, outputPath, split);
if (err != NO_ERROR) {
fprintf(stderr, "ERROR: packaging of '%s' failed\n", outputPath.string());
goto bail;
}
}
}
...
return retVal;
}
buildResources
status_t pbuildResources(Bundle* bundle, const sp<AaptAssets>& assets, sp<ApkBuilder>& builder)
{
...
// 首先根据AndroidManifest.xml解析出Package、min-sdkversion等参数
status_t err = parsePackage(bundle, assets, androidManifestFile);
if (err != NO_ERROR) {
return err;
}
...
// 根据命令行参数判断当前资源类型 , 一般都是APP
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对象
ResourceTable table(bundle, String16(assets->getPackage()), packageType);
// 将资源路径添加到asset对象中
err = table.addIncludedResources(bundle, assets);
...
// 使用标准的XML格式
int xmlFlags = XML_COMPILE_STANDARD_RESOURCE;
// 如果没有特别指定UTF16的话 , 就使用UTF8编码
if (!bundle->getUTF16StringsOption()) {
xmlFlags |= XML_COMPILE_UTF8;
}
// 第一步 : 开始收集所有资源的信息
// resType -> leafName -> group
KeyedVector<String8, sp<ResourceTypeSet> > *resources =
new KeyedVector<String8, sp<ResourceTypeSet> >;
// 收集资源路径
collect_files(assets, resources);
// 创建各种类型资源的Set集合
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;
sp<ResourceTypeSet> fonts;
// 设置资源路径
assets->setResources(resources);
// now go through any resource overlays and collect their files
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();
}
// 会将子目录下的文件也都添加到drawables中
// 例如res/drawable/drawable/drawable/string.xml文件也会添加到&xmls集合中
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, &fonts, "font") ||
!applyFileOverlay(bundle, assets, &mipmaps, "mipmap")) {
return UNKNOWN_ERROR;
}
bool hasErrors = false;
// 如果drawables集合中存在资源文件
if (drawables != NULL) {
if (bundle->getOutputAPKFile() != NULL) {
// 如果要输出APK文件的话
err = preProcessImages(bundle, assets, drawables, "drawable");
}
if (err == NO_ERROR) {
err = makeFileResources(bundle, assets, &table, drawables, "drawable");
if (err != NO_ERROR) {
hasErrors = true;
}
} else {
hasErrors = true;
}
}
if (mipmaps != NULL) {
if (bundle->getOutputAPKFile() != NULL) {
// 开始预处理PNG图片 , 内部会启动一个WorkQueue , 最多会有4个线程并行
// 执行PreProcessImageWorkUnit任务 , 只处理后缀为png的图片 ,
// 内部会读取PNG图片 , 处理.9.png等
// 具体代码在Image.cpp中
err = preProcessImages(bundle, assets, mipmaps, "mipmap");
}
if (err == NO_ERROR) {
// 将资源名生成资源ID(即Rid) , 并且存入ResourceTable中 , 用于后续生成R文件
err = makeFileResources(bundle, assets, &table, mipmaps, "mipmap");
if (err != NO_ERROR) {
hasErrors = true;
}
} else {
hasErrors = true;
}
}
// fronts、layouts、anims、animators、transitions、interpolators、xmls、raws
// 文件夹中的资源也会同时加入ResourceTable中 , 也是调用makeFileResources方法
...
// 开始编译资源
current = assets;
while(current.get()) {
KeyedVector<String8, sp<ResourceTypeSet> > *resources =
current->getResources();
// 获取Resouce名称对应的ResouceTypeSet对象 , 也就是上面的各种资源Set
// 例如drawable、fronts、layouts等等
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) {
const sp<AaptFile>& file = it.getFile();
// 得到资源文件 , 调用compileResourceFile编译文件
// 其中会解析资源的XML , 包括attr、color、bool、integer等字段
// 包括declare_styleable、attr、item、string16、drawable、color等
// 然后解析每一个block中的id 、type、name等字段保存到Res_value中
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;
}
}
if (hasErrors) {
return UNKNOWN_ERROR;
}
// 开始分配资源ID并且开始初始化资源Table
if (table.hasResources()) {
// 在这地方分配所有资源对应的ID
err = table.assignResourceIds();
if (err < NO_ERROR) {
return err;
}
}
// 最后开始编译XML文件 , 因为有些资源会引用其他的资源
if (layouts != NULL) {
ResourceDirIterator it(layouts, String8("layout"));
while ((err=it.next()) == NO_ERROR) {
String8 src = it.getFile()->getPrintableSource();
// 开始编译XML文件
err = compileXmlFile(bundle, assets, String16(it.getBaseName()),
it.getFile(), &table, xmlFlags);
// Only verify IDs if there was no error and the file is non-empty.
if (err == NO_ERROR && it.getFile()->hasData()) {
ResXMLTree block;
block.setTo(it.getFile()->getData(), it.getFile()->getSize(), true);
// 检查ID
checkForIds(src, block);
} else {
hasErrors = true;
}
}
}
if (anims != NULL) {
ResourceDirIterator it(anims, String8("anim"));
while ((err=it.next()) == NO_ERROR) {
// 编译anim的xml文件
err = compileXmlFile(bundle, assets, String16(it.getBaseName()),
it.getFile(), &table, xmlFlags);
if (err != NO_ERROR) {
hasErrors = true;
}
}
}
// 后续会同上根据compileXmlFile来编译animator、interpolator、transition、xml、drawable、mipmap、color、menu、font的XML文件
...
// Now compile any generated resources.
// 在XML编译过后 , 会开始编译资源
std::queue<CompileResourceWorkItem>& workQueue = table.getWorkQueue();
while (!workQueue.empty()) {
CompileResourceWorkItem& workItem = workQueue.front();
int xmlCompilationFlags = xmlFlags | XML_COMPILE_PARSE_VALUES
| XML_COMPILE_ASSIGN_ATTRIBUTE_IDS;
if (!workItem.needsCompiling) {
xmlCompilationFlags &= ~XML_COMPILE_ASSIGN_ATTRIBUTE_IDS;
xmlCompilationFlags &= ~XML_COMPILE_PARSE_VALUES;
}
err = compileXmlFile(bundle, assets, workItem.resourceName, workItem.xmlRoot,
workItem.file, &table, xmlCompilationFlags);
if (err == NO_ERROR && workItem.file->hasData()) {
assets->addResource(workItem.resPath.getPathLeaf(),
workItem.resPath,
workItem.file,
workItem.file->getResourceType());
} else {
hasErrors = true;
}
workQueue.pop();
}
...
// 获取AndroidManifest文件
const sp<AaptFile> manifestFile(androidManifestFile->getFiles().valueAt(0));
String8 manifestPath(manifestFile->getPrintableSource());
// 开始生成最终的编译过后的AndroidManifest.xml文件
manifestFile->clearData();
// 开始解析AndroidManfest文件
sp<XMLNode> manifestTree = XMLNode::parse(manifestFile);
if (manifestTree == NULL) {
return UNKNOWN_ERROR;
}
// 解析AndroidManifest.xml文件 , 将minSdkVersion、application、versionCode等参数设置到bundle中
err = massageManifest(bundle, &table, manifestTree);
if (err < NO_ERROR) {
return err;
}
// 编译manifest文件
err = compileXmlFile(bundle, assets, String16(), manifestTree, manifestFile, &table);
if (table.modifyForCompat(bundle) != NO_ERROR) {
return UNKNOWN_ERROR;
}
// 开始生成最终的Resouce Table
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;
}
Vector<sp<ApkSplit> >& splits = builder->getSplits();
const size_t numSplits = splits.size();
// 根据分割配置生成resouces.arsc文件 , 尽量不要配置splite configuration
...
// 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.
// 开始执行一个基础的AndroidManifest校验
sp<AaptFile> outManifestFile = new AaptFile(manifestFile->getSourceFile(),
manifestFile->getGroupEntry(),
manifestFile->getResourceType());
// 编译AndroidManifest文件 , 并且将相关的字段保存到outManifestFile对象中
err = compileXmlFile(bundle, assets, String16(), manifestFile,
outManifestFile, &table, XML_COMPILE_STANDARD_RESOURCE & ~XML_COMPILE_STRIP_COMMENTS);
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;
// 开始遍历AndroidManifest的block
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;
}
// 判断当前block是否为manifest
if (strcmp16(block.getElementName(&len), manifest16.string()) == 0) {
// 校验package属性
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);
}
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 (resFile != NULL) {
err = assets->addIncludedResources(resFile);
if (err < NO_ERROR) {
fprintf(stderr, "ERROR: Unable to parse generated resources, aborting.\n");
return err;
}
}
return err;
}
-
writeResourceSymbols
在处理完PNG图片并且编译完资源的XML文件后 , 开始写入R.java
status_t writeResourceSymbols(Bundle* bundle, const sp<AaptAssets>& assets,
const String8& package, bool includePrivate, bool emitCallback)
{
const char* textSymbolsDest = bundle->getOutputTextSymbols();
String8 R("R");
const size_t N = assets->getSymbols().size();
for (size_t i=0; i<N; i++) {
sp<AaptSymbols> symbols = assets->getSymbols().valueAt(i);
String8 className(assets->getSymbols().keyAt(i));
String8 dest(bundle->getRClassDir());
dest.appendPath(className);
dest.append(".java");
FILE* fp = fopen(dest.string(), "w+");
...
// 将symbols写入R.java文件中
status_t err = writeSymbolClass(fp, assets, includePrivate, symbols,
className, 0, bundle->getNonConstantId(), emitCallback);
if (textSymbolsDest != NULL && R == className) {
String8 textDest(textSymbolsDest);
textDest.appendPath(className);
textDest.append(".txt");
FILE* fp = fopen(textDest.string(), "w+");
if (fp == NULL) {
fprintf(stderr, "ERROR: Unable to open text symbol file %s: %s\n",
textDest.string(), strerror(errno));
return UNKNOWN_ERROR;
}
if (bundle->getVerbose()) {
printf(" Writing text symbols for class %s.\n", className.string());
}
status_t err = writeTextSymbolClass(fp, assets, includePrivate, symbols,
className);
fclose(fp);
if (err != NO_ERROR) {
return err;
}
}
...
return NO_ERROR;
}
-
writeProguardFile
写入Proguard文件
status_t writeProguardFile(Bundle* bundle, const sp<AaptAssets>& assets)
{
status_t err = -1;
if (!bundle->getProguardFile()) {
return NO_ERROR;
}
ProguardKeepSet keep;
// 写入AndroidManifest的Proguard
err = writeProguardForAndroidManifest(&keep, assets, false);
if (err < 0) {
return err;
}
// 写入layout的proguard文件
err = writeProguardForLayouts(&keep, assets);
if (err < 0) {
return err;
}
return writeProguardSpec(bundle->getProguardFile(), keep, err);
}
- 在
Package.cpp
中存在writeAPK
函数 , 主要用来执行打包 , 这个APK中只有资源并不会有Dex文件.
status_t writeAPK(Bundle* bundle, const String8& outputFile, const sp<OutputSet>& outputSet)
{
ZipFile* zip = NULL;
int count;
// 得到输出文件的类型
FileType fileType = getFileType(outputFile.string());
status_t status;
zip = new ZipFile;
// 打开Zip文件
status = zip->open(outputFile.string(), ZipFile::kOpenReadWrite | ZipFile::kOpenCreate);
// 处理Asset文件 , 也就是通过zip->add将文件添加到Zip中
count = processAssets(bundle, zip, outputSet);
...
// 处理Jar文件
count = processJarFiles(bundle, zip);
// 中间还会删除一些Zip中的Entry
...
// 将zip的缓冲区都输出到文件中
result = zip->flush();
if (result != NO_ERROR) {
fprintf(stderr, "ERROR: Zip flush failed, archive may be hosed\n");
goto bail;
}
...
return result;
}
网友评论