应用模块化后引用方法数增加

Referenced Method Count Increased After App Modularization

AS:3.5.3; Android Gradle 插件:3.5.0; Gradle:5.6.2;

我们观察到,在将 'app' 模块拆分为几个小模块后,应用中引用的方法数量急剧增加。但奇怪的是,每个 class 添加的引用方法少于 Android Apk Analyzer Tool 中提到的总数。

出于测试目的,我已将 WebActivity.class 从 'app' 模块移动到 'adapters' 模块,并且引用的方法计数增加了 181 个方法。

总结:

app/WebActivity = 63546 实际引用的方法,但显示 65394 方法。 adapter/WebActivity = 63543 实际引用的方法,但显示 65575 方法。

我们 观察到 'referenced method count' 在 adding/splitting 4 个新模块后增加了将近 10k。

具体问题是什么?

应用程序模块化如何将引用方法数量大幅增加到如此之高?

以下是我截取的两个不同 APK 的屏幕截图-唯一的区别是 WebActivity 从 'app' 模块移动到 'adapter' 模块并且增加了 181 个引用方法:

'app' 模块中的 WebActivity

将 WebActivity 移动到 'adapter' 模块

为什么截图中每个class引用方法的加法(红色标记)不等于Apk Analyzer给出的总数?

我已经阅读了很长时间有关代码性能和调整参数的内容。事实上,Android 程序是我关注的重点之一。

让我们首先介绍有助于我们达成解决方案的基本或最重要的概念。

Android Developer所述

module can be independently built, tested, and debugged

因此,模块有自己的 Gradle 和依赖项。您可以在项目 Hierarchy Viewer.

中探索它

模块化强调维护事项。与性能问题不同。因为模块化有这么重要的影响:

  • 增加继承深度

这是我绘制的图表以使其清楚。如你看到的。在使用离散模块时,调用方法 A 有 2N micro secs 与没有离散模块的 N micro secs 相比。

这个问题你可能会想到Referenced Methods算什么跟Depth of inheritance有什么关系?

答案是:虽然使用模块化增加了Referenced Methods。但是,它不会影响应用程序性能,主要可能的问题是 继承深度,在大多数情况下是 可忽略.

我确实强调,模块化中引用方法的增加是由于每个模块 Gradle 和依赖关系

How app modularization can increase the referenced method count drastically so high?

重要影响 APK 分析器的条件 参考方法

Also note that minification and code shrinking can each also considerably change the contents of a DEX file after source code is compiled.

除了上述官方声明外,我想补充一个影响APK分析器的条件:

开发人员在模块化方面有多少经验?

模块化就像一个家,架构(开发商)定义了哪里应该是厨房,哪里应该是洗手间,哪里应该是厕所。 如果架构决定合并 WC 和厨房怎么办? 是的,这是一场灾难。

如果开发人员不是很有经验,这可能会在模块化时发生。


除了额外信息外,还在回答 OP 问题

我在这里回答op在评论中提出的问题

Why would separate Gradle add to the referenced method count? And for separate dependency, if the final result is a single APK then I do not think duplicate dependencies in 'app' and feature module would add to referenced method count.

因为可以构建、测试和调试模块,所以它们必须有自己的 Gradle 和依赖项。

在编译多模块项目时,编译器会生成几个 .dex 文件,包括:

  • 一个 .dex 完全集成依赖项的文件
  • 模块.dexs

依赖项.dex文件是所有模块gradles

的集成

让我们看看模块 gradle 如何影响最终的引用方法计数?!

2 APK 个结果相同,但引用方法计数不同。

它们都是空活动,根据它们的功能,引用方法计数有很大差异 1.7k。 关键区别在于它们的 模块的 Gradle 其中一个被配置为

dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])
    implementation 'androidx.appcompat:appcompat:1.1.0'
    implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
}

另一个配置为

dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])
    implementation 'androidx.appcompat:appcompat:1.2.0-alpha01'
    implementation 'androidx.constraintlayout:constraintlayout:2.0.0-beta4'
}

虽然它们只是空活动,但 Gradle 的微小差异会导致引用方法计数的 1.7k 差异。

AppGradle是

dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])
    implementation 'androidx.appcompat:appcompat:1.1.0'
    implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
    implementation project(path: ':module')
}

major concern is why the addition of individually referenced method count is different than the total referenced method count in Apk Analyzer?

这只是一个 IDE 过滤器而已。当然,如果你只有 select 一个 .dex 文件引用方法计数等于每行引用方法计数的总和但是如果你有多个 select .dex 文件,你会查看 SUM 和实际 Count 之间的差异,因为 References 中的相等性导致 Analyzer 倾向于过滤它们。

在您的屏幕截图中,您 select 编辑了多个 .dex 文件,然后分析器过滤了相等性。

in our project we are using centralized dependencies. gradle file so there is no chance of a different version. So, do you think even if we have the same/exact set of dependencies and their versions in feature modules, it will increase referenced method count?

理论上,它应该增加引用方法的数量。但是,正如我所解释的,开发者经验 高度影响最终结果。

Team Analyzer 应该在发布前检查并修复性能问题,例如

  • proguard 规则
  • 缩减资源
  • androidManifest.xml
  • gradle 设置

现在我想阐明开发人员经验和代码维护如何影响最终结果。 即使您的 APK 使用集中式依赖项

在上面的示例中,我增加了 5.1k 引用方法计数 即使 ,我有 集中依赖性 !!!!!

怎么可能?

答案是:我只是在项目的libs目录下添加了一个无用的隐藏的.jar文件。就像你看到的一样简单我影响了最终结果。

如您所见,开发人员体验 影响最终结果。因此,实际上可能会增加引用方法的数量,尽管理论上应该.

And why there is no difference in referenced method count when I compile only 'app' module by disabling parallel compilation? It should have decreased as only 'app' module's dependencies would have been used, right?

编译与引用的方法没有任何关系counts.it符合开发人员希望符合的内容。


结论

我已经涵盖了围绕该问题的所有可能性。事实上,它可能会在不同的情况下出现,开发人员使用此指南可以解决问题。

  • 我希望您能找到增加引用方法的原因 为什么在某些情况下它可能会急剧增加。
  • 模块有其 Gradle & 依赖性和模块化增加 模块。因此,这些方法参考。
  • 模块化对应用程序性能的影响可以忽略不计,但 您的应用维护得更好。
  • 开发人员在模块化方面的经验也对最终结果有很大影响 结果。

重要提示:几乎所有的陈述都是我的调查和研究。事实上,可能存在错误和错误,将来会更新以添加更多信息。


我看到了您的 'com' 包裹中的所有差异。您可以展开并比较确切 类 缩小的内容。如果您使用最新的 R8 构建,它可以默认删除一些代码。当您将一些 类 放入模块时,收缩器不知道 public classes/methods 是否可以删除或必须留在另一个模块中使用。

回答我自己的问题,因为解决方案在我脑海中刚刚被点击,虽然这没有尝试过但肯定会或很可能会奏效。 :) 对于达成最终解决方案非常有用。它谈到为什么?但不是如何避免它或如何改进它。

这是一个 返回 original/actual 引用方法计数 -

的方法

这不取决于我们如何模块化应用程序,而是取决于我们如何添加依赖项。如果我们使用 'implementation' 添加依赖项,那么该依赖项将保持为模块私有,其他模块无法使用它。如果我们使用 'api' 添加相同的依赖项(等于弃用 'compile'),那么它会变成 public 并且其他依赖模块可以使用它. 由于我们在多模块项目中使用'implementation'在每个模块中添加依赖项,因此每个模块都具有自包含的所有必需依赖项,这就是它可以单独编译的原因。这导致 build/compile 时间减少,因为只能编译修改后的模块。 但是,使用 'implementation' 会增加引用的方法计数 ,因为有这么多重复引用的方法。

因此,如果构建时间不是您关心的问题,而是引用的方法计数,那么您可以绘制所有模块的依赖关系树,并且 通过在基本模块中使用 'api' 来避免添加重复依赖关系.这样即使是顶级模块也可以使用基础模块添加的依赖项,这将避免重复。请记住,这会增加构建时间。

如果我们能够区分调试和发布构建的依赖关系,我们就可以实现这两个目标。使用 'implementation' 为 debug 构建添加所有依赖项,并使用 'api' 为 release 构建仅添加必需和优化的依赖项。这样调试构建会更快,发布构建会更慢,这是可以承受的。

注意:一旦我弄清楚如何为调试和发布构建提供单独的依赖项,我会更新这个答案。