Referenced Method Count Increased After App Modularization

Roh*_*ase 16 android modularization apk android-studio

AS: 3.5.3; Android Gradle Plugin: 3.5.0; Gradle: 5.6.2;

We observed a drastic increase in the number of methods referenced in our app after splitting the 'app' module into several small modules. But the strange thing is that the addition of referenced methods by each class is less than the mentioned total in Android Apk Analyzer Tool.

For test purpose, I have moved WebActivity.class from 'app' module to 'adapters' module and referenced method count increased by 181 methods.

To Summarize:

app/WebActivity = 63546 Actual referenced methods but showing 65394 methods. adapter/WebActivity = 63543 Actual referenced methods but showing 65575 methods.

We have observed 'referenced method count' increased by almost 10k after adding/splitting 4 new modules.

What is the exact issue?

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

Following are the screenshots I took of two different APKs-only difference is WebActivity moved from 'app' module to 'adapter' module and 181 referenced methods increased:

WebActivity in 'app' module 在此处输入图片说明

Moved WebActivity to 'adapter' module 在此处输入图片说明

In the screenshots, why the addition of referenced methods by each class (marked in red color) is not equal to the total given in Apk Analyzer?

Mr.*_*.AF 9

我一直在阅读有关代码性能和调优参数的文章。确实,Android 程序是我的重点之一。

让我们首先介绍帮助我们找到解决方案的基本或最重要的概念。

正如Android 开发人员所说

模块可以独立构建、测试和调试

因此,Modules 有自己的Gradle 和 Dependencies。你可以在 project 中探索它Hierarchy Viewer

事实上,模块化强调维护问题。与性能问题不同。因为,模块化具有以下重要影响:

  • 增加继承深度

这是我绘制的图表以使其清楚。如您所见。使用离散模块时,为了调用方法 A,2N micro secsN micro secs不使用离散模块进行比较。

在此处输入图片说明

这个问题我想到了引用方法计算与继承深度相关的内容?

答案是:虽然使用模块化增加了引用方法。但是,它实际上并不影响应用程序性能,主要可能的问题是继承深度,在大多数情况下是可以忽略的

我强调模块化中增加的引用方法是由于每个模块 Gradle 和依赖项

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

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

另请注意,在编译源代码后,缩小和代码收缩也会显着改变 DEX 文件的内容。

除了上述官方声明之外,我想添加另一个影响 APK 分析器的条件,即:

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

模块化就像一个家,建筑(开发商)定义了哪里应该是厨房,哪里应该是休息室,哪里应该是卫生间。 如果架构决定将卫生间和厨房结合起来怎么办?是的,这是一场灾难。

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


回答 OP 问题以及额外信息

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

为什么单独的 Gradle 会添加到引用的方法计数中?对于单独的依赖项,如果最终结果是单个 APK,那么我认为“应用程序”和功能模块中的重复依赖项不会增加引用的方法计数。

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

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

  • .dex完全集成依赖的文件
  • 模块.dex小号

依赖.dex文件是所有模块gradle的集成

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

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

图1 图2

它们都是空活动,它们1.7k的引用方法计数差异非常大,具体取决于它们的功能。他们的主要区别在于他们的模块的 Gradle其中之一被配置为

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

另一个配置为

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

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

App Gradle 是

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')
}
Run Code Online (Sandbox Code Playgroud)

主要问题是为什么添加单独引用的方法计数与 Apk Analyzer 中的总引用方法计数不同?

这只是 IDE 过滤器而已。当然,如果你只选择一个.dex文件引用方法计数等于每行引用方法计数的 SUM 但如果你多选.dex文件,你会看到 SUM 和实际计数的差异,因为分析器首选的引用相等过滤它们。

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

在我们的项目中,我们使用集中式 dependencies.gradle 文件,因此不会有不同版本的机会。那么,您是否认为即使我们在功能模块中有相同/完全相同的依赖项及其版本,也会增加引用方法的数量?

理论上它应该增加引用的方法计数。但是,正如我所解释的,开发人员体验对最终结果有很大影响。

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

  • 扰乱规则
  • 缩小和缩小资源
  • androidManifest.xml
  • 渐变设置

现在我想澄清一下开发人员体验和代码维护如何影响最终结果。即使您的 APK 使用集中依赖

图3

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

怎么可能?

答案是:我只是.jarlibs项目目录中添加了一个无用的隐藏文件。正如您所看到的那样简单,我影响了最终结果。

正如你所看到开发人员体验会影响最终result.as结果,实际上它可能是引用的方法计数虽然增加理论上是否应NOT

为什么当我通过禁用并行编译只编译“app”模块时,引用的方法计数没有区别?它应该减少,因为只会使用“app”模块的依赖项,对吗?

编译与引用的方法计数没有任何关系。它符合开发人员想要编译的内容。


结论

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

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

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



Roh*_*ase 3

回答我自己的问题作为解决方案只是在我的脑海中响起,尽管这还没有尝试过,但肯定或最有可能会起作用。:) AF 先生给出的答案对于达成最终解决方案非常有用。它谈论的是为什么?但不是如何避免它或如何改进它。

这是一种获取原始/实际引用方法计数的方法-

它不取决于我们如何模块化应用程序,而是取决于我们如何添加依赖项。如果我们使用“ implementation ”添加依赖项,那么该依赖项对于该模块来说仍然是私有的,并且其他模块都不能使用它。如果我们使用“ api ”(等于已弃用的“compile”)添加相同的依赖项,那么它就会成为公共的,其他依赖模块可以使用它。由于我们使用“实现”在多模块项目中的每个模块中添加依赖项,因此每个模块都具有独立的所有必需依赖项,这就是它可以单独编译的原因。这会减少构建/编译时间,因为只能编译修改后的模块。但是,使用“实现”会增加引用的方法计数,因为有很多重复的引用方法。

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

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

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