aboutsummaryrefslogtreecommitdiffhomepage
path: root/src/tools/android/java/com/google/devtools/build/android/aapt2/ProtoResourceUsageAnalyzer.java
diff options
context:
space:
mode:
Diffstat (limited to 'src/tools/android/java/com/google/devtools/build/android/aapt2/ProtoResourceUsageAnalyzer.java')
-rw-r--r--src/tools/android/java/com/google/devtools/build/android/aapt2/ProtoResourceUsageAnalyzer.java88
1 files changed, 64 insertions, 24 deletions
diff --git a/src/tools/android/java/com/google/devtools/build/android/aapt2/ProtoResourceUsageAnalyzer.java b/src/tools/android/java/com/google/devtools/build/android/aapt2/ProtoResourceUsageAnalyzer.java
index 171bbf3791..a1cd448827 100644
--- a/src/tools/android/java/com/google/devtools/build/android/aapt2/ProtoResourceUsageAnalyzer.java
+++ b/src/tools/android/java/com/google/devtools/build/android/aapt2/ProtoResourceUsageAnalyzer.java
@@ -15,6 +15,7 @@
package com.google.devtools.build.android.aapt2;
import static java.util.stream.Collectors.joining;
+import static java.util.stream.Collectors.toList;
import com.android.build.gradle.tasks.ResourceUsageAnalyzer;
import com.android.resources.ResourceType;
@@ -24,7 +25,9 @@ import com.android.tools.lint.detector.api.LintUtils;
import com.android.utils.XmlUtils;
import com.google.common.base.Preconditions;
import com.google.common.base.Splitter;
+import com.google.common.collect.HashMultimap;
import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Multimap;
import com.google.devtools.build.android.aapt2.ProtoApk.ManifestVisitor;
import com.google.devtools.build.android.aapt2.ProtoApk.ReferenceVisitor;
import com.google.devtools.build.android.aapt2.ProtoApk.ResourcePackageVisitor;
@@ -34,11 +37,16 @@ import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Path;
import java.nio.file.StandardCopyOption;
+import java.util.ArrayDeque;
import java.util.Collection;
+import java.util.Deque;
import java.util.HashSet;
import java.util.Iterator;
+import java.util.List;
import java.util.Set;
import java.util.logging.Logger;
+import javax.annotation.CheckReturnValue;
+import javax.annotation.Nullable;
import javax.xml.parsers.ParserConfigurationException;
import org.w3c.dom.Attr;
import org.w3c.dom.DOMException;
@@ -48,6 +56,8 @@ import org.xml.sax.SAXException;
/** A resource usage analyzer tha functions on apks in protocol buffer format. */
public class ProtoResourceUsageAnalyzer extends ResourceUsageAnalyzer {
+ private static final Logger logger = Logger.getLogger(ProtoResourceUsageAnalyzer.class.getName());
+
public ProtoResourceUsageAnalyzer(Set<String> resourcePackages, Path mapping, Path logFile)
throws DOMException, ParserConfigurationException {
super(resourcePackages, null, null, null, mapping, null, logFile);
@@ -72,7 +82,8 @@ public class ProtoResourceUsageAnalyzer extends ResourceUsageAnalyzer {
* @param keep A list of resource urls to keep, unused or not.
* @param discard A list of resource urls to always discard.
*/
- public void shrink(
+ @CheckReturnValue
+ public ProtoApk shrink(
ProtoApk apk,
Path classes,
Path destination,
@@ -107,30 +118,59 @@ public class ProtoResourceUsageAnalyzer extends ResourceUsageAnalyzer {
keepPossiblyReferencedResources();
- dumpReferences();
-
- // Remove unused.
- final ImmutableSet<Resource> unused = ImmutableSet.copyOf(model().findUnused());
+ final List<Resource> resources = model().getResources();
- // ResourceUsageAnalyzer uses the logger to generate the report.
- Logger logger = Logger.getLogger(getClass().getName());
- unused.forEach(
- resource ->
- logger.fine(
- "Deleted unused file "
- + ((resource.locations != null && resource.locations.getFile() != null)
- ? resource.locations.getFile().toString()
- : "<apk>" + " for resource " + resource)));
+ List<Resource> roots =
+ resources.stream().filter(r -> r.isKeep() || r.isReachable()).collect(toList());
- apk.copy(
+ final Set<Resource> reachable = findReachableResources(roots);
+ return apk.copy(
destination,
- (resourceType, name) ->
- !unused.contains(
- Preconditions.checkNotNull(
- model().getResource(resourceType, name),
- "%s/%s was not declared but is copied!",
- resourceType,
- name)));
+ (resourceType, name) -> reachable.contains(model().getResource(resourceType, name)));
+ }
+
+ private Set<Resource> findReachableResources(List<Resource> roots) {
+ final Multimap<Resource, Resource> referenceLog = HashMultimap.create();
+ Deque<Resource> queue = new ArrayDeque<>(roots);
+ final Set<Resource> reachable = new HashSet<>();
+ while (!queue.isEmpty()) {
+ Resource resource = queue.pop();
+ if (resource.references != null) {
+ resource.references.forEach(
+ r -> {
+ referenceLog.put(r, resource);
+ queue.add(r);
+ });
+ }
+ // if we see it, it is reachable.
+ reachable.add(resource);
+ }
+
+ // dump resource reference map:
+ final StringBuilder keptResourceLog = new StringBuilder();
+ referenceLog
+ .asMap()
+ .forEach(
+ (resource, referencesTo) ->
+ keptResourceLog
+ .append(printResource(resource))
+ .append(" => [")
+ .append(
+ referencesTo
+ .stream()
+ .map(ProtoResourceUsageAnalyzer::printResource)
+ .collect(joining(", ")))
+ .append("]\n"));
+
+ logger.fine("Kept resource references:\n" + keptResourceLog);
+
+ return reachable;
+ }
+
+ private static String printResource(Resource res) {
+ return String.format(
+ "{%s[isRoot: %s] = %s}",
+ res.getUrl(), res.isReachable() || res.isKeep(), "0x" + Integer.toHexString(res.value));
}
private static final class ResourceDeclarationVisitor implements ResourceVisitor {
@@ -138,11 +178,11 @@ public class ProtoResourceUsageAnalyzer extends ResourceUsageAnalyzer {
private final ResourceShrinkerUsageModel model;
private final Set<Integer> packageIds = new HashSet<>();
- public ResourceDeclarationVisitor(ResourceShrinkerUsageModel model) {
+ private ResourceDeclarationVisitor(ResourceShrinkerUsageModel model) {
this.model = model;
}
- @javax.annotation.Nullable
+ @Nullable
@Override
public ManifestVisitor enteringManifest() {
return null;