如何在模块化的Android应用程序中共享依赖项

11

我有一个Android项目,按模块化方式进行架构。我通过将源代码分割成多个Gradle模块来实现项目的模块化,遵循“Clean Architecture”。

以下是该应用程序的结构。

enter image description here

在这个层次结构中,顶层模块 App 是没有任何其他模块依赖的主模块。较低级别的模块 domaindata 不依赖于 App 模块,而 App 模块包括 datadomain 模块。我已经将下面的代码添加到 app 模块的 build.gradle 文件中。

    implementation project(':domain')
    api project(':data')

现在,我遇到了保持每个模块间依赖关系的问题。因为每个模块都是单独的Android模块,每个模块都有自己的build.gradle文件。App模块可以使用data和domain模块中的类。但是,我有一些通用的类(例如一些注释、实用程序、广播类、Dagger范围等),我希望在所有模块中使用。但这些是我面临的问题:

  • 由于这些类包含在主模块app中,我无法在我的data和domain中访问它们,因为这些模块不依赖于更高层次的app。
  • 我在所有层中使用的任何库(例如:RxJava)都需要包含在每个模块的build.gradle文件中。

作为解决方案,我考虑添加另一个Android模块,例如common,其中将包含我所有通用的类以及我在所有模块中使用的库。

我的其他模块app、domain和data将具有此模块作为其依赖项。

implementation project(':common')

所以,任何全局库和类都将添加到此模块中,每个单独的模块只会有特定于该模块的类。

这是一个好方法吗?还是有更有效地解决这个问题的方法吗?


取消模块化您的应用程序?模块化仅有助于大型团队有效协作。大多数应用程序并不真正需要它。如果您是单独开发人员/小团队并遇到问题,除了作为学习经验之外,维护“清洁架构”毫无意义。这只是为了学习还是您将要在生产中使用它? - Karan Harsh Wardhan
@KaranHarshWardhan 这是一个生产应用程序,大小有点大。因此,我们希望在模块之间有一些清晰的分离。 - doe
模块化如何帮助满足大型尺寸要求?听起来像是过度工程化。 - Karan Harsh Wardhan
@KaranHarshWardhan 我想尝试一下这个链接中的内容(https://proandroiddev.com/intro-to-app-modularization-42411e4c421e)。不管它是否过度工程化,但如果我们决定进行这样的模块化,那么对于上述问题,什么是理想的解决方案呢? - doe
1个回答

17

最近我们遇到了这个问题,因为我们转向了一个多模块项目,以便于重用、编译时间优化(未更改的模块不会重新编译)等。你的核心目标是尽可能缩小你的app模块,因为每次都会重新编译。

我们使用了一些通用的原则,可能会对你有帮助:

  • 一个共同的base-ui模块包含主要的strings.xmlstyles.xml等。
  • 其他前端模块(profiledashboard等)实现了这个base-ui模块。
  • 将在所有用户界面模块中使用的库作为api而非implementation包含在base-ui中。
  • 仅在某些模块中使用的库作为依赖项添加到那些模块中。
  • 该项目还广泛使用数据同步等,因此也有base-datadashboard-data等模块,遵循相同的逻辑。
  • dashboard功能模块依赖于dashboard-data
  • app模块只依赖于功能模块,如dashboardprofile等。
我强烈建议预先勾画出模块依赖流程图,我们最终有大约15个左右的严格组织的模块。在你的情况下,你提到这已经是一个相当大的应用程序,因此我想app需要将功能模块分离出来,domain也是一样。记住,小模块=重新编译的代码更少!
我们遇到了一些问题,确保所有子模块都使用相同版本(buildTypeflavors)的应用程序。基本上,所有子模块必须像app模块一样定义相同的flavorbuildType
另一方面,多模块开发确实让您思考依赖关系,并强制执行功能之间的严格分离。您可能会遇到一些意想不到的问题,这些问题以前从未考虑过。例如,像显示应用程序版本一样简单的事情突然变得复杂起来(免责声明:我的文章)。 这篇文章也帮助我们决定了我们的方法。您链接的文章似乎也是一个很好的资源,我希望它在我们过渡时已经存在!

在评论讨论后,这是一个示例图表(有些凌乱,但足以说明概念。请注意,区分apiimplementation 将是下一步很好的选择): 模块依赖关系图


感谢您的详细解释。您的方法听起来不错。我还有一些疑问。据我理解,base-ui层是更接近框架的一层。这一层是否作为依赖注入逻辑的驱动模块?我们正在使用dagger2进行DI。因此,所有子模块的依赖关系解析代码是否可以存在于此层中?这将作为所有其他模块的驱动模块? - doe
在架构过程中,我们遇到了一个问题,即我们希望仅在相关模块之间保持交互。例如,我们需要在app->domain->data层之间以这样的方式进行交互:app仅与domain层通信,而不是直接与内部层data通信。因此,我们限制了任何data层类在app模块内部的访问权限,因为domain层是直接处理data的层。如果将data模块添加为gradle implementationdomain模块中,则可以限制此访问。 - doe
然而,由于顶层的 app 层(在您的结构中可能是 base-ui)是管理 DI 逻辑的层,这意味着 Dagger 存在于 app 层并且需要访问 data 层中的某些类来解决依赖关系图。这迫使我们将 data 模块直接添加到 app 模块中,或者作为 domain 模块的 api,以便获得对 data 模块的访问权限。但是添加它将允许从 app 层直接访问 data 层中的任何类。这是一个好的方法吗?还是应该将 DI 代码存在于其他专用模块中? - doe
我们没有使用 DI,所以我不能具体说明。但总的来说,如果你按照之前提到的方式采用较小的模块,就可以避免大部分应用程序能够访问它们不需要的基础模块。我认为你可能对“app”有些误解,它始终是顶层。而“base-ui”则是底层,“dashboard”则处于中间。如果需要的话,我可以画个图来帮助说明? - Jake Lee
如果有助于理解,我可以画一个图表。这将非常有帮助。 - doe
这是一份关于编程的内容,但它可能有点过时了,而且我没有区分“api”和“implementation”。不过这应该足以解释我的想法! - Jake Lee

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接