As mentioned in the previous article, in addition to staking, Matrix also selects whether to perform resource optimization based on user configuration to remove unnecessary resource files.

Arsc file format

One of the functions of Matrix resource optimization is pruning resources.arsc. Before analyzing this function, let’s have a brief understanding of the file format of ARSC.

Let’s start with a few concepts from the ARSC file:

  1. Chunk refers to a data Block. Table, Package, String Block, and Type in the following sections are all chunks, containing information such as file header, Type, size, alignment, and padding
  2. Resource Table, one ARSC file corresponds to one Resource Table
  3. Package is used to describe a Package, one Table corresponds to multiple packages, and packageID is the highest eight bits of resource resID, generally speaking, android is 1(0x01). Common ones such as com.0700. mm will be 127(0x7f), and the rest will start at 2, which can also be specified in aapt.
  4. String Block, a Table has a global String resource pool, a Package has a String resource pool for storage resource types, and a String resource pool for storage resource names
  5. Resource Type refers to the Resource Type, such as attr, drawable, layout, ID, color, anim, etc. A Package corresponds to multiple types
  6. Config: describes the dimensions of resources, such as vertical and horizontal screens, screen density, and language. One Type corresponds to one Config
  7. PNG and icon2. PNG in mdPI. Then, there are two entries in MDPI

The file structure is as follows:

Delete an unused resource

Let’s start analyzing how Matrix performs resource optimization.

Matrix will first obtain apK file, R file, signature configuration file and other file information:

String unsignedApkPath = output.outputFile.getAbsolutePath();
removeUnusedResources(unsignedApkPath,
        project.getBuildDir().getAbsolutePath() + "/intermediates/symbols/${variant.name}/R.txt",
        variant.variantData.variantConfiguration.signingConfig);
Copy the code

Next, determine the resource information to delete, including the resource name and its ID:

Obtain the resources you need to remove / / according to the configuration Set < String > ignoreRes = project. Extensions. Matrix. RemoveUnusedResources. IgnoreResources; Set<String> unusedResources = project.extensions.matrix.removeUnusedResources.unusedResources; Iterator<String> iterator = unusedResources.iterator(); String res = null; while (iterator.hasNext()) { res = iterator.next(); If (ignoreResource(res)) {// Specify that the ignored resource does not need to be deleted iterator.remove(); Map<String, Integer> resourceMap = new HashMap(); Map<String, Pair<String, Integer>[]> styleableMap = new HashMap(); File resTxtFile = new File(rTxtFile); readResourceTxtFile(resTxtFile, resourceMap, styleableMap); Map<String, Integer> removeResources = new HashMap<>(); for (String resName : unusedResources) { if (! IgnoreResource (resName)) {// If the resource will be deleted, remove it from resourceMap. Removeresources.put (resName, resourcemap.remove (resName)); }}Copy the code

You can then delete the specified resource by creating a new APK file and ignoring unwanted resources:

for (ZipEntry zipEntry : zipInputFile.entries()) { if (zipEntry.name.startsWith("res/")) { String resourceName = entryToResouceName(zipEntry.name); If (removeResources. Either containsKey (resourceName)) {/ / need to delete the resources would not be written to the new file continue; } else {// Write normal resource information to the new APK file addZipEntry(zipOutputStream, zipEntry, zipInputFile); }}}Copy the code

If shrinkArsc is enabled, you also need to modify the ARSC file to remove the deleted resource information:

if (shrinkArsc && zipEntry.name.equalsIgnoreCase("resources.arsc") && unusedResources.size() > 0) { File srcArscFile = new File(inputFile.getParentFile().getAbsolutePath() + "/resources.arsc"); File destArscFile = new File(inputFile.getParentFile().getAbsolutePath() + "/resources_shrinked.arsc"); / / read the resource information from arsc file ArscReader reader = new ArscReader (srcArscFile. GetAbsolutePath ()); ResTable resTable = reader.readResourceTable(); For (String resName: String resName: String resName: String resName: removeResources.keySet()) { ArscUtil.removeResource(resTable, removeResources.get(resName), resName); } / / will be cut after ResTable written to the new arsc file ArscWriter writer = new ArscWriter (destArscFile. GetAbsolutePath ()); writer.writeResTable(resTable); AddZipEntry (zipOutputStream, zipEntry, destArscFile); }Copy the code

This can be removed by setting its Entry to NULL:

public static void removeResource(ResTable resTable, int resourceId, String resourceName) throws IOException { ResPackage resPackage = findResPackage(resTable, getPackageId(resourceId)); // Find the package if (resPackage! = null) { List<ResType> resTypeList = findResType(resPackage, resourceId); For (ResType ResType: resTypeList) {// Iterate through the ResType of package to find the corresponding type int entryId = getResourceEntryId(resourceId); // Find the corresponding entry resType. GetEntryTable ().set(entryId, NULL); // Set to null restyp.getentryoffsets ().set(entryId, ArscConstants.NO_ENTRY_INDEX); // Set to null restyp.getentryoffsets ().set(entryId, ArscConstants. resType.refresh(); } resPackage.refresh(); resTable.refresh(); }}Copy the code

Above, the new APK file is written after removing unnecessary resources.

conclusion

Arsc file structure:

RemoveUnusedResourcesTask execute the following steps:

  1. Obtain file information such as apK file, R file, and signature configuration file
  2. Determine the resource information to be deleted based on the unusedResource file and R file provided by the user, including the resource name and ID
  3. Deletes the specified resource by ignoring it while a new APK file is written
  4. If shrinkArsc is enabled, modify the ARSC file to remove the deleted resource information by setting its Entry to NULL
  5. Other data is written to the new APK file intact