1. 声明性编程范式
传统视图层次结构的挑战:
- Android传统视图层次结构以界面widget树的形式表示。
- 应用状态变化时,需手动更新视图层次结构以显示最新数据。
- 常见更新方式包括使用
findViewById()等函数遍历树,并通过调用button.setText()、container.addChild()等方法更改节点状态。 - 手动操纵视图易出错,尤其是数据在多个位置呈现时,可能忘记更新某个视图,或导致非法状态。
声明性界面模型的兴起:
- 近年来,行业转向声明性界面模型,简化了构建和更新界面的工程任务。
- 声明性模型概念上从头开始重新生成整个屏幕,但仅执行必要的更改,避免了手动更新有状态视图层次结构的复杂性。
Jetpack Compose的声明性特性:
- Jetpack Compose是一个声明性界面框架,简化了应用界面的编写和维护。
- 通过定义接受数据并发出界面元素的可组合函数来构建界面。
2. 可组合函数的核心概念
可组合函数定义:
- 使用
@Composable注解标记,告知Compose编译器该函数用于将数据转换为界面。 - 接受参数以描述界面,不返回任何内容,因为它们描述目标屏幕状态。
可组合函数的属性:
- 快速:执行效率高,避免在界面更新时造成卡顿。
- 幂等:使用相同参数多次调用时,行为一致。
- 无附带效应:不修改全局变量或产生对其他部分可见的更改。
示例:Greeting Widget:
@Composable
fun Greeting(name: String) {
Text("Hello $name")
}
- 接受一个String参数,用于在界面上显示问候消息。
3. 声明性范式转变
命令式与声明式的对比:
- 命令式:通过实例化widget树初始化界面,通常通过膨胀XML布局文件实现。每个widget维护自己的内部状态,提供getter和setter方法与应用逻辑交互。
- 声明式:widget相对无状态,不提供setter或getter函数。通过调用带有不同实参的同一可组合函数来更新界面,简化了向架构模式(如ViewModel)提供状态的过程。
界面更新流程:
- 应用逻辑为顶级可组合函数提供数据。
- 可组合函数通过调用其他可组合函数描述界面,并传递数据。
- 用户与界面交互时,触发事件通知应用逻辑,应用逻辑改变状态后,系统使用新数据再次调用可组合函数,导致界面元素重新绘制(重组)。
4. 动态内容与灵活性
动态界面构建:
- 可组合函数用Kotlin编写,可像其他Kotlin代码一样动态。
- 示例:构建一个问候多个用户的界面。
@Composable
fun Greeting(names: List<String>) {
for (name in names) {
Text("Hello $name")
}
}
- 接受名称列表,为每个用户生成问候语。
条件与循环:
- 可组合函数内可使用if语句、循环等,拥有底层语言的全部灵活性。
5. 重组机制
重组定义:
- 输入更改时,再次调用可组合函数的过程。
- Compose根据新输入智能重组,仅调用可能已更改的函数或lambda,跳过其余部分。
重组优化:
- 避免重组整个界面树,减少计算量和电池消耗。
- 重组是乐观的操作,可能会被取消。如果参数在重组完成前更改,Compose会取消当前重组,使用新参数重新开始。
可组合函数编写建议:
- 保持快速:避免在可组合函数中执行成本高昂的操作。
- 幂等且无附带效应:确保函数行为一致,不产生对其他部分可见的更改。
- 使用参数传递数据:避免从共享偏好设置等源头直接读取数据,应在后台协程中读取,并将结果作为参数传递。
6. 可组合函数的执行特性
执行频率:
- 可组合函数可能非常频繁地运行,如界面动画的每一帧。
并行执行:
- Compose可能并行运行可组合函数,利用多个核心优化重组。
- 可组合函数可能在后台线程池中执行,调用可能发生在不同线程上。
执行顺序:
- 可组合函数可以按任何顺序执行,不保证按代码出现顺序运行。
- 每个可组合函数应保持独立,不依赖其他函数的执行顺序或附带效应。
7. 最佳实践与注意事项
避免附带效应:
- 切勿在可组合函数中产生附带效应,如写入共享对象属性、更新ViewModel中的可观察项等。
- 应通过回调触发附带效应,确保在界面线程上执行。
线程安全:
- 编写可组合函数时考虑多线程环境,避免使用非线程安全的代码。
- 不应在可组合lambda中修改变量,因为这可能导致不可预测的行为。
资源利用:
- 合理利用资源,避免在可组合函数中执行阻塞操作,以免影响界面性能。
通过遵循这些编程思想和最佳实践,可以更有效地使用Jetpack Compose构建高效、灵活且易于维护的Android应用界面。