From fab50875b98e8274ac8ee44b38ba42521bbbf1f9 Mon Sep 17 00:00:00 2001 From: Adam Lesinski Date: Wed, 16 Apr 2014 14:40:42 -0700 Subject: Add support for building split APKs Build multiple APKs, each containing a disjoint subset of configurations. These can then be loaded into the device AssetManager and should operate as if they were never split. Use the idea of building multiple sets of files, where each set represents an APK. An ApkBuilder can place files in a set based on its configuration, but you can actually add directly to a set, in the case of the resources.arsc and generated AndroidManifest.xml for splits. Change-Id: Ic65d3f0ac1bbd290185695b9971d425c85ab1de3 --- tools/aapt/Resource.cpp | 150 ++++++++++++++++++++++++++---------------------- 1 file changed, 82 insertions(+), 68 deletions(-) (limited to 'tools/aapt/Resource.cpp') diff --git a/tools/aapt/Resource.cpp b/tools/aapt/Resource.cpp index d0581f687d16..e599643bfdb4 100644 --- a/tools/aapt/Resource.cpp +++ b/tools/aapt/Resource.cpp @@ -179,24 +179,6 @@ bool isValidResourceType(const String8& type) || type == "color" || type == "menu" || type == "mipmap"; } -static sp getResourceFile(const sp& assets, bool makeIfNecessary=true) -{ - sp group = assets->getFiles().valueFor(String8("resources.arsc")); - sp file; - if (group != NULL) { - file = group->getFiles().valueFor(AaptGroupEntry()); - if (file != NULL) { - return file; - } - } - - if (!makeIfNecessary) { - return NULL; - } - return assets->addFile(String8("resources.arsc"), AaptGroupEntry(), String8(), - NULL, String8()); -} - static status_t parsePackage(Bundle* bundle, const sp& assets, const sp& grp) { @@ -359,23 +341,6 @@ static status_t preProcessImages(const Bundle* bundle, const sp& ass return (hasErrors || (res < NO_ERROR)) ? UNKNOWN_ERROR : NO_ERROR; } -status_t postProcessImages(const sp& assets, - ResourceTable* table, - const sp& set) -{ - ResourceDirIterator it(set, String8("drawable")); - bool hasErrors = false; - ssize_t res; - while ((res=it.next()) == NO_ERROR) { - res = postProcessImage(assets, table, it.getFile()); - if (res < NO_ERROR) { - hasErrors = true; - } - } - - return (hasErrors || (res < NO_ERROR)) ? UNKNOWN_ERROR : NO_ERROR; -} - static void collect_files(const sp& dir, KeyedVector >* resources) { @@ -906,7 +871,38 @@ status_t updatePreProcessedCache(Bundle* bundle) return 0; } -status_t buildResources(Bundle* bundle, const sp& assets) +status_t generateAndroidManifestForSplit(const String16& package, const sp& split, + sp& outFile) { + const String8 filename("AndroidManifest.xml"); + const String16 androidPrefix("android"); + const String16 androidNSUri("http://schemas.android.com/apk/res/android"); + sp root = XMLNode::newNamespace(filename, androidPrefix, androidNSUri); + + // Build the tag + sp manifest = XMLNode::newElement(filename, String16(), String16("manifest")); + + // Add the 'package' attribute which is set to the original package name. + manifest->addAttribute(String16(), String16("package"), package); + + // Add the 'split' attribute which describes the configurations included. + String8 splitName("config_"); + splitName.append(split->getDirectorySafeName()); + manifest->addAttribute(String16(), String16("split"), String16(splitName)); + + // Build an empty tag (required). + sp app = XMLNode::newElement(filename, String16(), String16("application")); + manifest->addChild(app); + root->addChild(manifest); + + status_t err = root->flatten(outFile, true, true); + if (err != NO_ERROR) { + return err; + } + outFile->setCompressionMethod(ZipEntry::kCompressDeflated); + return NO_ERROR; +} + +status_t buildResources(Bundle* bundle, const sp& assets, sp& builder) { // First, look for a package file to parse. This is required to // be able to generate the resource information. @@ -1122,12 +1118,6 @@ status_t buildResources(Bundle* bundle, const sp& assets) // -------------------------------------------------------------------- if (table.hasResources()) { - sp resFile(getResourceFile(assets)); - if (resFile == NULL) { - fprintf(stderr, "Error: unable to generate entry for resource data\n"); - return UNKNOWN_ERROR; - } - err = table.assignResourceIds(); if (err < NO_ERROR) { return err; @@ -1235,16 +1225,24 @@ status_t buildResources(Bundle* bundle, const sp& assets) } if (drawables != NULL) { - err = postProcessImages(assets, &table, drawables); - if (err != NO_ERROR) { + ResourceDirIterator it(drawables, String8("drawable")); + while ((err=it.next()) == NO_ERROR) { + err = postProcessImage(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(assets, it.getFile(), &table, xmlFlags); + err = compileXmlFile(assets, it.getFile(), &table, xmlFlags); if (err != NO_ERROR) { hasErrors = true; } @@ -1261,12 +1259,13 @@ status_t buildResources(Bundle* bundle, const sp& assets) while ((err=it.next()) == NO_ERROR) { String8 src = it.getFile()->getPrintableSource(); err = compileXmlFile(assets, it.getFile(), &table, xmlFlags); - if (err != NO_ERROR) { + if (err == NO_ERROR) { + ResXMLTree block; + block.setTo(it.getFile()->getData(), it.getFile()->getSize(), true); + checkForIds(src, block); + } else { hasErrors = true; } - ResXMLTree block; - block.setTo(it.getFile()->getData(), it.getFile()->getSize(), true); - checkForIds(src, block); } if (err < NO_ERROR) { @@ -1319,15 +1318,35 @@ status_t buildResources(Bundle* bundle, const sp& assets) return err; } - resFile = getResourceFile(assets); - if (resFile == NULL) { - fprintf(stderr, "Error: unable to generate entry for resource data\n"); - return UNKNOWN_ERROR; - } + Vector >& splits = builder->getSplits(); + const size_t numSplits = splits.size(); + for (size_t i = 0; i < numSplits; i++) { + sp& split = splits.editItemAt(i); + sp flattenedTable = new AaptFile(String8("resources.arsc"), + AaptGroupEntry(), String8()); + err = table.flatten(bundle, split->getResourceFilter(), flattenedTable); + 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); - err = table.flatten(bundle, resFile); - if (err < NO_ERROR) { - return err; + if (split->isBase()) { + resFile = flattenedTable; + finalResTable.add(flattenedTable->getData(), flattenedTable->getSize()); + } else { + sp generatedManifest = new AaptFile(String8("AndroidManifest.xml"), + AaptGroupEntry(), String8()); + err = generateAndroidManifestForSplit(String16(assets->getPackage()), split, + generatedManifest); + 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()) { @@ -1343,18 +1362,13 @@ status_t buildResources(Bundle* bundle, const sp& assets) table.writePublicDefinitions(String16(assets->getPackage()), fp); fclose(fp); } - - // Read resources back in, - finalResTable.add(resFile->getData(), resFile->getSize()); - -#if 0 - NOISY( - printf("Generated resources:\n"); - finalResTable.print(); - ) -#endif + + 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 -- cgit v1.2.3-59-g8ed1b