Compose的lazyColumn如何高效设计?

437 阅读5分钟

涉及技术点: AutoService、LaunchedEffect

AutoService原理

AutoService 是一个用于自动注册服务的库,主要用于 Java 和 Kotlin 项目中。它的原理基于 Java 的注解处理机制,具体来说,使用了 Java 的服务提供者接口(SPI)来实现服务的自动注册。以下是 AutoService 的工作原理的详细说明:

1. 注解处理

AutoService 通过注解处理器来扫描代码中使用了 @AutoService 注解的类。当你在某个类上使用 @AutoService 注解时,注解处理器会在编译时生成相应的服务描述文件。

2. 生成服务描述文件

在使用 @AutoService 注解的类中,注解处理器会生成一个 META-INF/services/<service-interface> 文件。这个文件的内容是实现了该接口的类的全限定名(fully qualified name)。例如,如果你有一个接口 MyService,并且有一个实现类 MyServiceImpl,那么生成的文件内容可能是:

com.example.MyServiceImpl

3. 使用 ServiceLoader 加载服务

在运行时,Java 提供的 ServiceLoader 类可以根据上述生成的服务描述文件来加载服务实现。你可以通过以下方式加载服务:

ServiceLoader<MyService> loader = ServiceLoader.load(MyService.class);
for (MyService service : loader) {
    service.performAction();
}

ServiceLoader 会查找 META-INF/services/MyService 文件,并加载其中列出的所有实现类。

4. 优势

  • 自动注册:开发者无需手动注册服务,减少了代码的重复和出错的可能性。
  • 解耦:实现类与使用类之间的耦合度降低,便于扩展和维护。
  • 灵活性:可以轻松添加新的服务实现,只需实现接口并添加 @AutoService 注解。

总结

AutoService 通过注解处理和服务提供者接口的机制,简化了服务的注册和加载过程,使得 Java 和 Kotlin 项目的扩展性和维护性得到了提升。通过使用 @AutoService 注解,开发者可以轻松地实现和注册服务,而无需编写繁琐的注册代码。

LaunchedEffect的原理

LaunchedEffect 是 Jetpack Compose 中的一个用于处理副作用的 API。它允许你在 Composable 函数中启动协程,以便执行异步操作或在特定条件下执行某些代码。下面是关于 LaunchedEffect 的详细解释及其原理。

LaunchedEffect 的功能

  1. 启动协程LaunchedEffect 会在 Composable 进入组合时启动一个协程。你可以在这个协程中执行异步操作,例如网络请求、数据库查询或其他需要时间的任务。

  2. 响应状态变化:你可以为 LaunchedEffect 提供一个或多个键(key),当这些键的值发生变化时,LaunchedEffect 会自动重新启动协程。这使得你可以在状态或参数变化时重新执行某些操作。

  3. 生命周期感知LaunchedEffect 会自动管理协程的生命周期。当 Composable 被移除时,协程会被取消,避免潜在的内存泄漏。

使用示例

下面是一个简单的使用示例,展示如何在 Composable 中使用 LaunchedEffect 来加载数据:

import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.remember
import kotlinx.coroutines.delay

@Composable
fun MyComposable() {
    // 记住一个状态
    val data = remember { mutableStateOf("Loading...") }

    // 使用 LaunchedEffect 启动协程
    LaunchedEffect(Unit) {
        // 模拟网络请求
        delay(2000) // 延迟 2 秒
        data.value = "Data loaded!" // 更新状态
    }

    // 显示数据
    Text(text = data.value)
}

原理

  1. 组合与重组LaunchedEffect 在 Composable 进入组合时启动协程,并在键的值发生变化时重新启动。这意味着当 Composable 重新组合时,LaunchedEffect 会根据提供的键判断是否需要重新执行协程。

  2. 协程作用域LaunchedEffectCoroutineScope 内部启动协程,这个作用域是与 Composable 的生命周期相同的。当 Composable 被移除时,协程会自动取消。

  3. 状态管理LaunchedEffect 允许你在协程中更新状态,Compose 会自动重新组合 UI 以反映状态的变化。

小结

LaunchedEffect 是 Jetpack Compose 中处理副作用和异步操作的强大工具。它使得在 Composable 中执行异步任务变得简单且安全,避免了在传统 Android 开发中常见的内存泄漏和生命周期管理问题。通过合理使用 LaunchedEffect,你可以有效地管理 UI 的状态和数据加载。

AutoService & LazyColumn & LaunchedEffect 配合使用

引入autoService


[versions]
autoservice="1.1.1"

[libraries]
# 添加autoservice依赖
autoservice={group="com.google.auto.service",name="auto-service",version.ref="autoservice"}
autoservice_annotations={group="com.google.auto.service",name="auto-service-annotations",version.ref="autoservice"}

修改项目级别的build.gradle.kts

plugins {
    
    kotlin("kapt")
}

dependencies {
    implementation(libs.autoservice.annotations)
    kapt(libs.autoservice)
}

新增列表类型的接口ListItemInterface

package com.example.androidx.loadmore

import androidx.compose.runtime.Composable

interface ListItemInterface {
    val id: String

    @Composable
    fun render()
}

新增文本类型

package com.example.androidx.loadmore

import android.util.Log
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import com.google.auto.service.AutoService

// 创建TextItem类型
@AutoService(ListItemInterface::class)
class TextItem() : ListItemInterface {

    override val id: String = "text_item_${System.currentTimeMillis()}"

    @Composable
    override fun render() {
        Log.d("TextItem", "render: ${id}")
        Text(text = "Text Item lala")
    }

}

新增图片类型

package com.example.androidx.loadmore

import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.size
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.unit.dp
import com.example.androidx.R
import com.google.auto.service.AutoService

// 创建ImageItem类型
@AutoService(ListItemInterface::class)
class ImageItem : ListItemInterface {

    override val id: String
        get() = "image_item_${System.currentTimeMillis()}"

    @Composable
    override fun render() {
        Box(
            modifier = Modifier.size(200.dp, 200.dp), // Set the size of the Box
            contentAlignment = Alignment.Center // Center the content within the Box
        ) {
            Image(
                painter = painterResource(id = R.drawable.ic_launcher),
                contentDescription = "My image description", // Provide a meaningful description
                modifier = Modifier.fillMaxSize(), // Fill the Box size
                contentScale = ContentScale.Fit // Scale the image to fit within the Box
            )
        }
    }
}
package com.example.androidx.loadmore

import java.util.ServiceLoader

// 创建ItemLoader用来加载不同类型的类
class ItemLoader {
    fun loadItems(): List<ListItemInterface> {
        return ServiceLoader.load(ListItemInterface::class.java).toList()
    }
}
package com.example.androidx.loadmore

import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.mutableStateListOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.snapshotFlow
import androidx.compose.ui.platform.LocalContext
import kotlinx.coroutines.launch

// 自定义列表布局
@Composable
fun ItemList() {

    val context = LocalContext.current
    val itemLoader = remember { ItemLoader() }

    // 创建一个可变状态列表以及存储要显示的内容
    val datas = remember{ mutableStateListOf<ListItemInterface>()}
    // 记住lazyColumn状态
    val listState = rememberLazyListState()
    // 携程作用域
    val coroutineScope = rememberCoroutineScope()

    // 初始化加载
    LaunchedEffect(Unit) {
        datas.addAll(itemLoader.loadItems())
    }

    // 监听滚动到底部
    LaunchedEffect(listState) {
        // 创建一个快照流以监视当前可见项的信息
        snapshotFlow { listState.layoutInfo.visibleItemsInfo }
            .collect{ visibleItems->
                // 检查最后一个可见项是否是当前数据列表的最后一项
                if(visibleItems.lastOrNull()?.index == datas.size - 1) {
                    // 启动协程加载更多的数据
                    coroutineScope.launch {
                        val newItems = itemLoader.loadItems()
                        datas.addAll(newItems)
                    }
                }
            }
    }

    // 展示列表
    LazyColumn (state = listState){
        // 遍历datas列表中的每项数据
        items(datas) { item->
            // 调用每一项的render方法
            item.render()
        }
    }
}
package com.example.androidx

import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.tooling.preview.Preview
import com.example.androidx.loadmore.ItemList

class MainActivity : ComponentActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            // 调用自定义列表
            ItemList()
        }
    }
}