为依赖构建反腐层

287 阅读6分钟

反腐败层

任何拥有一两年以上经验的 Android 开发人员都知道,Android Gradle 插件 (AGP) 一直在不断发展。对于最简单的项目,这些更改在很大程度上是透明的,但我猜大多数 Android 项目多年来对这些更改感到沮丧。

AGP 的痛点之一是其 API 不断变化。 (Google 甚至为此 API 有一个记录在案的“迁移时间表”;它跨越数年。)Google 还采取了一种特殊的观点,即它的许多公共类并不是真正的公共 API,而是内部实现细节,因此,它经常通过语义版本控制在没有任何相应信号的情况下推送重大更改。

这个问题有两种极端的解决方案。我强烈建议您遵循的一个极端是成为一名墨守成规的人。这是一种设计模式,您基本上“放弃”并承认您将永远无法真正制作出特定于您的项目的模型。你的模型是 AGP 的模型。

在另一个极端,我们有反腐败层。你可能不应该这样做。

当下游项目依赖于它(1)无法控制的上游时,使用反腐败层; (2) 无法避免; (3) 不信任。这准确地描述了我们与 AGP 的关系。这个概念通常被概括为“包装”依赖,但它不止于此。

我们从外观或包装接口开始。 外观是我们希望拥有的接口,它负责重定向到依赖项提供的实际接口(在本例中为 AGP)。这是一个简化的示例:

image.png

右边是AGP,它有majorArcanum(...) 和minorArcanum(...) 等复杂的方法,每个方法都有很多参数。在左边我们有我们想要的接口,它只是一个不带参数的方法。我们可以这样做是因为,虽然 AGP 是一个通用构建工具,它必须为每个 Android 开发者解决所有问题,但我们有一个不同的、更小的问题:如何为我们的开发者构建我们的应用程序。

我们将外观包装在一个“适配器”中,该适配器知道如何与不同版本的 AGP 通信。这就是我们解决AGP接口快速变化问题的方法。我们的适配器可以为我们支持的每个 AGP 版本提供不同的实现,即当前版本加上 AGP 管道中接下来的两个候选版本。通过这种机制,我们可以轻松地更新到每个新版本——不再需要长达数月的迁移。

为了帮助您直观地了解其工作原理,请考虑以下简化的实现:

class AndroidGradlePluginFactory(
  private val project: Project,
  private val agpVersion: AgpVersion,
) {

  companion object {
    fun getAdapter(project: Project): AndroidGradlePlugin {
      return AndroidGradlePluginFactory(
        project = project,
        agpVersion = AgpVersion.current(project)
      ).newAdapter()
    }
  }

  fun newAdapter(): AndroidGradlePlugin = when {
    agpVersion >= AgpVersion.version("7.3") -> AndroidGradlePlugin7_3(project)
    agpVersion >= AgpVersion.version("7.2") -> AndroidGradlePlugin7_2(project)
    agpVersion >= AgpVersion.version("7.1") -> AndroidGradlePlugin7_1(project)
    else -> throw UnknownAgpException("No adapter for AGP $agpVersion")
  }
}

请注意,AgpVersion.current(project) 封装了一个重要的实现,用于检索运行时存在的 AGP 的实际版本。另请注意,每个实现都封装在自己的项目(模块)中以隔离其类路径。

要使用这个工厂,你的插件应该是这样的:

class MyPlugin : Plugin<Project> {
  override fun apply(target: Project) {
    val agp = AndroidGradlePluginFactory.getAdapter(target)
    // all accesses of AGP functionality _must_ go through `agp`
  }
}

还可以将 agp 实例注入自定义扩展,然后要求您的开发人员使用该扩展(而不是支持的 android 扩展)来自定义他们的项目。

abstract class CircleExtension(agp: AndroidGradlePlugin) { 
  fun buildMyAppPlease(how: String) { … }
}
// app/build.gradle
plugins {
  id 'com.circle.android.app'
}

circle {
  buildMyAppPlease 'with style'
}

护栏

综合测试套件

我们所有的插件都针对所有受支持的 AGP 版本进行了全面测试。这很难做到,但绝对必要。

NoAgpAtRuntime

我们有一个任务 noAgpAtRuntime,它注册在我们所有的构建逻辑模块上。如果在构建类路径上发现来自 AGP 的类,此任务将失败。我们希望应用我们插件的构建(“主构建”)负责提供 AGP。

严格依赖

我们在根构建类路径上设置了严格的依赖约束,以确保它使用我们为每个构建指定的 AGP 版本。

// root build.gradle of main build
buildscript {
  dependencies {
    constraints {
      classpath('com.android.tools.build:gradle') {
        version { strictly agpVersion /* a string */ }
      }
    }
  }
}

依赖卫士

如果构建类路径意外更改,我们使用 DependencyGuard 使构建失败。

// root build.gradle
plugins {
  id 'com.dropbox.dependency-guard'
}
dependencyGuard {
  configuration('classpath')
}

虚无主义名声不好

我认为大多数人对虚无主义的含义都有奇怪的想法。我第一次接触它可能是在我十几岁的时候看到 The Big Lebowski 的时候。

image.png

这不是我所说的那种虚无主义。我的意思是,它更像是对公认真理的放弃。在软件开发的上下文中,我们有很多公认的事实,并且简单地接受它们会导致上面讨论的循规蹈矩的方法(或“模式”)。对我来说,拒绝这些真理并走自己的路包含了我发现自己拥抱的虚无主义元素。

什么都不重要,做你想做的

Everything Everywhere All at Once 电影是极度虚无主义的,我非常喜欢它。影片中的主要反派乔布·图帕基(Jobu Tupaki)已经意识到一切都无关紧要,因此创造了一个无所事事的百吉饼,一个可以消灭所有凝视它的人的东西。这种自我毁灭,正是她自己所追求的。她的母亲伊芙琳也有同样的认识,但得出了不同的结论,那就是,因为什么都不重要,她可以做她想做的事,她想做的就是与她所爱的人共度一生。

我知道无关紧要的一种方式是,我们已经从根本上破坏了气候的稳定,以至于数十亿人将死去,文明将衰落已成定局,而目前的大规模灭绝将继续下去。我们要阻止它已经太晚了,这是在大多数人想要阻止它的背景下进行的,但是我们的公共机构如此腐败,以至于我们只能惊恐地看着它们朝相反的方向发展。确实,“无所谓”。

我想你可以说我是一个生态虚无主义者。

在像今天这样的一天,我还必须指出,美国最高法院以一种破坏性的虚无主义行为进一步证明了我的观点,反对民主多数,扩大枪支权利和限制妇女权利,几乎可以肯定的是在未来几年通过攻击 LGBTQ 权利进一步限制个人自由。

反腐层

顺从会容易得多——这实际上是我周围的每个人都希望我做的事情。但我发现这实际上是不可能的。我们生活在一个无尽空旷的宇宙中的花园世界;它可能是一个天堂,但我们却建造了一台死亡机器。我很生气。愤怒是我的盾牌,但我的虚无主义也越来越成为我的盾牌。什么都不重要,所以我会做我想做的事。

image.png