public class ResourceUsageAnalyzer
extends java.lang.Object
It does this by examining
A resource is referenced in code if either the field R.type.name is referenced (which is the
case for non-final resource references, e.g. in libraries), or if the corresponding int value is
referenced (for final resource values). We check this by looking at the shrinked output classes
with an ASM visitor. One complication is that code can also call Resources#getIdentifier(String,String,String)
where they can pass in the names of resources to
look up. To handle this scenario, we use the ClassVisitor to see if there are any calls to the
specific Resources#getIdentifier
method. If not, great, the usage analysis is completely
accurate. If we do find one, we check all the string constants found anywhere in
the app, and look to see if any look relevant. For example, if we find the string "string/foo" or
"my.pkg:string/foo", we will then mark the string resource named foo (if any) as potentially
used. Similarly, if we find just "foo" or "/foo", we will mark all resources named "foo"
as potentially used. However, if the string is "bar/foo" or " foo " these strings are ignored.
This means we can potentially miss resources usages where the resource name is completed computed
(e.g. by concatenating individual characters or taking substrings of strings that do not look
like resource names), but that seems extremely unlikely to be a real-world scenario.
Analyzing dex files is also supported. It follows the same rules as analyzing class files.
For now, for reasons detailed in the code, this only applies to file-based resources like layouts, menus and drawables, not value-based resources like strings and dimensions.
Modifier and Type | Class and Description |
---|---|
static class |
ResourceUsageAnalyzer.ApkFormat |
Modifier and Type | Field and Description |
---|---|
static java.util.regex.Pattern |
FORMAT |
static boolean |
REPLACE_DELETED_WITH_EMPTY
Whether we should create small/empty dummy files instead of actually
removing file resources.
|
static byte[] |
TINY_9PNG |
static long |
TINY_9PNG_CRC |
static byte[] |
TINY_BINARY_XML |
static long |
TINY_BINARY_XML_CRC |
static byte[] |
TINY_PNG |
static long |
TINY_PNG_CRC |
static byte[] |
TINY_PROTO_XML |
static long |
TINY_PROTO_XML_CRC |
static boolean |
TWO_PASS_AAPT
Whether we support running aapt twice, to regenerate the resources.arsc file
such that we can strip out value resources as well.
|
Constructor and Description |
---|
ResourceUsageAnalyzer(java.io.File rDir,
java.lang.Iterable<java.io.File> classes,
java.io.File manifest,
java.io.File mapping,
java.io.File resources,
java.io.File reportFile,
ResourceUsageAnalyzer.ApkFormat format) |
ResourceUsageAnalyzer(java.io.File rDir,
java.lang.Iterable<java.io.File> classes,
java.io.File manifest,
java.io.File mapping,
java.lang.Iterable<java.io.File> resources,
java.io.File reportFile,
ResourceUsageAnalyzer.ApkFormat format) |
Modifier and Type | Method and Description |
---|---|
void |
analyze() |
void |
dispose() |
void |
emitConfig(java.nio.file.Path destination) |
void |
emitWhitelist(java.nio.file.Path destination)
Writes the whitelist string to whitelist file specified by destination
|
int |
getUnusedResourceCount() |
boolean |
isDebug() |
boolean |
isDryRun() |
boolean |
isVerbose() |
void |
removeUnused(java.io.File destination)
Remove resources (already identified by
analyze() ). |
void |
rewriteResourceZip(java.io.File source,
java.io.File dest)
"Removes" resources from an .ap_ file by writing it out while filtering out
unused resources.
|
void |
setDebug(boolean verbose) |
void |
setDryRun(boolean dryRun) |
void |
setVerbose(boolean verbose) |
public static final boolean REPLACE_DELETED_WITH_EMPTY
public static final boolean TWO_PASS_AAPT
public static final byte[] TINY_PNG
public static final long TINY_PNG_CRC
public static final byte[] TINY_9PNG
public static final long TINY_9PNG_CRC
public static final byte[] TINY_BINARY_XML
public static final long TINY_BINARY_XML_CRC
public static final byte[] TINY_PROTO_XML
public static final long TINY_PROTO_XML_CRC
public static final java.util.regex.Pattern FORMAT
public ResourceUsageAnalyzer(@NonNull java.io.File rDir, @NonNull java.lang.Iterable<java.io.File> classes, @NonNull java.io.File manifest, @Nullable java.io.File mapping, @NonNull java.lang.Iterable<java.io.File> resources, @Nullable java.io.File reportFile, @NonNull ResourceUsageAnalyzer.ApkFormat format)
public ResourceUsageAnalyzer(@NonNull java.io.File rDir, @NonNull java.lang.Iterable<java.io.File> classes, @NonNull java.io.File manifest, @Nullable java.io.File mapping, @NonNull java.io.File resources, @Nullable java.io.File reportFile, @NonNull ResourceUsageAnalyzer.ApkFormat format)
public void dispose()
public void analyze() throws java.io.IOException, javax.xml.parsers.ParserConfigurationException, org.xml.sax.SAXException
java.io.IOException
javax.xml.parsers.ParserConfigurationException
org.xml.sax.SAXException
public boolean isDryRun()
public void setDryRun(boolean dryRun)
public boolean isVerbose()
public void setVerbose(boolean verbose)
public boolean isDebug()
public void setDebug(boolean verbose)
public void rewriteResourceZip(@NonNull java.io.File source, @NonNull java.io.File dest) throws java.io.IOException
source
- the .ap_ file created by aaptdest
- a new .ap_ file with unused file-based resources removedjava.io.IOException
public void emitWhitelist(java.nio.file.Path destination) throws java.io.IOException
java.io.IOException
public void emitConfig(java.nio.file.Path destination) throws java.io.IOException
java.io.IOException
public void removeUnused(@Nullable java.io.File destination) throws java.io.IOException, javax.xml.parsers.ParserConfigurationException, org.xml.sax.SAXException
analyze()
).
This task will copy all remaining used resources over from the full resource directory to a new reduced resource directory. However, it can't just delete the resources, because it has no way to tell aapt to continue to use the same id's for the resources. When we re-run aapt on the stripped resource directory, it will assign new id's to some of the resources (to fill the gaps) which means the resource id's no longer match the constants compiled into the dex files, and as a result, the app crashes at runtime.
Therefore, it needs to preserve all id's by actually keeping all the resource names. It can still save a lot of space by making these resources tiny; e.g. all strings are set to empty, all styles, arrays and plurals are set to not contain any children, and most importantly, all file based resources like bitmaps and layouts are replaced by simple resource aliases which just point to @null.
destination
- directory to copy resources into; if null, delete resources in placejava.io.IOException
javax.xml.parsers.ParserConfigurationException
org.xml.sax.SAXException
public int getUnusedResourceCount()