聊聊 Jetpack Compose

在 Beta 版发布之后,我先去阅读了官方的文档,然后在 Youtube 上面官方发布的 Jetpack Compose 的相关视频也全部看了一遍,之后又去参加了官方组织的 #AndroidDevChalleng 做了实际的上手体验。

今年主要是围绕 Jetpack Compose ,一共是四周的挑战内容,每周会有一个不一样的题目,目的是让你从不同维度熟悉 Jetpack Compose,现在已经进展到了第二周,有兴趣的朋友可以去关注一下。

我现在对 Jetpack Compose 的理解也比较有限,还不是很深入,但是在读完官方的文档之后,我觉得 Jetpack Compose 有很多在设计上让我感到很惊艳的地方,所以就迫不及待的想和大家分享一下。

1. 先来聊聊我个人对 Compose 的使用建议

你可以对它保持关注,官方有任何学习内容看一眼就行了,有个大概印象写个 Demo 玩玩就可以了,千万别激进的在正式项目里使用它。

我之前写过两个 iOS 的独立项目,在最开始我都是用的 SwiftUI 去写的,写的时候很爽,毕竟是新东西,好奇心以及成就感是非常强的,但无一例外,我之后都用传统的命令式 UI 把它们重写了。

因为坑实在是太多了,大部分时间我都在解 Bug ,而且很多时候我都不知道是它本身的 bug 还是我写出的 bug,当然这也和 iOS 本身闭源的原因有关。

在 Jetpack Compose 开发自己的新 feature 之前,它首要的任务,是把现有的 View 组件全部翻译一遍,翻译也是有一个过程的,首先会把 View 表层的东西翻译完,比如如何写一个 TextView,如何写一个 Layout,如何写一个列表,在之后是翻译 View 里层的东西,比如 View 的各种 Listener,各种 Event。

我认为可能要到 Release 2.0 的时候,里层的大多数功能,才会支持完善,看不到的功能缺失,会出现很多不可预估的问题,然后你上网去搜如何解决 XXX 问题,发现大家都和你一样懵逼,不知道怎么办,因为这本身就是它的 bug,只有官方去修复和支持才能彻底解决。

还有一个需要注意的点,就是生态,在生态不丰富之前,你只能选择官方支持的一些解决方案,遇到需要特殊自定义的功能,可能会增加你好几倍的开发成本。

所以,想要在正式项目里使用,一定要谨慎。

2. 传统的 View 体系和 Jetpack Compose 的差异

差异主要在两方面:UI 的构建和 UI 的刷新。

在维基百科中,命令式编程的定义是这样的:通过使用改变程序状态的语句,来实现某些功能和效果。声明式编程的定义是:在不描述其控制流程的情况下表达了计算的逻辑。

用白话来讲就是

命令式编程需要告诉系统每一步要做什么,具体该怎么做,通过这样的一种流程实现我们想要的功能。

声明式编程则是隐藏了怎么做这个流程,你只需要操作一些基本元素,通过组合方式,把你想要的效果搭建出来就好了。

使用声明式 UI 编写界面可以极大的减少代码量,同时也提高了容错率,整个 UI 构建逻辑也变得更加清晰、易读。

另一个差异点在于 UI 的刷新层面

传统的 View 体系中,是通过调用 View 的某些 set 方法来更新 UI 的状态,这是一个手动更新 UI 的过程,这个过程的维护性通常会随着 UI 复杂度的增加而增加,非常容易出错。

在 Compose 里动态的 UI 是和数据绑定的,当数据发生变化的时候,可以自动更新 UI 的状态,完全不需要手动更新,即使 UI 很复杂,我们也只需要专注于数据的更新逻辑就可以了。

从 UI 的构建到 UI 的刷新,其实都是一个由复杂变简单的过程,让开发者可以更专注于业务逻辑的开发。

3. Compose 的核心设计理念

我们还是从 UI 的构建和刷新这两个层面来聊,但这一部分要引入两个新名词:组合和重组

UI 的构建

在 Compose 中,View 不再是某个对象,是以 @Composable 标记的方法存在。

UI 的构建是通过组合不同的 @Composable 元素实现,例如我们要实现一个文本列表,可以选择 Column 元素(纵向列表),然后在内部声明 Text 元素,就可以非常快的实现。所有的 UI 效果都可以通过 Compose 中内置的基本元素组合实现。

@Composable 元素是互相独立的,不存在继承关系,这和传统的命令式编程是完全不一样的,元素的差异性也是通过组合实现的,通过组合不同的内置基本元素来实现一个新的元素结构。

Compose 有一个非常核心的类 Modifier,可以理解为元素增强或元素修饰,每一个 @Composable 元素都可以接受一个 Modifier,通过定义不同的 Modifier 来修改元素的实际表现形式。

推荐大家通过元素组合的方式实现一个新的自定义元素,然后传入不同的 Modifier 而达到自定义 View 的复用效果。

UI 的刷新

在 Compose 中,UI 的刷新是通过重新渲染生成整个屏幕实现的,这是所有声明式 UI 刷新页面的工作原理。

但是 Compose 会根据数据的变化,仅针对需要改变的元素做必要的修改,通过改变数据而重新生成组合 UI 这一过程称为重组。当数据更新了,虽然是从整个屏幕开始重新渲染,但与数据修改无关的元素,会保持之前生成的实例,因为 @Composable 方法渲染是非常快的、幂等且无副作用。

非常重要的一点就是幂等,幂等的意思是指可以使用相同参数重复执行,并能获得相同结果的函数。这些函数不会影响系统状态,也不用担心重复执行会对系统造成改变。说白了就是与数据修改无关的 @Composable 元素在屏幕重新渲染的过程中不会被重组。

整个重组的过程是非常智能的,Compose 编译器会在 @Composable 元素初始化的时候,对每一个元素做标记,然后根据数据是否修改来智能的选择需要被重组的元素。

我们也可以增加额外的辅助信息,帮助 Compose 编译器做更精准的判断,一个典型的例子就是在动态列表中,如果我们在最后添加一条数据,那么编译器就是认为前面的数据是没有任何修改的,仅重组最后一条元素。

但是当我们在 index == 0 的位置,插入一条数据,那么编译器会认为整个列表都被修改了,因为在它的默认标记中会记录位置信息,在 0 的位置插入数据,意味着整个列表都需要被重组。

这个时候,我们可以给列表的内容增加 key 的标记,通常是唯一不变的 id,告诉编译器还需要判断 id 是否变化,此时编译器仅会重组第一条元素,之后的元素依旧使用之前生成好的实例。

在 Compose 中 UI 刷新的唯一方法就是重组,是否重组的判断条件就是与 @Composable 元素绑定的数据是否发生了变化。

重组需要注意的地方

重组意味着系统会重新调用 @Composable 元素方法的执行,而重组可能会随时且非常频繁的发生,所有的 @Composable 方法的执行都是乱序和并行的,这一点我们需要注意的。

所以到 @Composable 方法里面,任何一个 Local 变量,任何一个动态计算方法,任何一个更新数据的逻辑,都需要仔细考虑,不然就会出现不可预估的错误。

可能在重组过程中,Local 变量会失效,动态计算方法会调用很多次,这里其实是一个大坑,希望 Android Studio 以后可以在 @Composable 方法里有严格的代码检查,帮助大家在写的时候就可以检查出一些潜在的错误。

3. 官方推荐的 UI 与数据的组合方式

一种类似于单项数据流的处理方式

动态的 @Composable 元素由上层传递的数据所控制,交互事件通过 Block 的方式再传回上层,通过在上层更新数据,从而实现 UI 的动态更新。数据传递是自上而下的,交互事件的传递是自下而上的。

按照这种逻辑,我们需要在 ViewModel 中定义可变的数据,同时定义好修改数据的方法,把数据自上而下传递到实际的 UI 中,然后将 UI 的交互事件向上传递,通过执行最上层 ViewModel 中修改数据的方法,更新数据。

但这样其实会引发一个新的问题,当我们的 UI 特别复杂的时候, @Composable 方法的参数可能会变得非常多。

这时候就需要你去使用不同的内置基本元素来实现一个新的元素结构,通过组合的方式,优化代码的结构,类似这样。

最后

虽然一眼看上去 Jetpack Compose 是一个全新的东西,但认真阅读了文档之后,其实都很好理解,重点是 Compose 加速了整个 Android 开发的速度,由复杂变简单,让我们可以专注于业务逻辑上的实现。

包括这几年 Jetpack 的快速迭代,目的也是一样的,而且 Compose 和之前 Jetpack 中的那一套架构设计规则是可以无缝使用的,只是把我们之前写 UI 的逻辑改变了。

Jetpack Compose 肯定不是 Android UI 开发的最终形态,但绝对是 Android 技术生态上的一个巨大的进步。

当然还是那句话,想要在正式项目里使用,一定要谨慎,再等等。

往期回顾

猜你喜欢

转载自blog.csdn.net/zhireshini233/article/details/114648492