开启shrinkResources后,打包过程会新增task transformClassesWithShrinkResFor{variant},gradle1.5之后只需要注册一个tranform就会产生一个对应的task,查看源码发现对应的tranfrom在com.android.build.gradle.internal.transforms.ShrinkResourcesTransform,此类中调用com.android.build.gradle.tasks.ResourceUsageAnalyzer的analyze方法进行分析无用资源。
ResourceUsageAnalyzer:
该类的开头有这么一段话:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 |
<span style="color: #008000;">/**</span><span style="color: #008000;"> * Class responsible for searching through a Gradle built tree (after resource merging, compilation * and shrinking has been completed, but before final .apk assembly), which figures out which * resources if any are unused, and removes them. * * <p>It does this by examining * * <ul> * <li>The merged manifest, to find root resource references (such as drawables used for activity * icons) * <li>The merged R class (to find the actual integer constants assigned to resources) * <li>The ProGuard mapping files (to find the mapping from original symbol names to short names)* * <li>The merged resources (to find which resources reference other resources, e.g. drawable * state lists including other drawables, or layouts including other layouts, or styles * referencing other drawables, or menus items including action layouts, etc.) * <li>The shrinked output classes (to find resource references in code that are actually * reachable) * </ul> * * From all this, it builds up a reference graph, and based on the root references (e.g. from the * manifest and from the remaining code) it computes which resources are actually reachable in the * app, and anything that is not reachable is then marked for deletion. * * <p>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 {</span><span style="color: #808080;">@code</span><span style="color: #008000;"> * 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 {</span><span style="color: #808080;">@code</span><span style="color: #008000;"> Resources#getIdentifier} method. If not, great, the usage analysis is completely * accurate. If we <b>do</b> find one, we check <b>all</b> 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 <b>all</b> 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. * * <p>Analyzing dex files is also supported. It follows the same rules as analyzing class files. * * <p>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. </span><span style="color: #008000;">*/</span> |
我翻译一下这段:
这个类负责搜索一颗gradle构建树(在合并资源,编译,压缩已经完成之后,最终的apk组装之前),这颗构建树确定了那些资源是未使用的,并将其删除。
它通过检查做到这一点:
- 合并的清单,用于查找根资源引用(例如启动图标)
- 合并的R类(查找分配给资源的实际整数常量)
- 混淆映射文件(找到原始名称到混淆后短名称的一个映射)
- 合并的资源(用于查找哪些资源引用其他资源,例如包括其他other drawables的drawable state lists,或包括其他布局的布局,或引用其他drawables的styles,或包括布局文件的菜单项等)
- 缩小的输出类(在代码中查找实际可访问的资源引用)
所有的这些,构建了一个引用图,并基于根引用(例如来自清单与剩余代码),它计算出了在app中哪些资源是实际可达的,然后将任何无法访问的内容标记为删除。
如果文件R.type.name被引用(非最终资源引用,例如在库中),或者在代码中引用相应的int值,我们通过ASM查看输出的代码来检查这一点。有一个复杂的问题是代码可以通过Resources#getIdentifier(String,String,String),通过传递资源的名称去查查找资源,为了处理这种情况,我们使用ClassVisitor来查看是否有对特定Resources#getIdentifier
方法的任何调用。如果没有,很好,使用分析是完全准确的。如果我们找到一个,我们检查 所有在应用程序中的任何位置找到的字符串常量,并查看是否有看起来有关。例如,如果我们找到字符串“string / foo”或“my.pkg:string / foo”,我们将标记名为foo的字符串资源(如果有)作为可能使用的字符串资源。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
<span style="color: #0000ff;">public</span> <span style="color: #0000ff;">void</span> analyze() <span style="color: #0000ff;">throws</span><span style="color: #000000;"> IOException, ParserConfigurationException, SAXException { gatherResourceValues(mResourceClassDir); recordMapping(mProguardMapping); </span><span style="color: #0000ff;">for</span><span style="color: #000000;"> (File jarOrDir : mClasses) { recordClassUsages(jarOrDir); } recordManifestUsages(mMergedManifest); recordResources(mMergedResourceDir); keepPossiblyReferencedResources(); dumpReferences(); mModel.processToolsAttributes(); mUnused </span>=<span style="color: #000000;"> mModel.findUnused(); }</span> |
一:
1 2 3 4 5 6 7 8 9 10 11 12 |
<span style="color: #0000ff;">private</span> <span style="color: #0000ff;">void</span> gatherResourceValues(File file) <span style="color: #0000ff;">throws</span><span style="color: #000000;"> IOException { </span><span style="color: #0000ff;">if</span><span style="color: #000000;"> (file.isDirectory()) { File[] children </span>=<span style="color: #000000;"> file.listFiles(); </span><span style="color: #0000ff;">if</span> (children != <span style="color: #0000ff;">null</span><span style="color: #000000;">) { </span><span style="color: #0000ff;">for</span><span style="color: #000000;"> (File child : children) { gatherResourceValues(child); } } } </span><span style="color: #0000ff;">else</span> <span style="color: #0000ff;">if</span> (file.isFile() &&<span style="color: #000000;"> file.getName().equals(SdkConstants.FN_RESOURCE_CLASS)) { parseResourceClass(file); } }</span> |
SdkConstants.FN_RESOURCE_CLASS = R.java
可以看出它找到的是R.java文件,然后调用parseResourceClass(file);方法,将R.java中的资源都标记为可达,加入表中。
recordMapping(mProguardMapping)
解析混淆文件,将现在的把映射存于表中
recordManifestUsages(mMergedManifest);
分析Manifest文件,将找到的引用标记为可达,例如:启动图标