专栏名称: Android技术之家
主要分享Android相关技术文章、移动互联网的相关产品和资讯。关注你将学习到更多基础以及框架相关的知识,为您的工作助力!
目录
相关文章推荐
51好读  ›  专栏  ›  Android技术之家

解读Compose的项目中的知识点

Android技术之家  · 公众号  ·  · 2024-06-13 08:07

正文

请到「今天看啥」查看全文


本篇文章转自 麦客奥德彪 的博客,文章主要分享了他如何阅读 Compose项目中的知识点 ,相信会对大家有所帮助!


原文 地址:

https://juejin.cn/post/7377439806135795764

/   前言   /


我是一个非常喜欢学习的人,在这行业中摸爬滚打这么多年,靠的就是技术栈从未落后过。 然而,不幸的是,公司不允许使用kotlin, 更别提compose了。


这就尴尬了,如果是我刚毕业那会儿,这无所谓,因为我有大把的时间弥补欠缺,之后的工作中补回来就OK了。


更尴尬的是,我30了,最最尴尬的是,这东西学了不用就全忘了,特别是理论的东西,又没有机会写,只能读项目了。


/   读项目吧   /


读之前也要做些了解的,比如:


  1. Compose 开发中的架构问题

  2. 页面应该怎么写

  3. 常用的控件啊 这些无所谓


我选择的项目是官方的nowinandroid, 他的好处在于它是一个教程类项目,教程类项目肯定有很多相关的 “炫技” 在里面。


阅读方式


这没办法扯,就这吧,当然:


  1. 你可以从MainActivity 中一行行读

  2. 也可以跳着读,遇到不懂的技术点时弄明白,直到没有看不懂的技术为止(小递归,如果每次读下来判断条件没有变化,那就不要读了,不要再学习了,转前端吧!转大前端吧!)


所以我记录的是这个项目中我不懂的一个个技术点。


/ 知识点 Effect及带出来的问题 /


首页第一个看不明白的就是:


 DisposableEffect(darkTheme) {
                //...
            } 


DisposableEffect, 位于Effect.kt 文件中,(这里有个小技巧,kotlin 作为可顶级函数编程模式,导致很多功能相似或相同的函数都位于同一个文件中)

查阅发现一共有以下几种:


  1. SideEffect

  2. LaunchedEffect

  3. DisposableEffect


LaunchedEffect 分析


@Composable
@NonRestartableComposable
@OptIn(InternalComposeApi::class)
fun LaunchedEffect(
    key1: Any?,
    block: suspend CoroutineScope.()
 -> Unit
) {
    val applyContext = currentComposer.applyCoroutineContext
    remember(key1) { LaunchedEffectImpl(applyContext, block) }
}


remember 是什么?


remember 是一个函数,用于在 Composable 函数中记住(缓存)某个值,并在 UI 重新组合时保持该值不变


@Composable
inline fun  remember(
    key1: Any?,
    crossinline calculation: @DisallowComposableCalls ()
 -> T
): T {
    return currentComposer.cache(currentComposer.changed(key1), calculation)
}

@ComposeCompilerApi
inline fun  Composer.cache(invalid: Boolean, block: @DisallowComposableCalls () -> T): T {
    @Suppress("UNCHECKED_CAST")
    return rememberedValue().let {
        if (invalid || it === Composer.Empty) {
            val value = block()
            updateRememberedValue(value)
            value
        } else it
    } as T
}


当currentComposer.changed(key1) 的值发生变化时,会更新存储并且执行calculation。


在LaunchedEffect中的rember 值变化时执行的LaunchedEffectImpl 是什么?


internal class LaunchedEffectImpl(
    parentCoroutineContext: CoroutineContext,
    private val task: suspend CoroutineScope.() -> Unit
) : RememberObserver {
    private val scope = CoroutineScope(parentCoroutineContext)
    private var job: Job? = null

    override fun onRemembered() {
        // This should never happen but is left here for safety
        job?.cancel("Old job was still running!")
        job = scope.launch(block = task)
    }

    override fun onForgotten() {
        job?.cancel(LeftCompositionCancellationException())
        job = null
    }

    override  fun onAbandoned() {
        job?.cancel(LeftCompositionCancellationException())
        job = null
    }
}


实现了RememberObserver。


RememberObserver


RememberObserver 是一个接口,用于管理 remember 状态对象的生命周期。通过实现这个接口,可以在状态对象的创建、进入和离开 Composition 的时候执行特定的逻辑。


RememberObserver 详解


RememberObserver 接口包含三个主要的生命周期回调:


  1. onRemembered:当状态对象被创建并进入 Composition 时调用。

  2. onForgotten:当状态对象离开 Composition 时调用。

  3. onAbandoned:当状态对象由于 Composition 中止或重启而被遗弃时调用。

这些回调使你能够在特定的生命周期阶段执行特定的逻辑,例如启动和清理资源。


接口定义


interface RememberObserver {
    fun onRemembered()
    fun onForgotten()
    fun onAbandoned()
}


示例


以下是一个实现 RememberObserver 的简单示例,用于在组件的生命周期内记录日志:


import androidx.compose.runtime.*

class LoggingRememberObserver(private val tag: String) : RememberObserver {
    override fun onRemembered() {
        println("$tag: Remembered")
    }

    override fun onForgotten() {
        println("$tag: Forgotten")
    }

    override fun onAbandoned() {
        println("$tag: Abandoned")
    }
}

@Composable
fun  RememberObserverExample() {
    val observer = remember { LoggingRememberObserver("MyObserver") }
    DisposableEffect(observer) {
        onDispose { /* Optionally perform cleanup here */ }
    }

    // Your UI content
    Text("RememberObserver Example")
}

@Preview
@Composable
fun PreviewRememberObserverExample() {
    RememberObserverExample()
}


解释


LoggingRememberObserver


  • 实现 RememberObserver 接口,在每个生命周期回调中记录日志。

  • onRemembered、onForgotten、onAbandoned 分别在不同的生命周期阶段调用。


RememberObserverExample


  • 使用 remember 创建 LoggingRememberObserver 实例,并将其与 Composition 关联。

  • DisposableEffect 可以用于处理在 Composition 离开时的清理操作。


UI 内容


显示一个简单的文本,表示示例的 UI 内容。


RememberObserver 的实际应用


RememberObserver 主要用于需要在状态对象的生命周期内执行特定操作的场景。例如:


  • 资源管理:打开和关闭数据库连接、注册和取消注册监听器等。

  • 副作用管理:启动和停止定时任务、管理网络请求的生命周期等。(而Effect 正是利用这个完成他们的功能的)

  • 性能优化:在适当的时候初始化和销毁昂贵的资源。


Composition 是什么


Composition 是一个核心概念,它描述了如何将 UI 的状态与其界面元素关联起来。具体来说,Composition 是一个将可组合函数(Composables)及其状态在运行时结合在一起的过程。


关键点


声明式 UI


在 Jetpack Compose 中,UI 是声明式的,即 UI 是当前状态的函数。UI 会根据状态的变化自动重组(recompose)。


Composable 函数


@Composable 注解的函数可以定义 UI 元素和布局。这些函数可以组合在一起,形成复杂的 UI 层次结构。


Recomposition


当与 UI 相关的状态发生变化时,Compose 会重新运行相应的可组合函数,以更新 UI。这种重新运行称为重组(Recomposition)。


Composition 的生命周期


Composition 的生命周期与 UI 的生命周期密切相关。当某个 Composable 函数第一次执行时,它进入 Composition。当状态变化导致 UI 需要更新时,该函数会重新组合。


示例


以下是一个简单的 Jetpack Compose 示例,展示了 Composition 的基本概念:


import androidx.compose.runtime.*
import androidx.compose.material.*
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.compose.foundation.layout.*

@Composable
fun Greeting(name: String) {
    Text(text = "Hello, $name!")
}

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

    Column(
        horizontalAlignment = Alignment.CenterHorizontally,
        verticalArrangement = Arrangement.Center,
        modifier = Modifier.fillMaxSize()
    ) {
        Text(text = "Count: $count", style = MaterialTheme.typography.h4)
        Spacer(modifier = Modifier.height(16.dp))
        Button(onClick = { count++ }) {
            Text("Increment")
        }
    }
}

@Preview(showBackground = true)
@Composable
fun DefaultPreview() {
    Column {
        Greeting(name = "Compose")
        Counter()
    }
}


解释


Greeting Composable 函数


定义了一个简单的文本显示函数。Greeting(name: String) 会显示一个带有 name 参数值的文本。


Counter Composable 函数


定义了一个计数器组件,显示当前计数并有一个按钮来增加计数。var count by remember { mutableStateOf(0) } 声明并记住状态 count,当 count 变化时,UI 会自动重组。Button(onClick = { count++ }):按钮点击事件会增加 count。


DefaultPreview Composable 函数


用于预览,展示 Greeting 和 Counter 组件的组合。


Composition 是 Jetpack Compose 中将 UI 与其状态结合在一起的过程。可组合函数 是构建 UI 的基本单元,通过组合这些函数,可以构建复杂的 UI。 重组 是当状态变化时重新运行可组合函数以更新 UI 的过程。


解释Effect问题


结合上述的知识点,这个组件的作用就是根据组件的Composition状态,做一些Compose之外的工作。


在 Jetpack Compose 中,Effects 是用于处理副作用(side effects)的工具。副作用是指在 Compose 之外的环境中执行的操作,比如网络请求、数据库访问、订阅和取消订阅等。Compose 提供了一组用于处理这些操作的 API,确保这些操作在 UI 状态变化时能正确执行。


Compose 中的主要 Effects API:


  1. SideEffect

  2. LaunchedEffect

  3. DisposableEffect


SideEffect


SideEffect 是一个简单的效果处理器,用于在每次重组(recomposition)后执行一些操作。它通常用于调试和日志记录。


@Composable
fun SideEffectExample(counter: Int) {
    SideEffect {
        println("The counter value is $counter")
    }
    Text("Counter: $counter")
}


LaunchedEffect


LaunchedEffect 用于启动协程来处理异步操作。它会在 Composable 进入 Composition 时启动,并在离开 Composition 时取消。它的主要特点是可以依赖于传入的键,当键发生变化时重新启动协程。


@Composable
fun LaunchedEffectExample(dataString) {
    var result by remember { mutableStateOf("Loading...") }

    LaunchedEffect(data) {
        // 模拟网络请求
        delay(1000L)
        result = "Result for $data"
    }

    Text(result)
}


DisposableEffect


DisposableEffect 用于处理在 Composition 生命周期内需要清理的副作用。它会在 Composable 进入 Composition 时启动,在离开时执行清理操作。


@Composable
fun DisposableEffectExample(userId: String) {
    DisposableEffect(userId) {
        println("Start observing $userId")

        onDispose {
            println("Stop observing $userId")
        }
    }
    Text("Observing user: $userId")
}


使用示例综合


以下是一个综合示例,展示了如何在实际应用中使用这些 Effects API:


@Composable
fun ComprehensiveExample(userId: String, counter: Int) {
    var userName by remember { mutableStateOf("Loading...") }

    // LaunchedEffect 用于异步加载数据
    LaunchedEffect(userId) {
        // 模拟网络请求
        delay(1000L)
        userName = "User: $userId"
    }

    // DisposableEffect 用于订阅和取消订阅用户状态
    DisposableEffect(userId) {
        println("Start observing $userId")

        onDispose {
            println("Stop observing $userId")
        }
    }



    Column {
        Text(userName)
        Text("Status: $userStatus")

        // SideEffect 用于记录日志
        SideEffect {
            println("Rendering ComprehensiveExample with userId: $userId and counter: $counter")
        }
    }
}

@Preview
@Composable
fun PreviewComprehensiveExample() {
    ComprehensiveExample(userId = "42", counter = 5)
}


总结


  • SideEffect:用于简单的副作用,如日志记录。

  • LaunchedEffect:用于在 Composition 中启动和管理协程,适合异步操作。

  • DisposableEffect:用于在 Composition 生命周期内处理需要清理的副作用。


/ 知识点 CompositionLocalProvider /


CompositionLocalProvider 是 Jetpack Compose 中用于在局部范围内提供数据的工具。它允许你定义和传递局部数据,而不需要通过参数一级一级地传递。这种机制类似于 React 中的 Context API,但在 Jetpack Compose 中使用的是 CompositionLocal。


CompositionLocalProvider 可以在 Compose 的 Composition 树中设置局部数据,这些数据可以在树中的任何子组件中访问。它通常与 CompositionLocal 一起使用。


核心概念


  • CompositionLocal:这是一个可以在 Composition 树中任何地方访问的局部数据的容器。

  • CompositionLocalProvider:这是用于提供 CompositionLocal 数据的 Composable 函数。


定义 CompositionLocal


首先,需要定义一个 CompositionLocal:


import androidx.compose.runtime.compositionLocalOf

data class User(val  name: String, val age: Int)

val LocalUser = compositionLocalOf { error("No user provided") }


在上面的代码中,我们定义了一个 CompositionLocal,用于存储 User 对象。


使用 CompositionLocalProvider


接下来,可以使用 CompositionLocalProvider 在局部范围内提供 User 数据:


import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.material.Text
import androidx.compose.ui.tooling.preview.Preview

@Composable
fun UserInfo() {
    val user = LocalUser.current
    Text("Name: ${user.name}, Age: ${user.age}")
}

@Composable
fun UserScreen() {
    val user = User(name = "John Doe", age = 30)

    CompositionLocalProvider(LocalUser provides user) {
        UserInfo()
    }
}

@Preview
@Composable
fun PreviewUserScreen() {
    UserScreen()
}


解释


定义 LocalUser


compositionLocalOf 定义了一个 CompositionLocal,它存储 User 类型的值。默认情况下,如果没有提供值,会抛出异常。


UserInfo Composable


使用 LocalUser.current 获取当前的 User 对象,并显示其姓名和年龄。


UserScreen Composable


创建一个 User 对象,并使用 CompositionLocalProvider 提供 LocalUser。在 CompositionLocalProvider 的作用范围内,LocalUser 的值将是我们提供的 user 对象。


多个 CompositionLocalProvider


可以在同一个 CompositionLocalProvider 中提供多个 CompositionLocal,如下所示:


import androidx.compose.runtime.staticCompositionLocalOf

val LocalThemeColor = staticCompositionLocalOf { Color.Black }

@Composable
fun ThemedUserInfo() {
    val user = LocalUser.current
    val  color = LocalThemeColor.current
    Text("Name: ${user.name}, Age: ${user.age}", color = color)
}

@Composable
fun ThemedUserScreen() {
    val user = User(name = "Jane Doe", age = 28)
    val themeColor = Color.Blue

    CompositionLocalProvider(
        LocalUser provides user,
        LocalThemeColor provides themeColor
    ) {
        ThemedUserInfo()
    }
}

@Preview
@Composable
fun PreviewThemedUserScreen() {
    ThemedUserScreen()
}


解释


定义 LocalThemeColor


staticCompositionLocalOf 定义了一个静态的 CompositionLocal,它存储 Color 类型的值。


ThemedUserInfo Composable


  • 使用 LocalUser.current 获取当前的 User 对象。

  • 使用 LocalThemeColor.current 获取当前的颜色值,并将其应用到文本颜色上。


ThemedUserScreen Composable


使用 CompositionLocalProvider 同时提供 LocalUser 和 LocalThemeColor。


只要是在 CompositionLocalProvider 的 content 块中调用的所有 Composable 函数,都可以访问 CompositionLocal 提供的数据。这是因为 CompositionLocalProvider 会在其 content 块内设置一个作用域,该作用域内所有的 Composable 都可以访问通过 CompositionLocal 提供的数据。


CompositionLocal的方式都有哪些


有两种主要的方式来定义 CompositionLocal,分别是 compositionLocalOf 和 staticCompositionLocalOf。下面是它们的详细介绍:


compositionLocalOf


compositionLocalOf 是一种在运行时动态创建 CompositionLocal 的方式。它允许你使用一个默认值来定义 CompositionLocal,并在需要的时候通过提供不同的值来替换它。


使用方法:


val LocalUser = compositionLocalOf { error("No user provided") }


  • LocalUser:定义的 CompositionLocal 变量。

  • compositionLocalOf :创建一个 CompositionLocal,其值的类型是 User。

  • { error("No user provided") }:提供一个默认值,当没有提供具体值时将抛出错误。


示例:

val LocalUser = compositionLocalOf { error("No user provided" ) }

@Composable
fun UserInfo() {
    val user = LocalUser.current
    Text("Name: ${user.name}")
}

@Composable
fun UserScreen() {
    val user = User(name = "John Doe", age = 30)

    CompositionLocalProvider(LocalUser provides user) {
        UserInfo()
    }
}


staticCompositionLocalOf


staticCompositionLocalOf 是一种在编译时静态创建 CompositionLocal 的方式。它在声明时就提供了一个默认值,并且该值是不可修改的。


使用方法:

val LocalThemeColor = staticCompositionLocalOf { Color.Black }


  • LocalThemeColor:定义的 CompositionLocal 变量。

  • staticCompositionLocalOf:创建一个静态的 CompositionLocal。

  • { Color.Black }:提供一个默认值,该值在编译时确定,不可更改。


示例:

val LocalThemeColor = staticCompositionLocalOf { Color.Black }

@Composable
fun ThemedUserInfo() {
    val color = LocalThemeColor.current
    Text("User Info", color = color)
}

@Composable
fun ThemedUserScreen() {
    CompositionLocalProvider(LocalThemeColor provides Color.Blue) {
        ThemedUserInfo()
    }
}


区别和适用场景


动态 vs. 静态


  • compositionLocalOf 提供了动态创建 CompositionLocal 的方式,适用于需要在运行时根据条件提供不同值的情况。

  • staticCompositionLocalOf 提供了在编译时确定默认值的方式,适用于值是固定的情况。


默认值的处理


  • compositionLocalOf 允许在提供默认值时执行代码逻辑,例如抛出异常等。

  • staticCompositionLocalOf 只能在编译时确定一个不可修改的默认值。


可变性


无论是动态还是静态创建的 CompositionLocal,其值都是可变的,可以在需要的时候通过 CompositionLocalProvider 提供不同的值。


一般来说,如果需要在运行时根据条件提供不同的默认值,或者需要在提供默认值时执行一些逻辑,那么使用 compositionLocalOf 更为适合。如果你的值是固定的,不会在运行时改变,那么使用 staticCompositionLocalOf 更为合适。

关注我获取更多知识或者投稿








请到「今天看啥」查看全文