应用程序模块化后,引用的方法计数增加


16

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

将“应用程序”模块拆分为几个小模块后,我们发现应用程序中引用的方法数量急剧增加。但是奇怪的是,每个类所引用的方法的添加量少于Android Apk Analyzer工具中提到的总数。

为了进行测试,我将WebActivity.class从“ app”模块移至“ adapters”模块,并且引用的方法计数增加了181个方法。

总结一下:

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

我们观察到添加/拆分4个新模块后,“引用方法计数”增加了将近1万。

确切的问题是什么?

应用程序模块化如何才能大幅提高引用方法的数量?

以下是我截取的两个不同APK的屏幕截图,不同之处在于WebActivity从“ app”模块移至“ adapter”模块,并增加了181种引用方法:

“应用”模块中的WebActivity 在此处输入图片说明

将WebActivity移至“适配器”模块 在此处输入图片说明

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


我创建了一个问题,您可以在这里跟踪它issuetracker.google.com/issues/146957168
Rohit Surwase

Answers:


9

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

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

正如Android开发人员所说

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

因此,模块具有自己的Gradle和依赖项。您可以在project中进行探索Hierarchy Viewer

实际上,模块化注重维护。不同于性能问题。因此,模块化具有以下重要影响:

  • 增加继承深度

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

在此处输入图片说明

我想到这个问题,即引用方法对继承深度有何影响?

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

我确实强调,模块化中引用方法的增加归因于每个模块的Gradle和依赖项

应用程序模块化如何才能大幅提高引用方法的数量?

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

还要注意,在编译源代码之后,压缩和代码收缩也可以极大地改变DEX文件的内容。

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

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

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

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


除其他信息外,回答OP问题

在这里,我回答评论中的操作性问题

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

因为模块可以被构建,测试和调试,所以它们必须具有自己的Gradle&Dependencies。

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

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

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

让我们看一下模块gradle如何影响最终的Reference Mothods Count ?!

2 APK s的结果相同,但“引用方法”计数不同。

图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'
}

另一个配置为

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引用方法计数的差异。

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')
}

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

这只是IDE过滤器。可以肯定的是,如果您仅选择一个.dex文件,则“引用方法计数”等于每行“引用方法计数”的SUM,但是如果您选择多个.dex文件,您将看到SUM和实际计数的差异,这是因为Analyzer首选的“引用”相等过滤它们。

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

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

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

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

  • 保护规则
  • 缩小和缩小资源
  • androidManifest.xml
  • gradle设置

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

图3

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

怎么可能?

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

如您所见,开发人员体验会影响最终结果。结果,实际上它可能是引用的方法计数虽然增加理论上是否应NOT

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

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


结论

我已经介绍了有关该问题的所有可能性。确实,它可以在不同情况下出现,并且开发人员可以使用此指南来解决此问题。

  • 我希望您能找到为什么增加了引用方法以及为什么在某些情况下可能会大大增加引用方法的原因。
  • 模块具有其Gradle&Dependencies和模块化增加模块。因此,这些方法参考。
  • 模块化实际上会影响应用程序的性能,但是可以使您的应用程序维护更好。
  • 开发人员在模块化方面的经验也会极大地影响最终结果。

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



谢谢AF先生,我希望在“这个问题让我想到引用方法计算与继承深度有关的问题?答案是”之后得到答案,但是您没有回答。您能否详细说明为什么继承深度会增加引用方法的数量?与我们的情况一样,我们没有添加任何额外的层,只是拆分了“ app”模块。如果某个功能模块通过“ app”模块访问另一个功能模块的方法,则引用方法的数量可能会增加,这是原因吗?
Rohit Surwase

@RohitSurwase的答案就是声明的其余部分。继承的深度不会增加方法引用,模块化会这样做,而模块化会导致继承深度增加。
AF先生

@RohitSurwase,访问另一个模块中另一个功能的功能实际上并不会增加引用方法的数量。引用方法数量增加的主要原因是每个模块所需的Gradle和依赖项。
Mr.AF

@RohitSurwase您为模块之间的关系指出了一些好的技巧。实际上,如果2个模块之间的关系和引用的方法过多,则应将它们组合在一起以获得更好的性能。实际上,模块在术语和概念上需要独立。
Mr.AF

1
如我所说,@ RohitSurwase可能不是您的情况。我的意思是开发人员的经验,有可能,这种可能性可能来自不同的来源。我列出了您需要搜索的所有可能来源
Mr.AF

2

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

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

它不取决于我们如何模块化应用程序,而取决于我们如何添加依赖项。如果我们使用' implementation ' 添加依赖项,则该依赖项对模块仍然是私有的,其他模块都无法使用它。而且,如果我们使用“ api ” 添加相同的依赖项(等于不推荐使用的“编译”),那么它将变为公共,其他依赖项模块也可以使用它。由于我们使用“实现”在多模块项目中的每个模块中添加依赖项,因此每个模块都具有所有必需的依赖项,因为这些都是自包含的,因此可以单独进行编译。由于只能编译修改的模块,因此可减少构建/编译时间。但是,使用 “实现”会增加引用方法的数量 因为有很多重复的引用方法。

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

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

注意:一旦弄清楚如何为调试和发行版本提供单独的依赖项,我将更新此答案。


我喜欢它。好的材料。
Mr.AF

0

我看到了您的“ com”包装中的所有区别。您可以展开并比较缩小的确切类。如果您使用最新的R8进行构建,则默认情况下可以删除一些代码。当您将某些类放到模块收缩器中时,不知道是否可以删除公共类/方法或必须将其保留供其他模块使用。 在此处输入图片说明


是的,我确实进行了扩展并检查了包括“ com”在内的每个包装。主要关注的问题是为什么在Apk Analyzer中添加的单独引用方法计数与总引用方法计数不同?
罗希特·瑟瓦斯
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.