Compose 经验分享:开发要点&常见错误&面试题

1.前言

从 Compose 还在 alpha 到现在,用 Compose 完整的从零到一写了三个应用:Twidere X Android、Mask-Android,还有一个暂未公开的项目。

https://github.com/TwidereProject/TwidereX-Android
https://github.com/DimensionDev/Mask-Android

每个应用都有不一样的收获,现将其中的经验集中总结一波,分享给大家,文章不长但是字字珠玑,如果没时间看可以先收藏~

2. 要点总结

直接说几个总结出来的要点吧:

  • Compose UI 最核心的一个思想就是:状态向下,事件向上,Compose UI 组件的状态都应该来自其参数而不是自身,不要在 Compose UI 组件中做任何计算,有非常多的性能问题其实是来自对于这一条核心思想的不理解。
  • 如果一个组件不得不内部持有一些状态,切记将这些状态所有的变量都用上 remember,因为 Compose 函数是会被非常频繁的执行,不用 remember 的话会导致频繁的赋值和初始化,甚至进行一些计算操作。
  • Compose UI 组件的参数最好是不可变(immutable)的,否则最好的情况是遇到和预期表现不符,最差的情况就是影响到性能了。
  • 每个 Compose UI 组件最好都有 Modifier,这样 Compose UI 组件就可以很方便的在不同地方复用。
  • 为了可维护性,请尽量拆分基础 Compose UI 组件和业务 Compose UI 组件,基础 Compose UI 组件尽量拆分的细一些,业务 Compose UI 组件看情况,最好也要拆分的细一些,你不会想去维护一个上千行的 Compose UI 组件的,同时细分也会提高一定的复用率。

下面总结了一些常见的不正确的用法,其中大部分会导致性能问题,有很多人会说 Compose 性能差,但其实更多的是本身的用法有误。

3. 滥用 remember { mutableStateOf() }

Compose UI 最核心的一个思想就是:状态向下,事件向上。这句话举个例子可能会更好理解。一般初学者在看完教程之后马上就会写下这样的代码:

@Composable
fun Counter() {
    var count by remember { mutableStateOf(0) }
    Button(
        onClick = {
            count++
        }
    ) {
        Text("count $count")
    }
}

然后当业务逻辑复杂之后,他的代码可能会像这样:


@Composable
fun Counter() {
    var count by remember { mutableStateOf(0) }
    var text by remember { mutableStateOf("") }

    Column {
        Button(
            onClick = {
                count++
            }
        ) {
            Text("count $count")
        }
        TextField(
            value = text,
            onValueChange = {
                text = it
            }
        )
        OtherCounter()
    }
}

@Composable
fun OtherCounter() {
    var text by remember { mutableStateOf("Hello world!") }
    Column {
        Text(text)
        TextField(
            value = text,
            onValueChange = {
                text = it
            }
        )
    }
}

抛开代码的业务逻辑不谈,这里的 Composable 函数是带状态的,这会带来不必要的 recomposition,从而导致写出来的 Compose UI 出现性能问题,按照核心思想状态向下,事件向上,上面的代码应该这样写:


@Composable
fun CounterRoute(
    viewModel: CounterViewModel = viewModel<CounterViewModel>()
) {
    val state by viewModel.state.collectAsState()
    Counter(
        state = state,
        onIncrement = {
            viewModel.onIncrement()
        },
        onTextChange = {
            viewModel.onTextChange(it)
        },
        onOtherTextChange = {
            viewModel.onOtherTextChange(it)
        },
    )
}

@Composable
fun Counter(
    state: CounterState,
    onIncrement: () -> Unit,
    onTextChange: (String) -> Unit,
    onOtherTextChange: (String) -> Unit,
) {
    Column {
        Button(
            onClick = {
                onIncrement.invoke()
            }
        ) {
            Text("count ${state.count}")
        }
        TextField(
            value = state.text,
            onValueChange = {
                onTextChange.invoke(it)
            }
        )
        OtherCounter(
            text = state.otherText,
            onTextChange = onOtherTextChange,
        )
    }
}

@Composable
fun OtherCounter(
    text: String,
    onTextChange: (String) -> Unit,
) {
    Column {
        Text(text)
        TextField(
            value = text,
            onValueChange = {
                onTextChange.invoke(it)
            }
        )
    }
}

这样的写法吧所有状态都放到顶层,同时事件也交由顶层处理,这样的 Compose UI 组件是没有任何状态的,这样的的 Compose UI 组件会有非常好的性能。

4. 忘记 remember

刚刚说完滥用,现在说忘记。当一个组件不得不内部持有状态的时候,这个时候切记:一定要吧所有的变量都用上 remember。

常见的有这样的错误:


@Composable
fun SomeList() {
    val list = listOf("a", "b", "c")
    LazyColumn {
        items(list) {
            Text(it)
        }
    }
}

这里的 list 完全没有被 remember,而 Compose 函数会非常频繁的执行,这就导致每次执行到 val list = listOf(“a”, “b”, “c”) 的时候都会有一次生成赋值甚至计算的操作,这样的写法是非常影响性能的,正确的写法应该是这样:


@Composable
fun SomeList() {
    val list = remember { listOf("a", "b", "c") }
    LazyColumn {
        items(list) {
            Text(it)
        }
    }
}

当然最好是把 list 移到参数上:


@Composable
fun SomeList(
    list: List<String>,
) {
    LazyColumn {
        items(list) {
            Text(it)
        }
    }
}

5. 参数是可变的

还是接着上一个例子,光是 list 移动到参数还是不够的,因为你可以在 Composable 函数外边更改这个列表,比如执行 list.add(“”) 的操作,Compose 编译器会认为这个 Composable 函数仍然是带状态的,所以还不是最优化的状态。最好是使用 kotlinx.collections.immutable 里面的 ImmutableList:

@Composable
fun SomeList(
    list: ImmutableList<String>,
) {
    LazyColumn {
        items(list) {
            Text(it)
        }
    }
}

除了基础类型之外,其他参数中的自定义 class 最好是标记上 @Immutable,这样 Compose 编译器会优化这个 Composable 函数。当然不要定义一个 data class 然后里面一个 var a: String 然后问为什么 a.a = “b” 没有效果,建议传给 Composable 函数的 data class 全是 val。

6. 没开启 R8

R8 对于 Compose 的提升是非常巨大的,如果是简单 UI 的话没有 R8 可能还可以用,复杂 UI 下非常推荐开启 R8,代码优化之后的性能的 Debug 的性能差距极大。

7. 最后:面试题推荐

其实理解了 Compose UI 的核心思想之后,写出来的 Compose 程序应该不会有什么性能问题,而且在这个核心思想下写出来的 Compose UI 逻辑非常的清晰,因为整个 UI 是无状态的,你只需要关系在什么状态下这个 UI 显示的是什么样的,心智负担非常小。

最后推荐一些 Compose 相关的面试题,大家可以做一个自我测试,如果你能回答的七七八八,那么恭喜你,可能已经击败 95% 的同行了。

  • Jetpack Compose有了解吗?和传统Android UI有什么不同?
  • DisposableEffect、SideEffect、LaunchedEffect之间的区别?
  • pointer事件在各个Composable function之间是如何处理的?
  • 自定义Layout?
  • CompositionLocal起什么作用?staticCompositionLocalOf和compositionLocalOf有什么区别?
  • Composable function的状态是如何持久化的?
  • LazyColumn是如何做Composable function缓存的?
  • 如何解决LazyColumn和其他Composable function的滑动冲突?
  • @Composable的作用是什么?
  • Jetpack Compose是用什么渲染的?执行流程是怎么样的?与flutter/react那样做diff有什么区别/优劣?
  • Jetpack Compose多线程执行是如何实现的?
  • 什么是有状态的 Composable 函数?什么是无状态的 Composable 函数?
  • Compose 的状态提升如何理解?有什么好处?
  • 如何理解 MVI 架构?和 MVVM、MVP、MVC 有什么不同的?
  • 在 Android 上,当一个 Flow 被 collectAsState,应用转入后台时,如果这个 Flow 再进行更新,对应的 State 会不会更新?对应的 Composable 函数会不会更新?

说了这么多也没有说如何学习,在之后这份谷歌发布的Jetpack Compose开发指南中您将学习:

  • 你可以遵循的不同迁移路径
  • 如何逐步将应用迁移到Compose
  • 如何将Compose添加到使用View构建的现有界面
  • 如何在Compose中使用View
  • 如何在Compose中使用基于View的主题
  • 如何测试使用View和Compose编写的混合界面

Android Jetpack Compose开发应用指南

第⼀章 初识Jetpack

  • JetPack是什么
  • JetPack和AndroidX
  • AndroidX的迁移

在这里插入图片描述

第⼆章 Compose的设计原理和基本概念

  • JetPack Compose 环境搭建
  • JetPack Compose 新特性和组件依赖
  • JetPack Compose 编程思想总结

在这里插入图片描述

第三章 Compose⼊⻔

  • JetPack Compose ⼊⻔的基础案列
  • JetPack Compose ⼊⻔的基础案列
    在这里插入图片描述

第四章 Compose布局

  • Compose State
  • Compose 样式(Theme)
  • Compose布局核⼼控件
  • ⾃定义布局
  • Compose中的ConstraintLayout

第五章 Compose动画

  • Compose SideEffect
  • Compose 动画概述
  • Compose Crossfade
  • Compose animateContentSize
  • Animatable
  • Compose⾃定义动画

第六章 Compose图形

  • Compose Canvas
  • Compose 绘制API的分析
  • Compose⾃定义绘制
    在这里插入图片描述

第七章 Compose核⼼控件总结

  • Scaffold
  • LazyColumn
    在这里插入图片描述

有需要的朋友可以【点击下方卡片】免费下载

  • 1
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值