Android Jetpack Compose 相关调研

Android Jetpack Compose 相关调研 —— 声明式UI

Compose 从一出现,最受到官方推崇以及关注者赞扬的就是它实现了声明式 UI,而我们传统的书写方式叫做「命令式 UI」,那么什么叫做「命令式」?

举个例子来说,当我们希望展示一个控件View时,我们需要在代码块中调用View.setVisibility()方法,这样的方式就是一个“命令”,即我们需要以命令的口吻来告诉一个View该怎么做。

对于大多数刚刚接触Compose的Android 开发者来说,我们的第一个问题就是:什么是「声明式 UI」?

在具体说明什么是「声明式 UI」,我们首先看一下Compose的代码是长什么样子的。Compose是由Kotlin语言编写的,每一个控制模块都是一个函数调用。比如想要展示一段文字,可以按照以下的写法:

Text("Hello")

看起来很像调用一个构造函数创建了一个对象,实际上已近在界面上展示了一段文字。但是实际上并没有创建一个对象,而且Text()也不是一个构造函数,只是一个普通函数。

我们接着看一段代码:

Text("Hello")

...

@Composable

fun Text(...) {

    ...

}

虽然函数的开头大写了,但是没有什么特殊含义,只是为了提高辨识度。在Compose中,规定这种以大写字母开头的方式,来标识这个是一个Compose函数,更为官方的叫法是这是一个Composable。

那么这个Text()实际上是个什么呢?TextView?答案肯定是No。在Compose中已经舍弃了原来的一套逻辑,其中的渲染机制、布局机制、触摸机制都是全部重写的。所以Text()不是一个View,也不是一个原生的控件,而是调用了更下层的API,即Canvas的那一部分。所以Compose中的组件都是一个全新的实现。

一个函数调用可算看成一个组件,两个函数调用就是两个组件,而这些函数组合在一起就构成了一个界面。这个就是Compose的写法,看到这里应该就明白了什么是「声明式 UI」。

Column {

    Text("Hello")

    Image()

}

传统的,当我们想去构建一个界面时,我们会去编写一个XML文件,来描述界面的构成方式。那么上面的类似的界面,可以被如下表示:

<!-- 代码有一定简化 -->

<LinearLayout>

  <TextView android:text="Hello" />

  <ImageView />

</LinearLayout>

和上面的Compose形式做对比,可以发现其实两者和相近,就是一些语法差异,那么为什么Compose的就算作声明式,而传统的则是命令式呢?

实际上「声明式 UI」,指的是你只需要把界面给「声明」出来,而不需要手动更新。关键在于「不需要手动更新」。比如传统布局里的 TextView,如果它对应的数据改变了,我们需要在代码块中调用findViewById()和setText() 方法来进行更新,让界面展示新的内容。

而在Compose中是怎么去更新数据的呢?其实是不需要我们进行手动调用的,当数据发生变化时,Compose的界面会自动更新数据。

Compose 会对界面中用到的数据自动进行订阅——不管是字符串还是图像还是别的什么,Compose 全部能够自动订阅——这样当数据改变的时候,Compose 会直接把新的数据更新到界面。

想要使用「自动订阅」的功能,需要在数据初始化的时候加上一个 by mutableStateOf() ,剩下的全都由 Compose 自动搞定。类似写法如下所示:

var text by mutableStateOf("Hello")

...

Column {

    Text(text)

    Image()

}

这个功能是利用 Kotlin 的 Property Delegation 属性委托来实现的,这也是为什么Compose只能用Kotlin来写的一个原因。

传统的 xml 写法和 Compose 的 Kotlin 写法,为什么一个是「命令式」,一个是「声明式」?这个问题其实本身就是错的。单单一段 xml 代码并不能称作是命令式 UI。传统写法的「命令式」并不在于 xml 部分,而在于 Java 部分:Java 代码去指挥、去命令界面更新,这才是「命令式」的含义所在;而 Compose 通过订阅机制来自动更新,所以不需要做这种「命令」,所以是「声明式」。

所以你看,不管是声明式还是命令式,跟 xml 和 Kotlin 是无关的,它们并不是语言角度的定义,也不是写法角度的定义,而是——功能角度。一个 UI 框架,如果可以让开发者只声明出界面的样子,而不用去写各种界面更新的代码,它就是一个声明式的 UI 框架。换句话说,如果 Android 可以让我们用 xml 写的界面也和数据做关联,让界面自动更新而不需要开发者手写更新代码,那么它就也是声明式 UI。声明式 UI 是一种强大的功能,而不是一种优秀的代码风格。

Android Jetpack Compose相关调研 —— 生命周期和副作用

与Activity和Fragment类似,可组合项(Compose)也有着生命周期,并且其中还有一个很重要的概念——副作用,接下来将详细介绍着两个部分。

Composable的生命周期

正如之前的文档所介绍,一个组合将描述一个应用的界面,并且通过运行组合来生成。组合是描述界面的可组合项的树状结构。

当 Jetpack Compose 首次运行可组合项时,在初始组合期间,它将跟踪您为了描述组合中的界面而调用的可组合项。然后,当应用的状态发生变化时,Jetpack Compose 会安排重组。

重组是指 Jetpack Compose 重新执行可能因状态更改而更改的可组合项,然后更新组合以反映所有更改。

组合只能通过初始组合生成且只能通过重组进行更新。重组是修改组合的唯一方式。

接下来,将展示一下Composable的生命周期:

其中 onActive():表示Composable首次进入组件树;onCommit():表示UI随着Recomposition变化时;onDiapose():表示Composable从组件树中移除时。

Android Jetpack Compose 相关调研 —— 问题引入

引言

Jetpack Compose 是一个适用于 Android 的新式声明性界面工具包。Compose 提供声明性 API,可在不以命令方式改变前端视图的情况下呈现应用界面,从而使编写和维护应用界面变得更加容易。

2019 年中,Google 在 I/O 大会上公布了 Android 最新的 UI 框架:Jetpack Compose。Compose 可以说是 Android 官方有史以来动作最大的一个库了。它在 2019 年中就公布了,但要到今年也就是 2021 年才会正式发布。这两年的时间 Android 团队在干嘛?在开发这个库,在开发 Compose。一个 UI 框架而已,为什么要花两年来打造呢?因为 Compose 并不是像 RecyclerViewConstraintLayout 这种做了一个或者几个高级的 UI 控件,而是直接抛弃了我们写了 N 年的 View 和 ViewGroup 那一套东西,从上到下撸了一整套全新的 UI 框架。直白点说就是,它的渲染机制、布局机制、触摸算法以及 UI 的具体写法,全都是新的。

问题背景

关注点分离 (Separation of concerns, SOC) 是一个众所周知的软件设计原则,这是我们作为开发者所要学习的基础知识之一。然而,尽管其广为人知,但在实践中却常常难以把握是否应当遵循该原则。面对这样的问题,从 "耦合" 和 "内聚" 的角度去考虑这一原则可能会有所帮助。编写代码时,我们会创建包含多个单元的模块。"耦合" 便是不同模块中单元之间的依赖关系,它反映了一个模块中的各部分是如何影响另一个模块的各个部分的。"内聚" 则表示的是一个模块中各个单元之间的关系,它指示了模块中各个单元相互组合的合理程度。在编写可维护的软件时,我们的目标是最大程度地减少耦合并增加内聚。

当我们处理紧耦合的模块时,对一个地方的代码改动,便意味对其他的模块作出许多其他的改动。更糟的是,耦合常常是隐式的,以至于看起来毫无关联的修改,却会造成了意料之外的错误发生。关注点分离是尽可能的将相关的代码组织在一起,以便我们可以轻松地维护它们,并方便我们随着应用规模的增长而扩展我们的代码。

让我们在当前 Android 开发的上下文中进行更为实际的操作,并以视图模型 (view model) 和 XML 布局为例:

视图模型会向布局提供数据。事实证明,这里隐藏了很多依赖关系: 视图模型与布局间存在许多耦合。一个更为熟悉的可以让您查看这一清单的方式是通过一些 API,例如 findViewByID。使用这些 API 需要对 XML 布局的形式和内容有一定了解。使用这些 API 需要了解 XML 布局是如何定义并与视图模型产生耦合的。由于应用规模会随着时间增长,我们还必须保证这些依赖不会过时。

大多数现代应用会动态展示 UI,并且会在执行过程中不断演变。结果导致应用不仅要验证布局 XML 是否静态地满足了这些依赖关系,而且还需要保证在应用的生命周期内满足这些依赖。如果一个元素在运行时离开了视图层级,一些依赖关系可能会被破坏,并导致诸如 NullReferenceExceptions 一类的问题。

通常,视图模型会使用像 Kotlin 这样的编程语言进行定义,而布局则使用 XML。由于这两种语言的差异,使得它们之间存在一条强制的分隔线。然而即使存在这种情况,视图模型与布局 XML 还是可以关联得十分紧密。换句话说,它们二者紧密耦合。

这就引出了一个问题: 如果我们开始用相同的语言定义布局与 UI 结构会怎样?如果我们选用 Kotlin 来做这件事会怎样?

由于我们可以使用相同的语言,一些以往隐式的依赖关系可能会变得更加明显。我们也可以重构代码并将其移动至那些可以使它们减少耦合和增加内聚的位置。

现在,您可能会以为这是建议您将逻辑与 UI 混合起来。不过现实的情况是,无论您如何组织架构,您的应用中都将出现与 UI 相关联的逻辑。框架本身并不会改变这一点。

不过框架可以为您提供一些工具,从而帮您更加简单地实现关注点分离: 这一工具便是 Composable 函数,长久以来您在代码的其他地方实现关注点分离所使用的方法,您在进行这类重构以及编写简洁、可靠、可维护的代码时所获得的技巧,都可以应用在 Composable 函数上。

相关参考:

深入详解 Jetpack Compose | 优化 UI 构建 - 知乎

https://developer.android.com/jetpack/compose/mental-model?hl=zh-cn

猜你喜欢

转载自blog.csdn.net/cpcpcp123/article/details/121662781