一文读懂 Kotlin 数据流 Flow 的使用

📱 这是一份为 Android 开发团队精心准备的 Kotlin Flow 完全指南

目标:让每个开发者都能从"听说过"进阶到"能用好"


🎯 为什么我们要学 Kotlin Flow?

现状痛点

在学 Flow 之前,你可能遇到过这些问题:

问题 表现 Flow 如何解决
回调地狱 多层网络请求嵌套混乱 链式调用,代码线性可读
内存泄漏 后台任务没取消导致 OOM 自动生命周期管理
异常处理难 异常被吞掉或崩溃 统一的异常处理机制
线程切换复杂 手动管理 Handler、Thread Dispatcher 自动切换
状态同步困难 数据更新 UI 不及时 响应式自动同步

Flow 的核心价值

  • ✅ 简化异步代码
  • ✅ 自动生命周期管理
  • ✅ 完整的异常处理
  • ✅ 实时数据响应(代替 LiveData)
  • ✅ 与 Coroutine 完美结合

💡 核心概念速览(10 分钟理解)

概念 1:Flow 是什么?

简单说:Flow 是一条"数据传送带"

现实例子:快递物流

  • 📦 数据源 = 发货仓库
  • 🏭 中间加工 = 流转中心(分拣、扫描)
  • 📍 最终消费者 = 你的家门口

概念 2:Cold Flow vs Hot Flow(重要!)

❄️ Cold Flow(冷流)= 懒人快递

scss 复制代码
// 使用 flow { } 创建的 Flow 是冷流
val coldFlow = flow {
    println("开始加工快递")  // 有人下单才执行
    emit(1)
}

// 第一个订阅者
coldFlow.collect { println("用户1收到: $it") }
// 输出: 开始加工快递 → 用户1收到: 1

// 第二个订阅者
coldFlow.collect { println("用户2收到: $it") }
// 输出: 开始加工快递 → 用户2收到: 1
// ⚠️ 执行了两次!每个订阅都是独立的

特点:

  • ✅ 没人订阅就不执行(节省资源)
  • ❌ 重复订阅会重复执行
  • 🎯 适用于:网络请求、数据库查询

🔥 Hot Flow(热流)= 直播广播

scss 复制代码
// 使用 MutableSharedFlow/StateFlow 创建的是热流
val hotFlow = MutableSharedFlow<Int>()

// 发送者不等订阅者,直接广播
launch {
    hotFlow.emit(1)
    hotFlow.emit(2)
    hotFlow.emit(3)
}

// 晚到的订阅者错过前面的值
delay(100)
hotFlow.collect { println("迟到的用户: $it") }
// 输出: 迟到的用户: 2, 迟到的用户: 3
// ❌ 错过了第一个值 1

特点:

  • ✅ 无论有没有订阅都执行
  • ✅ 所有订阅者共享同一条流
  • ❌ 资源消耗更多
  • 🎯 适用于:事件通知、用户交互

快速判断

css 复制代码
需要维护当前状态?          → StateFlow(热流)
只需要发送事件通知?        → SharedFlow(热流)
想要每次都重新执行?        → Flow(冷流)
想要实时响应数据库变化?    → Room Flow(冷流)

🛠️ 实战:最常见的 5 个场景

场景 1:单个网络请求(最基础)

业务需求:点击按钮加载用户信息

kotlin 复制代码
// ViewModel 中
class UserViewModel : ViewModel() {

    // 将网络请求包装成 Flow
    fun fetchUser(userId: String) = flow {
        try {
            val user = apiService.getUser(userId)
            emit(user)  // 发送数据
        } catch (e: Exception) {
            throw e  // 异常会传给 UI 层处理
        }
    }
}

// Activity/Fragment 中
viewModel.fetchUser("123")
    .catch { exception ->
        Toast.show("加载失败: ${exception.message}")
    }
    .collect { user ->
        // 拿到数据,更新 UI
        updateUserUI(user)
    }

优点

  • ✅ 代码简洁,易读易维护
  • ✅ 统一的异常处理
  • ✅ 自动线程切换

场景 2:顺序网络请求(依赖关系)

业务需求:先获取用户信息,再根据用户 ID 获取他的文章列表

复制代码
用户输入 ID → 查询用户信息 → 获取该用户的文章 → 展示文章列表
kotlin 复制代码
// 错误方式 ❌(回调地狱)
apiService.getUser("123") { user ->
    apiService.getPosts(user.id) { posts ->
        updateUI(user, posts)
    }
}

// 正确方式 ✅(Flow 方式)
fun getUserWithPosts(userId: String) = flow {
    val user = apiService.getUser(userId)
    val posts = apiService.getPosts(user.id)
    emit(UserWithPosts(user, posts))
}

// 使用
viewModel.getUserWithPosts("123")
    .catch { Toast.show("加载失败") }
    .collect { data ->
        updateUI(data.user, data.posts)
    }

为什么用 Flow?

  • 📊 从"嵌套金字塔"变成"线性流水线"
  • 🎯 依赖关系一目了然
  • 🛡️ 异常处理统一

场景 3:并行网络请求(独立关系)

业务需求:首页需要同时加载用户、推荐、广告等数据(它们互不依赖)

ini 复制代码
┌─ 获取用户信息 ─┐
│                ├─ 合并 → 展示首页
├─ 获取推荐文章 ─┤
│                │
└─ 获取广告数据 ─┘

时间对比:
顺序执行: 1s + 1s + 1s = 3s  ❌
并行执行: max(1s, 1s, 1s) = 1s  ✅
scss 复制代码
// 使用 combine 操作符(并行加载)
fun getHomeData(): Flow<HomeData> = combine(
    apiService.getUser(),        // 流1
    apiService.getRecommends(),  // 流2
    apiService.getAds()          // 流3
) { user, recommends, ads ->
    // 所有数据都到达了,才执行这里
    HomeData(user, recommends, ads)
}

// 使用
viewModel.getHomeData()
    .catch { Toast.show("加载失败") }
    .collect { homeData ->
        updateHomeUI(homeData)
    }

性能对比

  • 🐢 顺序执行:需要 3 秒
  • 🚀 并行执行:只需 1 秒(快 3 倍!)

场景 4:实时搜索(防抖 + 去重 + 实时请求)

业务需求:搜索框边打字边搜索(但要避免频繁请求服务器)

makefile 复制代码
用户输入: k → ko → kot → kotl → kotlin
         ↓    ↓    ↓     ↓      ↓
      请求? 请求? 请求? 请求?  最后请求

问题分析

  • ❌ 用户每输入一个字就请求?太浪费流量
  • ❌ 新的搜索来了,还在处理旧的搜索结果?太慢

解决方案

kotlin 复制代码
fun search(queryFlow: Flow<String>): Flow<List<SearchResult>> = queryFlow
    .debounce(300)              // 延迟 300ms(停止输入 300ms 后才搜索)
    .filter { it.isNotEmpty() } // 过滤空字符串
    .distinctUntilChanged()     // 去重(相同词不重复搜索)
    .flatMapLatest { query ->   // 关键!新搜索来了,取消旧搜索
        apiService.search(query)
    }
    .catch { emptyFlow() }      // 搜索出错显示空列表

实际效果

makefile 复制代码
用户输入: k k o t l i n (间隔 100ms)
         ↓
debounce ✗ ✗ ✗ ✗ ✗ ✗ → 触发搜索(用户停止后)
distinctUntilChanged → "kotlin" (去重)
flatMapLatest → 仅处理最新的搜索
结果: 只发出 1 个网络请求!

场景 5:数据库 + 实时监听

业务需求:实时显示本地数据库的内容,数据库一改马上更新 UI

kotlin 复制代码
// Room DAO 层
@Dao
interface TodoDao {
    @Query("SELECT * FROM todos ORDER BY id DESC")
    fun getAllTodos(): Flow<List<Todo>>  // ← 返回 Flow!

    suspend fun insertTodo(todo: Todo)
}

// ViewModel
class TodoViewModel(private val dao: TodoDao) : ViewModel() {

    val todos: Flow<List<Todo>> = dao.getAllTodos()
        .catch { exception ->
            Log.e("TodoVM", "加载失败", exception)
            emitAll(emptyFlow())
        }
}

// Activity
viewModel.todos.collect { todoList ->
    // 每当数据库改变,这里自动执行
    updateTodoList(todoList)
}

优点

  • ✅ 无需手动查询数据库
  • ✅ 数据库一改,UI 自动更新
  • ✅ 消除了 LiveData 的"粘性值"问题

🎨 Flow 操作符完全手册

常用操作符速查表

📝 数据变换类

操作符 功能 使用场景 代码示例
map 数据转换 DTO → UI Model .map { it.toUIModel() }
filter 数据过滤 只要满足条件的 .filter { it.age > 18 }
transform 自定义变换 复杂转换 .transform { emit(it * 2) }

🔗 流合并类

操作符 特点 使用场景
zip 等待全部完成 多个独立请求,都需要
combine 实时组合 多个响应式数据源
flatMapConcat 顺序合并 依赖关系的任务
flatMapLatest 取最新值 搜索、自动完成

🛡️ 异常处理类

方式 使用场景 特点
catch 通用异常处理 可发送替代值
retryWhen 重试策略 支持条件重试
onCompletion 清理资源 成功/失败都执行

⚠️ 常见的坑(做过的人都踩过)

坑 1:忘记订阅 Flow

scss 复制代码
// ❌ 错误:Flow 不会执行
viewModel.fetchUser("123")
// 什么都不会发生!

// ✅ 正确:必须调用 collect
viewModel.fetchUser("123")
    .collect { user ->
        updateUI(user)
    }

记住: Flow 需要有"消费者"(collect)才会执行,这叫"冷流"。


坑 2:Flow 在主线程执行但没切换线程

scss 复制代码
// ❌ 错误:这样会阻塞主线程!
viewModel.getUserFromDatabase()
    .collect { user ->  // ← 在主线程等待数据库查询
        updateUI(user)
    }

// ✅ 正确:指定线程
viewModel.getUserFromDatabase()
    .flowOn(Dispatchers.IO)  // ← 数据库操作放在 IO 线程
    .collect { user ->
        updateUI(user)  // ← 这里还是主线程
    }

关键字flowOn(Dispatchers.IO) 上游异步,collect 自动回到主线程。


坑 3:重复 collect 导致重复执行

scss 复制代码
// ❌ 错误:订阅两次,执行两次网络请求
val userFlow = viewModel.fetchUser("123")

userFlow.collect { user1 ->
    updateProfile(user1)
}

userFlow.collect { user2 ->
    updateHeader(user2)
}
// 结果: 发出了 2 个相同的网络请求!浪费流量

// ✅ 正确方式 1:使用 SharedFlow(热流,共享结果)
val userFlow = viewModel.fetchUser("123")
    .shareIn(viewModelScope, SharingStarted.Lazily, replay = 1)

userFlow.collect { updateProfile(it) }
userFlow.collect { updateHeader(it) }
// 结果: 只发出 1 个网络请求

// ✅ 正确方式 2:使用 StateFlow
private val _user = MutableStateFlow<User?>(null)
val user = _user.asStateFlow()
// 只需要 collect 一次状态值

通俗理解

  • 🧊 Flow = 冷流,每次订阅都重新执行(如:每次点击都重新查询)
  • 🔥 StateFlow/SharedFlow = 热流,所有订阅者共享结果(如:用户登录后所有页面都知道)

坑 4:忘记处理异常

scss 复制代码
// ❌ 错误:异常被吞掉或导致崩溃
flow {
    throw Exception("网络错误")
}.collect { data ->
    updateUI(data)  // ← 异常时永远不会执行
}

// ✅ 正确:用 catch 处理
flow {
    throw Exception("网络错误")
}
.catch { exception ->
    showError(exception.message)
}
.collect { data ->
    updateUI(data)
}

最佳实践 :永远不要忘记 .catch { }


坑 5:ViewModel 卸载后 Flow 还在执行

scss 复制代码
// ❌ 错误:ViewModel 被销毁,Flow 还在执行
flow {
    repeat(100) {
        delay(1000)
        emit(it)
    }
}.collect { value ->
    println(value)  // ← ViewModel 销毁后仍然执行,内存泄漏!
}

// ✅ 正确:使用 viewModelScope
viewModelScope.launch {
    flow {
        repeat(100) {
            delay(1000)
            emit(it)
        }
    }.collect { value ->
        println(value)  // ← ViewModel 销毁时自动取消
    }
}

记住viewModelScope.launch { } 会自动取消任务。


📊 StateFlow vs SharedFlow 选择指南

场景决策树

markdown 复制代码
你想维护一个状态值?
├─ 是 → StateFlow
│   └─ 用户信息、表单数据、UI 状态等
└─ 否 → SharedFlow
    └─ 导航事件、消息提示等

完整对比

kotlin 复制代码
// StateFlow:维护当前状态
private val _userState = MutableStateFlow<User?>(null)
val userState: StateFlow<User?> = _userState.asStateFlow()

fun login(email: String) {
    viewModelScope.launch {
        _userState.value = apiService.login(email)  // ← 更新状态
    }
}

// 使用
viewModel.userState.collect { user ->
    if (user != null) {
        showMainUI()  // 用户已登录
    } else {
        showLoginUI()  // 用户未登录
    }
}

---

// SharedFlow:发送一次性事件
private val _navigationEvent = MutableSharedFlow<NavigationEvent>()
val navigationEvent: SharedFlow<NavigationEvent> = _navigationEvent.asSharedFlow()

fun goToDetail(itemId: Int) {
    viewModelScope.launch {
        _navigationEvent.emit(NavigationEvent.GoToDetail(itemId))  // ← 发送事件
    }
}

// 使用
viewModel.navigationEvent.collect { event ->
    when (event) {
        is NavigationEvent.GoToDetail -> {
            navigator.openDetailPage(event.itemId)
        }
    }
}

🔄 处理异常和重试的正确姿势

场景:网络请求失败时自动重试

方式 1:简单重试(重试 N 次)

kotlin 复制代码
fun fetchUser(id: String) = flow {
    emit(apiService.getUser(id))
}
.retry(3)  // ← 失败重试 3 次
.catch { exception ->
    Toast.show("重试 3 次后仍然失败: ${exception.message}")
}

问题:立即重试,没有延迟,容易 DDoS 服务器。


方式 2:延迟重试(指数退避)

kotlin 复制代码
fun fetchUser(id: String) = flow {
    emit(apiService.getUser(id))
}
.retryWhen { exception, attempt ->
    // 第一次失败: 延迟 1秒
    // 第二次失败: 延迟 2秒
    // 第三次失败: 延迟 4秒
    val delayMillis = (1000L shl attempt)  // 2^attempt * 1000
    delay(delayMillis)
    attempt < 3  // ← 最多重试 3 次
}
.catch { exception ->
    showError("网络连接失败,请检查网络")
}

输出时间线

scss 复制代码
第 1 次请求  ────► 失败 ──(等1秒)─→ 第 2 次请求  ────► 失败
  ──(等2秒)─→ 第 3 次请求  ────► 失败  ──(等4秒)─→ 第 4 次请求

方式 3:条件化重试(只重试某些错误)

kotlin 复制代码
fun fetchUser(id: String) = flow {
    emit(apiService.getUser(id))
}
.retryWhen { exception, attempt ->
    when {
        // 服务器错误,重试
        exception is HttpException &&
        exception.code() >= 500 -> {
            delay(1000 * (attempt + 1))
            true
        }
        // 客户端错误,不重试(肯定是请求格式错误)
        exception is HttpException &&
        exception.code() >= 400 -> {
            false
        }
        // 网络错误,重试
        exception is IOException -> {
            delay(1000 * (attempt + 1))
            attempt < 3
        }
        else -> false
    }
}

🧪 单元测试:如何测试你的 Flow

快速 Flow 测试框架

kotlin 复制代码
// 1. 添加依赖
dependencies {
    testImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-test"
    testImplementation "app.cash.turbine:turbine"  // ← Flow 测试神器
}

// 2. 编写测试
@Test
fun testFetchUserSuccess() = runTest {
    val viewModel = UserViewModel(mockApi)

    viewModel.fetchUser("123").test {
        // 验证第一个值
        val user = awaitItem()
        assertEquals("john", user.name)

        // 验证流完成
        awaitComplete()
    }
}

@Test
fun testFetchUserError() = runTest {
    val viewModel = UserViewModel(mockApiWithError)

    viewModel.fetchUser("123").test {
        val exception = awaitError()
        assertTrue(exception is NetworkException)
    }
}

Turbine 库的好处

  • ✅ 简洁的 API,易于理解
  • ✅ 自动处理时间控制
  • ✅ 支持多个 Flow 的并行测试

🚀 快速参考:常用代码片段

场景 1:加载列表(带加载状态)

kotlin 复制代码
sealed class UiState<T> {
    class Loading<T> : UiState<T>()
    data class Success<T>(val data: T) : UiState<T>()
    data class Error<T>(val message: String) : UiState<T>()
}

class ListViewModel : ViewModel() {

    fun loadItems(): Flow<UiState<List<Item>>> = flow {
        emit(UiState.Loading())
        try {
            val items = apiService.getItems()
            emit(UiState.Success(items))
        } catch (e: Exception) {
            emit(UiState.Error(e.message ?: "未知错误"))
        }
    }
}

// UI 层
viewModel.loadItems().collect { state ->
    when (state) {
        is UiState.Loading -> showProgressBar()
        is UiState.Success -> showItems(state.data)
        is UiState.Error -> showError(state.message)
    }
}

场景 2:搜索 + 分页

kotlin 复制代码
fun searchItems(query: Flow<String>, page: Int): Flow<List<Item>> = query
    .debounce(300)
    .distinctUntilChanged()
    .flatMapLatest { searchTerm ->
        flow {
            val results = apiService.search(searchTerm, page)
            emit(results)
        }
    }
    .catch { emitAll(emptyFlow()) }

场景 3:定时任务

scss 复制代码
fun startHeartbeat(): Flow<Long> = flow {
    while (true) {
        emit(System.currentTimeMillis())
        delay(5000)  // 每 5 秒发送一次心跳
    }
}

// 使用
viewModel.startHeartbeat()
    .take(10)  // 只取前 10 个
    .collect { timestamp ->
        sendHeartbeatToServer(timestamp)
    }

📱 在 Android 项目中集成 Flow 的最佳实践

项目架构推荐

kotlin 复制代码
app/
├── ui/
│   ├── activities/
│   │   └── MainActivity.kt
│   ├── fragments/
│   └── viewmodels/
│       └── UserViewModel.kt  ← 使用 Flow 和 StateFlow
├── data/
│   ├── api/
│   │   └── ApiService.kt  ← 返回 Flow 或 suspend fun
│   ├── database/
│   │   └── UserDao.kt  ← 返回 Flow
│   ├── repository/
│   │   └── UserRepository.kt  ← 组合多个 Flow
│   └── models/
│       └── User.kt
└── di/
    └── AppModule.kt  ← 依赖注入

各层的职责

kotlin 复制代码
// 📡 API 层(网络请求)
interface ApiService {
    @GET("users/{id}")
    suspend fun getUser(@Path("id") id: String): User
}

// 🗂️ DAO 层(数据库)
@Dao
interface UserDao {
    @Query("SELECT * FROM users WHERE id = :id")
    fun getUser(id: String): Flow<User>
}

// 📦 Repository 层(数据聚合)
class UserRepository(
    private val apiService: ApiService,
    private val userDao: UserDao
) {
    fun getUser(id: String): Flow<User> = flow {
        try {
            // 优先返回本地数据
            userDao.getUser(id).collect { localUser ->
                emit(localUser)
            }

            // 然后从网络更新
            val remoteUser = apiService.getUser(id)
            userDao.insertUser(remoteUser)  // 更新本地
            emit(remoteUser)
        } catch (e: Exception) {
            throw e
        }
    }
}

// 🎯 ViewModel 层(UI 数据管理)
class UserViewModel(private val repository: UserRepository) : ViewModel() {

    fun fetchUser(id: String): Flow<User> = repository.getUser(id)
        .catch { exception ->
            println("加载失败: ${exception.message}")
            emitAll(emptyFlow())
        }
}

// 📱 UI 层(展示)
class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        lifecycleScope.launch {
            repeatOnLifecycle(Lifecycle.State.STARTED) {
                viewModel.fetchUser("123").collect { user ->
                    updateUI(user)
                }
            }
        }
    }
}

💼 团队协作建议

1. 统一规范

kotlin 复制代码
// ❌ 不同的返回方式,容易混乱
fun getUserA(): User { }                    // 同步
fun getUserB(callback: (User) -> Unit) { } // 回调
fun getUserC(): Flow<User> { }             // Flow

// ✅ 统一用 Flow(或 suspend)
suspend fun getUser(id: String): User { }  // 网络请求
fun getUser(id: String): Flow<User> { }   // 数据库/长时间任务

建议

  • 网络请求用 suspend 函数
  • 数据库查询返回 Flow
  • 长时间任务用 Flow
  • ViewModel 暴露 StateFlow<T>Flow<T>

2. Code Review 检查清单

arduino 复制代码
□ Flow 有没有被订阅(collect)?
□ 异常处理完了吗(catch)?
□ 在正确的线程执行吗(flowOn)?
□ ViewModel 销毁会自动取消吗(viewModelScope)?
□ 重复订阅会导致重复执行吗?
□ 单元测试覆盖了吗?

3. 常见的代码评审问题

scss 复制代码
// ❌ Review 驳回:没有异常处理
viewModel.fetchUser("123").collect { user ->
    updateUI(user)
}

// ✅ Review 通过:加上异常处理
viewModel.fetchUser("123")
    .catch { exception ->
        showError(exception.message)
    }
    .collect { user ->
        updateUI(user)
    }

---

// ❌ Review 驳回:没有指定线程
val users = databaseDao.getAllUsers()
    .collect { users ->
        updateUI(users)  // 可能在 IO 线程!
    }

// ✅ Review 通过:指定了线程
val users = databaseDao.getAllUsers()
    .flowOn(Dispatchers.IO)  // 数据库操作放在 IO 线程
    .collect { users ->
        updateUI(users)  // 自动切回主线程
    }

📚 推荐学习资源

官方资源

深入学习

测试工具

  • Turbine:Flow 测试库
  • kotlinx-coroutines-test:协程测试框架

🔥 核心速记表

Flow 操作符一览

scss 复制代码
创建: flow { }, flowOf(), MutableSharedFlow(), MutableStateFlow()
转换: map(), filter(), transform()
合并: zip(), combine(), flatMapConcat(), flatMapLatest()
处理: catch(), retry(), retryWhen(), onCompletion()
限流: debounce(), throttle(), distinctUntilChanged()
消费: collect(), launchIn(), collectAsState()

快速决策

问题 答案
每次订阅都要重新执行吗? 是 → Flow,否 → StateFlow/SharedFlow
需要维护当前状态吗? 是 → StateFlow,否 → SharedFlow
担心网络请求失败吗? .catch { }
线程管理问题吗? .flowOn(Dispatchers.IO)
需要重试吗? .retry(n).retryWhen { }

❓ FAQ(常见问题)

Q1: Flow 会比 LiveData 多占用多少内存?

A: 基本没区别,Flow 更轻量。区别在于功能强大程度。

Q2: 什么时候该用 suspend 函数而不是 Flow?

A:

  • 一次性网络请求 → suspend fun
  • 持续监听数据库/数据源 → Flow<T>

Q3: 如何取消一个正在执行的 Flow?

A : 使用 viewModelScope 会自动取消,或手动调用 cancel()

Q4: Flow 对性能有影响吗?

A: 反而更优化!避免了多层回调和线程频繁切换。

Q5: 老项目全是 RxJava,要全部迁移吗?

A: 不用全部迁移,新代码用 Flow,逐步替换即可。


🎯 总结:为什么 Flow 是未来

特性 RxJava LiveData Flow
学习曲线 陡 😰 平 😊 平 😊
功能强大 ⭐⭐⭐⭐⭐ ⭐⭐ ⭐⭐⭐⭐⭐
与 Coroutine 融合 中等 ✅ 完美
官方推荐 渐淡 ✅ 重点
心智成本

结论

Flow 是 Kotlin 异步编程的未来。不仅功能强大,而且与官方 Coroutines 完美配套。现在是学习 Flow 的最好时机。

祝你 Flow 学习之旅顺利!

一旦掌握 Flow,你会发现异步编程原来可以这么优雅。

相关推荐
xiangpanf2 小时前
Laravel 10.x重磅升级:五大核心特性解析
android
robotx5 小时前
安卓线程相关
android
消失的旧时光-19436 小时前
Android 面试高频:JSON 文件、大数据存储与断电安全(从原理到工程实践)
android·面试·json
dalancon7 小时前
VSYNC 信号流程分析 (Android 14)
android
dalancon7 小时前
VSYNC 信号完整流程2
android
dalancon7 小时前
SurfaceFlinger 上帧后 releaseBuffer 完整流程分析
android
用户69371750013848 小时前
不卷AI速度,我卷自己的从容——北京程序员手记
android·前端·人工智能
程序员Android8 小时前
Android 刷新一帧流程trace拆解
android
墨狂之逸才9 小时前
解决 Android/Gradle 编译报错:Comparison method violates its general contract!
android
阿明的小蝴蝶10 小时前
记一次Gradle环境的编译问题与解决
android·前端·gradle