Kotlin 高阶工程化与 Android 深入实践

Kotlin 高阶工程化与 Android 深入实践

本文是 kotlin_advanced_android.md 的高阶篇。它不再只讲"语法怎么写",而是讲 Kotlin 在真实 Android 项目中的工程化能力:编译产物、协程调度、Flow 共享、Compose 状态模型、KSP、API 设计、性能优化、测试和架构落地。目标是让你不仅能用 Kotlin,还能判断什么时候该用、为什么这么用、出了问题怎么查。

适合谁看

  • 已经能写基础 Kotlin 和常见 Android 页面。
  • 知道 data classsealed class、Lambda、协程、Flow 的基本用法。
  • 想从"会写"进阶到"能设计、能排查、能优化"。
  • Java 转 Kotlin 后,想理解 Kotlin 背后的 JVM 和 Android 运行时影响。

学习路线

模块 核心问题 Android 落点
编译模型 Kotlin 最后变成什么 Java 字节码、DEX、混淆、方法数
API 设计 怎么写稳定好用的 Kotlin API Repository、UseCase、SDK、组件库
协程底层 协程如何挂起、恢复和取消 ViewModel 请求、生命周期任务
Flow 架构 冷流热流如何选 UI State、事件、缓存、搜索
Compose 思维 状态如何驱动 UI 重组、稳定性、状态提升
KSP / 注解 编译期生成代码怎么工作 Room、Moshi、Hilt、路由
性能 Kotlin 写法如何影响运行成本 列表、主线程、内存、启动
测试 异步状态怎么测 coroutine-test、Turbine、ViewModel 测试
混编 Kotlin 和 Java 怎么互相友好 老项目迁移、公共库 API

1. Kotlin 编译后是什么

核心理解

Kotlin 在 Android 上通常会编译成 JVM 字节码,然后再经过 Android 构建流程变成 DEX。运行时看到的不是 .kt 文件,而是编译后的类、方法、字段、状态机和辅助类。

伪代码

text 复制代码
Kotlin 源码:
    .kt 文件
Kotlin 编译器:
    生成 JVM 字节码
Android 构建:
    字节码转换成 DEX
最终运行:
    ART 执行 DEX

为什么 Android 开发要关心

  • Lambda、默认参数、协程、委托属性都可能生成额外代码。
  • 过度抽象会影响方法数、启动、内存和堆栈可读性。
  • 混淆时要理解生成类和反射使用。

示例

kotlin 复制代码
fun greet(name: String = "Android") {
    println("Hello, $name")
}

为了支持默认参数,编译器可能生成辅助方法。你写起来很简洁,但公共 API 面向 Java 调用时要考虑兼容性。


2. 默认参数和 Java 调用边界

Kotlin 写法

kotlin 复制代码
class ToastHelper {
    fun show(
        message: String,
        duration: Int = Toast.LENGTH_SHORT
    ) {
        Toast.makeText(context, message, duration).show()
    }
}

Java 调用问题

Java 不能天然理解 Kotlin 默认参数。如果这是给 Java 调用的 API,可以使用 @JvmOverloads

kotlin 复制代码
class AvatarView @JvmOverloads constructor(
    context: Context,
    attrs: AttributeSet? = null,
    defStyleAttr: Int = 0
) : View(context, attrs, defStyleAttr)

原理

text 复制代码
Kotlin 默认参数:
    Kotlin 调用方由编译器补参数
Java 调用方:
    看不到默认参数语义
@JvmOverloads:
    编译器额外生成多个重载方法或构造函数

Android 使用建议

  • 自定义 View 构造函数常用 @JvmOverloads
  • SDK、组件库、工具库如果给 Java 调用,也要考虑 @JvmOverloads
  • 只在 Kotlin 内部使用的函数,不必为了 Java 生成多余重载。

3. Kotlin API 设计原则

好 API 的特点

  • 调用处清楚。
  • 参数不容易传错。
  • 空值和错误状态表达明确。
  • Java 调用边界可控。
  • 不把实现细节暴露出去。

示例:不够清楚

kotlin 复制代码
fun open(id: Long, type: Int, force: Boolean)

调用时很难读:

kotlin 复制代码
open(1001L, 2, true)

改进

kotlin 复制代码
@JvmInline
value class UserId(val value: Long)

enum class OpenMode {
    Normal,
    ForceRefresh
}

fun openUser(id: UserId, mode: OpenMode)

调用处更清楚:

kotlin 复制代码
openUser(UserId(1001L), OpenMode.ForceRefresh)

Android 落点

  • Repository 方法参数。
  • UseCase 入参。
  • 页面路由参数。
  • 公共组件配置。

4. 空安全的工程化边界

基础原则

不要把 String?User? 一路传到所有层。越靠近数据源,越应该处理不可信空值;越靠近 UI,状态越应该明确。

示例

kotlin 复制代码
data class UserDto(
    val id: Long?,
    val name: String?
)

data class User(
    val id: UserId,
    val displayName: String
)

fun UserDto.toDomain(): User? {
    val safeId = id ?: return null
    return User(
        id = UserId(safeId),
        displayName = name ?: "未命名用户"
    )
}

伪代码

text 复制代码
网络层:
    字段可能为空
Mapper:
    丢弃无效数据或补默认值
Domain 层:
    尽量使用非空类型
UI 层:
    根据明确状态渲染

Android 使用建议

  • IntentBundle 参数进入页面时尽早校验。
  • DTO 转 Domain 时消化空值。
  • ViewModel 暴露给 UI 的状态尽量不要有大量可空字段。

5. inline 的成本和边界

什么时候适合

  • 小型高阶函数。
  • 泛型需要 reified
  • 性能敏感且 Lambda 调用频繁。
kotlin 复制代码
inline fun View.onClick(crossinline action: () -> Unit) {
    setOnClickListener {
        action()
    }
}

什么时候不适合

  • 函数体很大。
  • 被很多地方调用,导致字节码膨胀。
  • 只是为了看起来高级。

伪代码

text 复制代码
inline 前:
    调用函数 -> 执行函数体 -> 调用 Lambda
inline 后:
    编译器把函数体复制到调用处
好处:
    少一些调用和对象成本
代价:
    调用处变多时,产物可能变大

Android 落点

Kotlin 标准库中很多集合操作、作用域函数都使用 inline。你写自己的工具函数时,可以参考它们的使用边界。


6. reified 与类型安全路由

常见写法

kotlin 复制代码
inline fun <reified T : Activity> Context.openActivity(
    block: Intent.() -> Unit = {}
) {
    val intent = Intent(this, T::class.java).apply(block)
    startActivity(intent)
}

调用:

kotlin 复制代码
openActivity<DetailActivity> {
    putExtra("id", 1001L)
}

原理

普通泛型运行时类型会被擦除,reified 依赖 inline,让编译器在调用处知道真实类型。

text 复制代码
调用 openActivity<DetailActivity>()
编译器展开函数:
    T::class.java 替换为 DetailActivity::class.java

Android 使用建议

  • 适合内部路由小工具。
  • 不要把复杂页面协议都塞进 Intent Lambda,复杂参数更适合封装成明确方法。
  • 对大型项目,优先考虑 Navigation、路由框架或统一页面协议。

7. 委托属性的真实价值

自定义 SharedPreferences Delegate

kotlin 复制代码
class StringPreference(
    private val prefs: SharedPreferences,
    private val key: String,
    private val defaultValue: String
) {
    operator fun getValue(thisRef: Any?, property: KProperty<*>): String {
        return prefs.getString(key, defaultValue) ?: defaultValue
    }

    operator fun setValue(thisRef: Any?, property: KProperty<*>, value: String) {
        prefs.edit().putString(key, value).apply()
    }
}

使用:

kotlin 复制代码
class UserSettings(prefs: SharedPreferences) {
    var token by StringPreference(prefs, "token", "")
}

原理

text 复制代码
读取 settings.token:
    调用 delegate.getValue()
写入 settings.token:
    调用 delegate.setValue()

Android 使用建议

  • 属性委托适合封装重复的 get/set 模板代码。
  • 注意线程和 IO 成本,不要在主线程读写重数据。
  • DataStore 比 SharedPreferences 更适合新项目的异步配置存储。

8. 类委托和组合优先

示例

kotlin 复制代码
interface Analytics {
    fun track(event: String)
}

class FirebaseAnalyticsLogger : Analytics {
    override fun track(event: String) {
        // send event
    }
}

class LoginViewModel(
    analytics: Analytics
) : ViewModel(), Analytics by analytics {

    fun login() {
        track("login_click")
    }
}

伪代码

text 复制代码
LoginViewModel 实现 Analytics
但具体实现交给 analytics 对象
调用 track:
    自动转发给 analytics.track

Android 落点

  • 组件复用横向能力:日志、埋点、权限、错误上报。
  • 比继承更灵活,避免基类越来越大。
  • 适合组合多个小接口。

9. DSL 的边界设计

简单 DSL

kotlin 复制代码
class DialogConfig {
    var title: String = ""
    var message: String = ""
    var cancelable: Boolean = true
}

fun showDialog(block: DialogConfig.() -> Unit) {
    val config = DialogConfig().apply(block)
    AlertDialog.Builder(context)
        .setTitle(config.title)
        .setMessage(config.message)
        .setCancelable(config.cancelable)
        .show()
}

调用:

kotlin 复制代码
showDialog {
    title = "删除"
    message = "确认删除这条记录?"
    cancelable = false
}

Android 落点

  • Compose 是 DSL 思维的典型代表。
  • Gradle Kotlin DSL 也是 receiver Lambda。
  • 自定义弹窗、表单、埋点配置可以使用 DSL。

使用边界

DSL 应该让调用者更清楚。如果嵌套太深、隐式 this 太多、类型难推断,反而会降低可维护性。


10. 协程挂起的本质

核心理解

suspend 不等于切线程。它表示函数可以挂起,并在未来恢复执行。编译器会把挂起函数转换成状态机。

kotlin 复制代码
suspend fun loadUser(): User {
    val token = api.getToken()
    return api.getUser(token)
}

伪代码

text 复制代码
状态 0:
    调用 getToken()
    如果挂起,保存当前位置
状态 1:
    token 返回后恢复
    调用 getUser(token)
    如果挂起,保存当前位置
状态 2:
    user 返回后恢复
    返回结果

Android 使用建议

  • suspend 只是可挂起,不代表自动在 IO 线程。
  • 网络库如 Retrofit 的 suspend 接口会自己处理异步等待。
  • 文件、数据库、大计算要明确放到合适 Dispatcher。

11. Dispatcher 和线程边界

常见选择

Dispatcher 用途
Main UI 更新
IO 网络、数据库、文件
Default CPU 计算、排序、解析

示例

kotlin 复制代码
suspend fun loadAndParse(): List<User> {
    val json = withContext(Dispatchers.IO) {
        file.readText()
    }
    return withContext(Dispatchers.Default) {
        parseUsers(json)
    }
}

伪代码

text 复制代码
读文件:
    切到 IO
解析 JSON:
    切到 Default
更新 UI:
    回到 Main

Android 落点

主线程卡顿经常不是"用了协程还卡",而是耗时逻辑仍然跑在 Main 上。协程负责组织异步流程,Dispatcher 才决定执行线程。


12. 结构化并发与生命周期

示例

kotlin 复制代码
class UserViewModel(
    private val repository: UserRepository
) : ViewModel() {

    fun loadPage() {
        viewModelScope.launch {
            val userDeferred = async { repository.getUser() }
            val messageDeferred = async { repository.getMessages() }

            _uiState.value = UiState.Content(
                user = userDeferred.await(),
                messages = messageDeferred.await()
            )
        }
    }
}

原理

text 复制代码
viewModelScope 是父作用域
launch 创建父协程
async 创建子协程
ViewModel 清除:
    父作用域取消
    子协程一起取消

Android 使用建议

  • ViewModel 内优先使用 viewModelScope
  • Fragment / Activity 内优先使用 lifecycleScope
  • UI 收集 Flow 优先配合 repeatOnLifecycle
  • 避免 GlobalScope,它脱离生命周期,很容易泄漏或失控。

13. 协程异常传播

launch

kotlin 复制代码
viewModelScope.launch {
    throw RuntimeException("failed")
}

launch 异常会向父作用域传播。

async

kotlin 复制代码
viewModelScope.launch {
    val task = async {
        throw RuntimeException("failed")
    }
    task.await()
}

async 异常会在 await() 时抛出。

SupervisorJob

kotlin 复制代码
viewModelScope.launch {
    supervisorScope {
        val user = async { repository.getUser() }
        val banner = async { repository.getBanner() }

        val safeBanner = runCatching { banner.await() }.getOrNull()
        val requiredUser = user.await()
    }
}

Android 落点

页面有多个并行请求时,要区分"一个失败全失败"和"局部失败可降级"。


14. 协程取消不能乱吞

错误示例

kotlin 复制代码
viewModelScope.launch {
    try {
        repository.load()
    } catch (e: Exception) {
        _uiState.value = UiState.Error("失败")
    }
}

这可能误吞 CancellationException

推荐

kotlin 复制代码
viewModelScope.launch {
    try {
        repository.load()
    } catch (e: CancellationException) {
        throw e
    } catch (e: Exception) {
        _uiState.value = UiState.Error("失败")
    }
}

伪代码

text 复制代码
如果是取消异常:
    继续抛出,尊重协程取消
如果是业务异常:
    转换为错误状态

15. Flow 冷流与热流的选择

冷流

kotlin 复制代码
fun loadUsers(): Flow<List<User>> = flow {
    emit(api.getUsers())
}

没人 collect 时不执行,每次 collect 重新执行。

热流

kotlin 复制代码
private val _uiState = MutableStateFlow(UiState.Loading)
val uiState: StateFlow<UiState> = _uiState

即使没人收集,它也有当前值,适合 UI 状态。

选择规则

场景 推荐
数据源查询 冷 Flow
页面状态 StateFlow
一次性事件 SharedFlow
生产者消费者 Channel
多订阅者共享上游 shareIn / stateIn

16. stateIn 和 shareIn

stateIn

把冷流转换成有当前值的 StateFlow

kotlin 复制代码
val uiState: StateFlow<UiState> = repository.observeUsers()
    .map { users -> UiState.Content(users) }
    .stateIn(
        scope = viewModelScope,
        started = SharingStarted.WhileSubscribed(5000),
        initialValue = UiState.Loading
    )

shareIn

把上游结果共享给多个收集者。

kotlin 复制代码
val events = repository.observeEvents()
    .shareIn(
        scope = viewModelScope,
        started = SharingStarted.WhileSubscribed(),
        replay = 0
    )

原理

text 复制代码
冷流:
    每个 collect 独立启动上游
shareIn/stateIn:
    在指定 scope 中启动共享上游
多个 collect:
    复用同一个上游结果

Android 使用建议

页面 UI 状态优先用 stateIn,避免每次旋转屏幕后重复触发昂贵上游。


17. Flow 背压和最新值策略

常见操作符

kotlin 复制代码
searchText
    .debounce(300)
    .distinctUntilChanged()
    .flatMapLatest { keyword ->
        repository.search(keyword)
    }

原理

text 复制代码
用户快速输入:
    debounce 等用户停一下
关键词变化:
    flatMapLatest 取消旧搜索
只渲染最新结果

Android 落点

  • 搜索框。
  • 地图拖动后请求周边。
  • Tab 快速切换。
  • 下拉刷新和自动刷新竞争。

易错点

collectLatest 会取消上一次收集逻辑。如果上一次逻辑里有必须完成的保存操作,不要随便用。


18. UI State 和 UI Event 分离

状态

状态是"现在页面长什么样"。

kotlin 复制代码
data class LoginUiState(
    val loading: Boolean = false,
    val account: String = "",
    val password: String = ""
)

事件

事件是"一次性动作"。

kotlin 复制代码
sealed interface LoginEvent {
    data object GoHome : LoginEvent
    data class ShowToast(val message: String) : LoginEvent
}

ViewModel

kotlin 复制代码
private val _uiState = MutableStateFlow(LoginUiState())
val uiState: StateFlow<LoginUiState> = _uiState

private val _events = MutableSharedFlow<LoginEvent>()
val events: SharedFlow<LoginEvent> = _events

Android 使用建议

不要把 Toast、导航、弹窗这类一次性动作塞进 StateFlow 后靠重置字段解决。状态和事件分开,旋转屏幕后行为更可控。


19. Compose 稳定性和重组

基础理解

Compose 根据状态变化决定哪些 UI 需要重组。稳定的数据结构能帮助 Compose 减少不必要的工作。

kotlin 复制代码
data class UserUiState(
    val users: List<UserUiModel>,
    val loading: Boolean
)

状态提升

kotlin 复制代码
@Composable
fun SearchScreen(
    keyword: String,
    onKeywordChange: (String) -> Unit
) {
    TextField(
        value = keyword,
        onValueChange = onKeywordChange
    )
}

伪代码

text 复制代码
状态在上层:
    子组件只负责显示和回调
用户输入:
    回调通知上层
上层更新状态:
    Compose 根据新状态重组

Android 使用建议

  • UI 组件尽量无状态。
  • ViewModel 暴露稳定的 UI State。
  • 避免在 Composable 里直接启动不可控副作用。

20. Compose 副作用

常见 API

kotlin 复制代码
LaunchedEffect(userId) {
    viewModel.loadUser(userId)
}
kotlin 复制代码
DisposableEffect(Unit) {
    val listener = createListener()
    register(listener)
    onDispose {
        unregister(listener)
    }
}
kotlin 复制代码
val latestOnClick by rememberUpdatedState(onClick)

Android 落点

  • LaunchedEffect:参数变化时启动协程。
  • DisposableEffect:注册和释放监听器。
  • rememberUpdatedState:在长生命周期 effect 中拿到最新回调。

易错点

不要把网络请求直接写在 Composable 函数体里。函数体可能因为重组多次执行。


21. KSP 和注解处理

核心理解

KSP 是 Kotlin Symbol Processing,适合在编译期读取 Kotlin 符号并生成代码。

伪代码

text 复制代码
编译开始:
    KSP 扫描带注解的类
    读取类名、属性、类型
    生成辅助代码
编译继续:
    项目引用生成代码

Android 落点

  • Room 生成 DAO 和数据库相关代码。
  • Moshi 生成 JSON Adapter。
  • Hilt / DI 工具生成依赖注入代码。
  • 路由框架生成路由表。

示例

kotlin 复制代码
@Entity
data class UserEntity(
    @PrimaryKey val id: Long,
    val name: String
)

Room 会在编译期处理这些注解,生成数据库访问相关代码。


22. 反射和混淆

问题

Kotlin 反射功能强,但在 Android 上要关注包体、性能和混淆。

kotlin 复制代码
val clazz = User::class
val javaClass = User::class.java

Android 使用建议

  • 高频路径避免反射。
  • 能用 KSP 生成代码时,优先编译期生成。
  • 反射访问的类和字段,混淆时需要 keep 规则。
  • JSON、路由、DI 框架要检查它们的混淆要求。

伪代码

text 复制代码
如果运行时按名字找类或字段:
    混淆可能改名
所以:
    需要 keep 规则
或者:
    改为编译期生成代码

23. Java/Kotlin 混编迁移策略

原则

不要一次性重写整个项目。优先迁移边界清晰、测试容易补的文件。

推荐顺序

  1. 工具函数。
  2. 数据类和 UI Model。
  3. Repository 边缘逻辑。
  4. ViewModel。
  5. 新页面优先 Kotlin。
  6. 老核心链路稳定后再逐步迁移。

API 兼容

kotlin 复制代码
object UserRoutes {
    @JvmStatic
    fun detailIntent(context: Context, userId: Long): Intent {
        return Intent(context, DetailActivity::class.java)
            .putExtra("user_id", userId)
    }
}

Java 调用:

java 复制代码
Intent intent = UserRoutes.detailIntent(context, 1001L);

Android 落点

混编阶段,公共入口要照顾 Java 调用体验,内部实现可以慢慢 Kotlin 化。


24. Kotlin 性能优化

常见成本

  • 过长集合链可能创建中间集合。
  • Lambda 捕获变量可能产生对象。
  • 委托和反射可能带来额外调用成本。
  • 默认参数和 data class copy 可能生成额外方法。

示例

kotlin 复制代码
val names = users
    .filter { it.active }
    .map { it.name }

普通页面数据量很小,这样写清楚就好。大列表高频执行时再考虑优化:

kotlin 复制代码
val names = ArrayList<String>(users.size)
for (user in users) {
    if (user.active) {
        names.add(user.name)
    }
}

Android 使用建议

  • UI 帧内代码优先简单直接。
  • RecyclerView onBindViewHolder 和 Compose 高频重组路径要少分配。
  • 启动路径减少反射、IO 和复杂初始化。

25. 内存泄漏与生命周期

易错写法

kotlin 复制代码
object ActivityHolder {
    var activity: Activity? = null
}

原因

单例生命周期长于 Activity。单例持有 Activity,会阻止它被回收。

更安全的写法

kotlin 复制代码
class ImageLoader(
    private val appContext: Context
) {
    constructor(context: Context) : this(context.applicationContext)
}

协程泄漏

kotlin 复制代码
GlobalScope.launch {
    repository.sync()
}

优先换成:

kotlin 复制代码
viewModelScope.launch {
    repository.sync()
}

或确实需要全局后台任务时使用 WorkManager。


26. 测试协程

依赖思路

协程测试通常需要可控 Dispatcher。

kotlin 复制代码
class UserViewModel(
    private val repository: UserRepository,
    private val dispatcher: CoroutineDispatcher
) : ViewModel() {

    fun load() {
        viewModelScope.launch(dispatcher) {
            _uiState.value = UiState.Content(repository.getUsers())
        }
    }
}

测试伪代码

text 复制代码
创建测试 Dispatcher
创建 fake repository
创建 ViewModel
调用 load
推进协程时间
断言 uiState

示例

kotlin 复制代码
@Test
fun loadUsers_showContent() = runTest {
    val repository = FakeUserRepository()
    val viewModel = UserViewModel(repository, StandardTestDispatcher(testScheduler))

    viewModel.load()
    advanceUntilIdle()

    assertTrue(viewModel.uiState.value is UiState.Content)
}

27. 测试 Flow

手动收集

kotlin 复制代码
@Test
fun uiState_emitContent() = runTest {
    val values = mutableListOf<UiState>()
    val job = launch {
        viewModel.uiState.take(2).toList(values)
    }

    viewModel.load()
    advanceUntilIdle()

    assertEquals(UiState.Loading, values[0])
    assertTrue(values[1] is UiState.Content)

    job.cancel()
}

原理

text 复制代码
启动测试协程收集 Flow
触发 ViewModel 动作
推进虚拟时间
检查收集到的状态序列
取消收集任务

Android 使用建议

复杂 Flow 测试可以使用 Turbine。关键是验证"状态顺序",不是只看最终值。


28. 高阶架构示例

目标

实现一个搜索页面:输入关键词后防抖搜索,只展示最新结果,页面状态稳定,事件独立。

状态

kotlin 复制代码
data class SearchUiState(
    val keyword: String = "",
    val loading: Boolean = false,
    val results: List<UserUiModel> = emptyList(),
    val errorMessage: String? = null
)

事件

kotlin 复制代码
sealed interface SearchEvent {
    data class Toast(val message: String) : SearchEvent
}

ViewModel

kotlin 复制代码
class SearchViewModel(
    private val repository: UserRepository
) : ViewModel() {

    private val keyword = MutableStateFlow("")

    val uiState: StateFlow<SearchUiState> = keyword
        .debounce(300)
        .distinctUntilChanged()
        .flatMapLatest { query ->
            flow {
                emit(SearchUiState(keyword = query, loading = true))
                val users = repository.search(query)
                emit(SearchUiState(
                    keyword = query,
                    results = users.map { it.toUiModel() }
                ))
            }.catch {
                emit(SearchUiState(keyword = query, errorMessage = "搜索失败"))
            }
        }
        .stateIn(
            scope = viewModelScope,
            started = SharingStarted.WhileSubscribed(5000),
            initialValue = SearchUiState()
        )

    fun onKeywordChange(value: String) {
        keyword.value = value
    }
}

UI 收集

kotlin 复制代码
viewLifecycleOwner.lifecycleScope.launch {
    repeatOnLifecycle(Lifecycle.State.STARTED) {
        viewModel.uiState.collect { state ->
            render(state)
        }
    }
}

这段代码体现的高阶点

高阶点 作用
MutableStateFlow 保存输入状态
debounce 防止每个字符都请求
flatMapLatest 取消旧搜索
catch 把异常转成 UI 状态
stateIn 冷流转 StateFlow
WhileSubscribed 页面没人看时减少上游工作
Mapper Domain 转 UI Model

29. 代码审查时看什么

Kotlin 高阶代码 Review 清单

检查项 问题
空安全 是否有不必要的 !!
协程 是否使用了正确 scope
Dispatcher 耗时任务是否离开主线程
Flow 状态和事件是否混在一起
生命周期 是否使用 repeatOnLifecycle
API 参数是否容易传错
inline 是否过度内联
反射 是否需要混淆规则
Compose 是否在函数体直接做副作用
性能 高频路径是否产生多余对象

30. 高阶小抄

需求 推荐做法
运行时拿泛型类型 inline + reified
Java 友好调用 @JvmStatic@JvmOverloads
页面状态建模 data class + sealed interface
一次性事件 SharedFlow
UI 状态 StateFlow
冷流转状态 stateIn
多订阅者共享 shareIn
搜索只要最新结果 flatMapLatest
页面可见才收集 repeatOnLifecycle
并行请求 async/await
局部失败可降级 supervisorScope
编译期生成代码 KSP
减少反射风险 注解处理 / 手写注册表
类型安全 id value class
配置式 API DSL

最后建议

高阶 Kotlin 不是堆语法。真正有价值的是:用类型系统减少误传参数,用协程和 Flow 表达异步数据流,用 sealed 状态让 UI 可预测,用 KSP 和编译期能力减少运行时反射,用测试锁住状态变化。写 Android 项目时,优先让代码"调用处清楚、生命周期安全、状态变化可验证",这比看起来高级更重要。

相关推荐
zhangphil1 小时前
AI大模型生成maxTokens 与上下文context
android·llama
驭渊的小故事1 小时前
java抽奖项目-奖品上传中传递参数类型的错误
java·开发语言
淡淡的香烟1 小时前
Androidiot开发之猫脸识别
android·物联网
名字还没想好☜1 小时前
Go 时间格式化为什么用 2006-01-02:time.Format/Parse 的参考时间、时区与解析踩坑
开发语言·后端·golang·go
Dream Cosmos1 小时前
C++ 多态下篇:虚函数表、动态绑定与多态底层原理
开发语言·c++
2501_915106321 小时前
iOS数据采集技术详解:从性能监控到崩溃分析的全链路实践
android·ios·小程序·https·uni-app·iphone·webview
2601_962071579 小时前
【Java报错已解决】org.springframework.beans.factory.BeanCreationException
java·开发语言
天空之城--10 小时前
Android Koin 完全指南:从原理到实践
android
淡海水11 小时前
07-04-并发-ConcurrentBag-T-工作窃取WorkStealing算法
开发语言·算法·c#·bag·concurrent·workstealing