Android 新开发模式深度实践:Kotlin + 协程 + Flow+MVVM

在移动开发技术飞速迭代的今天,Android 开发模式正经历着从 "能用" 到 "好用、高效、易维护" 的本质升级。Kotlin 语言的全面普及、协程对多线程的革命性优化、Flow 带来的响应式编程范式,以及 MVVM 架构的标准化落地,共同构建了当前 Android 开发的主流技术栈。

一、技术选型底层逻辑:为何是 Kotlin + 协程 + Flow+MVVM?

在深入技术细节前,我们首先要明确这套技术栈的选型逻辑 ------ 它并非单纯的 "技术跟风",而是对 Android 开发核心痛点的精准解决。

1. 传统开发模式的痛点

  • Java 语言局限性:空指针风险高、语法冗余、缺乏函数式编程特性,导致开发效率低且易出错;
  • 多线程管理混乱:AsyncTask、HandlerThread 等传统方案存在内存泄漏风险,线程切换逻辑复杂,代码可读性差;
  • 数据与 UI 耦合严重:MVC 架构中 Activity/Fragment 既承担 UI 渲染又处理业务逻辑,导致代码臃肿,测试难度大;
  • 响应式场景适配不足:网络请求、数据库操作等异步场景中,数据变化后的 UI 更新需手动管理,易出现数据一致性问题。

2. 新开发模式的核心优势

技术组件 核心解决痛点 技术价值
Kotlin Java 语法冗余、空安全问题 代码量减少 30%+,空安全机制降低 80% 空指针崩溃,支持函数式编程
协程 多线程切换复杂、回调地狱 以同步代码风格编写异步逻辑,自动管理线程切换,无内存泄漏风险
Flow 异步数据流处理繁琐 支持数据流的过滤、转换、合并,实现数据变化的自动响应
MVVM 数据与 UI 耦合、测试困难 架构分层清晰,业务逻辑与 UI 完全解耦,支持单元测试与 UI 自动化测试

这套技术栈的核心思想是:通过 Kotlin 奠定高效编程基础,用协程解决异步问题,靠 Flow 实现响应式数据流管理,以 MVVM 架构保障代码可维护性,四者相辅相成,形成闭环。

二、核心技术深度解析与实践

(一)Kotlin:Android 开发的 "语法利器"

Kotlin 作为 Google 官方推荐的 Android 开发语言,其优势不仅在于语法简洁,更在于对 Android 开发场景的深度适配。

1. 必用核心特性(附实战场景)
  • 空安全机制 :通过?!!明确空值类型,结合let?:操作符,从编译期避免空指针。

    kotlin

    复制代码
    // 传统Java写法(存在空指针风险)
    String userName = user.getName();
    if (userName != null) {
        textView.setText(userName);
    }
    // Kotlin优化写法(无空指针风险)
    val userName = user.name ?: "默认用户名"
    textView.text = userName
    // 或使用let安全调用
    user.name?.let { 
        textView.text = it 
    }
  • 扩展函数 :为系统类或第三方库添加自定义方法,避免工具类冗余。

    kotlin

    复制代码
    // 为TextView添加设置颜色和字体大小的扩展函数
    fun TextView.setTextStyle(color: Int, textSize: Float) {
        setTextColor(color)
        setTextSize(TypedValue.COMPLEX_UNIT_SP, textSize)
    }
    // 调用:直接通过TextView实例调用
    tvTitle.setTextStyle(ContextCompat.getColor(context, R.color.red), 18f)
  • 数据类与密封类 :简化数据模型定义,密封类替代枚举更灵活。

    kotlin

    复制代码
    // 数据类(自动生成getter/setter、equals、hashCode等方法)
    data class User(val id: Int, val name: String?, val age: Int)
    // 密封类(用于状态管理,限制子类类型)
    sealed class UiState<out T> {
        object Loading : UiState<Nothing>()
        data class Success<out T>(val data: T) : UiState<T>()
        data class Error(val msg: String) : UiState<Nothing>()
    }
  • Lambda 表达式与高阶函数 :简化回调逻辑,尤其适用于点击事件、集合操作等场景。

    kotlin

    复制代码
    // 集合过滤与转换(替代Java的for循环+if判断)
    val adultUsers = userList.filter { it.age >= 18 }
                            .map { it.name ?: "匿名用户" }
    // 点击事件简化(替代匿名内部类)
    btnSubmit.setOnClickListener { 
        viewModel.login(etPhone.text.toString(), etPwd.text.toString())
    }
2. Kotlin 与 Java 混编注意事项
  • 开启 Java 调用 Kotlin 的空安全支持:在build.gradle中添加kotlinOptions { jvmTarget = "1.8" }
  • Kotlin 中调用 Java 类时,需通过@Nullable/@NonNull注解明确空值,避免空指针;
  • 优先使用 Kotlin 的ArrayListHashMap替代 Java 集合,支持更多扩展函数。

(二)协程:异步编程的 "革命性方案"

协程(Coroutines)是 Kotlin 提供的轻量级异步编程框架,核心优势是 "用同步代码写异步逻辑",彻底解决回调地狱问题。

1. 协程核心原理
  • 协程是 "可暂停的函数",通过suspend关键字标记,暂停时不会阻塞线程,仅释放 CPU 资源;

  • 协程的调度依赖CoroutineDispatcher,默认提供四大调度器:

    调度器 适用场景 线程模型
    Dispatchers.Main UI 更新 主线程
    Dispatchers.IO 网络请求、数据库操作 IO 线程池
    Dispatchers.Default 复杂计算 CPU 核心数线程池
    Dispatchers.Unconfined 无特定线程要求 跟随调用方线程
  • 协程的生命周期通过CoroutineScope管理,ViewModel中推荐使用viewModelScope(自动跟随 ViewModel 生命周期取消),避免内存泄漏。

2. 协程实战:网络请求异步处理

kotlin

复制代码
// 1. 依赖配置(build.gradle)
dependencies {
    implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.3'
}

// 2. ViewModel中使用协程发起网络请求
class LoginViewModel : ViewModel() {
    private val loginRepository = LoginRepository()
    val uiState = MutableStateFlow<UiState<User>>(UiState.Loading)

    fun login(phone: String, pwd: String) {
        viewModelScope.launch(Dispatchers.IO) {
            try {
                // 异步请求(suspend函数,可暂停)
                val user = loginRepository.login(phone, pwd)
                // 切换到主线程更新UI状态
                withContext(Dispatchers.Main) {
                    uiState.value = UiState.Success(user)
                }
            } catch (e: Exception) {
                withContext(Dispatchers.Main) {
                    uiState.value = UiState.Error(e.message ?: "登录失败")
                }
            }
        }
    }
}

// 3. Repository中的suspend函数(网络请求)
class LoginRepository {
    private val apiService = RetrofitClient.create(ApiService::class.java)
    // suspend标记:可暂停的函数,只能在协程或其他suspend函数中调用
    suspend fun login(phone: String, pwd: String): User {
        return apiService.login(phone, pwd)
    }
}
学习问题 1:协程中viewModelScope.launchCoroutineScope(Dispatchers.IO).launch的区别?为何多次使用后者会导致内存泄漏?
  • 问题场景 :在 ViewModel 中发起网络请求时,可能误用CoroutineScope(Dispatchers.IO).launch替代viewModelScope.launch,导致屏幕旋转后协程仍在执行,持有 ViewModel 引用引发内存泄漏。
  • 问题分析
    • viewModelScope是 Jetpack 提供的生命周期绑定协程作用域,ViewModel 销毁时会自动取消所有子协程,无需手动管理;
    • 手动创建的CoroutineScope(Dispatchers.IO)无生命周期关联,即使 ViewModel 销毁,协程仍在 IO 线程执行,且协程内部若引用了 ViewModel 的属性(如uiState),会导致 ViewModel 无法被 GC 回收。
  • 解决方案
    • 所有 ViewModel 中的协程必须使用viewModelScope(或lifecycleScope用于 Activity/Fragment);

    • 若需手动创建协程(如全局后台任务),需绑定生命周期所有者,示例: kotlin

      复制代码
      // 错误写法(内存泄漏风险)
      CoroutineScope(Dispatchers.IO).launch { 
          val user = repository.login(phone, pwd) // 屏幕旋转后仍执行
      }
      
      // 正确写法(绑定ViewModel生命周期)
      viewModelScope.launch(Dispatchers.IO) { 
          val user = repository.login(phone, pwd) // ViewModel销毁时自动取消
      }
      
      // 全局任务正确写法(绑定Application生命周期)
      val applicationScope = CoroutineScope(SupervisorJob() + Dispatchers.IO)
      applicationScope.launch { 
          // 全局后台任务(如数据同步)
      }
3. 协程避坑指南
  • 严禁在Activity中使用GlobalScope(生命周期不受控,易内存泄漏),优先使用lifecycleScope/viewModelScope

  • 网络请求等异步操作必须在Dispatchers.IO调度器中执行,避免阻塞主线程;

  • 多个协程并发时,使用async+await获取结果,避免嵌套launch

    kotlin

    复制代码
    // 并发获取用户信息和订单列表
    viewModelScope.launch {
        val userDeferred = async(Dispatchers.IO) { repository.getUserInfo() }
        val orderDeferred = async(Dispatchers.IO) { repository.getOrderList() }
        val user = userDeferred.await()
        val orderList = orderDeferred.await()
        // 处理数据...
    }

(三)Flow:响应式数据流管理

Flow 是 Kotlin 提供的响应式编程框架,用于处理 "连续的异步数据流"(如网络请求结果、数据库数据变化、UI 输入变化等),核心是 "数据发射 - 中间处理 - 接收响应" 的流程。

1. Flow 与 LiveData 的区别(为何选 Flow?)
特性 Flow LiveData
生命周期感知 需结合repeatOnLifecycle 天生支持
数据流操作 支持 map、filter、flatMap 等丰富操作符 仅支持 map、switchMap
背压处理 天然支持(缓冲策略可配置) 不支持,易出现数据丢失
异常处理 支持 catch、retry 等操作符 需手动处理
适用场景 复杂数据流处理(多源数据合并、过滤) 简单 UI 状态更新

结论:Flow 更适合复杂异步场景,LiveData 仅适用于简单的 UI 状态通知,项目中建议优先使用 Flow。

2. Flow 核心操作符实战

kotlin

复制代码
// 1. 定义Flow(数据库数据变化监听)
class UserRepository {
    private val userDao = AppDatabase.getInstance().userDao()
    // 从数据库获取用户列表Flow(数据变化时自动发射新数据)
    fun getUserListFlow(): Flow<List<User>> {
        return userDao.queryAllUsers()
            .map { userEntities -> 
                // 数据转换:Entity -> Model
                userEntities.map { it.toModel() }
            }
            .filter { it.isNotEmpty() } // 过滤空列表
            .retry(2) { e -> e is SQLException } // 数据库异常时重试2次
            .catch { e -> emit(emptyList()) } // 最终异常处理:发射空列表
    }
}

// 2. 收集Flow(ViewModel中)
class UserViewModel : ViewModel() {
    private val userRepository = UserRepository()
    val userListState = MutableStateFlow<UiState<List<User>>>(UiState.Loading)

    fun loadUserList() {
        viewModelScope.launch {
            userRepository.getUserListFlow()
                .flowOn(Dispatchers.IO) // 数据流处理在IO线程
                .collect { userList ->
                    // 收集数据,更新UI状态
                    userListState.value = UiState.Success(userList)
                }
        }
    }
}

// 3. Activity中收集(生命周期安全)
class UserActivity : AppCompatActivity() {
    private val viewModel by viewModels<UserViewModel>()

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        // 结合repeatOnLifecycle,避免后台数据导致UI泄漏
        lifecycleScope.launch {
            repeatOnLifecycle(Lifecycle.State.STARTED) {
                viewModel.userListState.collect { uiState ->
                    when (uiState) {
                        is UiState.Loading -> showLoading()
                        is UiState.Success -> showUserList(uiState.data)
                        is UiState.Error -> showError(uiState.msg)
                    }
                }
            }
        }
    }
}
学习问题 2:Flow 收集时出现 "只有最后一条数据被接收" 的问题?如何解决 Flow 的 "背压" 导致的数据丢失?
  • 问题场景:在监听数据库频繁更新(如 1 秒内发射 10 条数据)或大文件流传输时,Flow 收集者(如 UI 层)处理速度跟不上发射速度,导致中间数据丢失,仅能收到最后一条数据。
  • 问题分析
    • Flow 默认的背压策略是BUFFERED(缓冲 64 条数据),当发射速度远超收集速度时,缓冲队列满后会丢弃早期数据;
    • 新手未配置背压策略,或误用collectLatest(取消前一个未处理完的收集任务,仅处理最新数据)导致数据丢失。
  • 解决方案
    • 根据场景选择合适的背压策略(通过buffer操作符配置);

    • 避免在 UI 层使用collectLatest处理需要完整数据的场景(如列表刷新)。

    • 示例代码: kotlin

      复制代码
      // 问题代码(数据丢失)
      repository.getUserListFlow()
          .flowOn(Dispatchers.IO)
          .collectLatest { userList -> // 仅处理最新数据,丢弃中间数据
              showUserList(userList) // UI渲染速度慢,导致中间数据被取消
          }
      
      // 解决代码(配置背压策略,确保数据不丢失)
      repository.getUserListFlow()
          .flowOn(Dispatchers.IO)
          .buffer(capacity = Channel.UNLIMITED) // 无限制缓冲(适合小数据量)
          // 或使用conflate():合并重复数据,仅保留最新一条(适合大数据量更新)
          // .conflate()
          .collect { userList ->
              showUserList(userList) // 所有数据按顺序接收,无丢失
          }
3. 冷 Flow 与热 Flow 区别及选型
  • 冷 Flow :默认是冷流,只有调用collect后才会发射数据,每个收集者独立接收数据(如上述数据库 Flow);
  • 热 Flow :无论是否有收集者,都会发射数据,多个收集者共享同一数据流(如StateFlowSharedFlow);
  • 选型建议:
    • 单源数据单接收者(如单个页面监听数据库):用冷 Flow;
    • 多接收者共享数据(如全局通知、用户登录状态):用StateFlow(状态持有)或SharedFlow(事件通知)。

(四)MVVM 架构:分层设计与解耦实践

MVVM(Model-View-ViewModel)架构的核心是 "数据驱动 UI",通过分层设计实现业务逻辑与 UI 的完全解耦,架构图如下:

plaintext

复制代码
View(Activity/Fragment):UI渲染、用户交互 -> 观察ViewModel的状态变化
ViewModel:业务逻辑处理、数据状态管理 -> 不持有View引用,通过Flow/StateFlow通知UI
Repository:数据访问层 -> 统一管理网络请求、数据库操作、本地缓存
Model:数据模型 -> 实体类(Entity)、数据模型(Model)、请求/响应类(Request/Response)
1. 各层职责与通信规则
  • View 层

    • 职责:初始化 UI、绑定点击事件、观察 ViewModel 的状态变化;

    • 通信:通过调用 ViewModel 的方法触发业务逻辑,不直接操作数据;

    • 示例: kotlin

      复制代码
      class LoginActivity : AppCompatActivity() {
          private val viewModel by viewModels<LoginViewModel>()
          private lateinit var binding: ActivityLoginBinding
      
          override fun onCreate(savedInstanceState: Bundle?) {
              super.onCreate(savedInstanceState)
              binding = ActivityLoginBinding.inflate(layoutInflater)
              setContentView(binding.root)
      
              // 绑定点击事件(View -> ViewModel)
              binding.btnSubmit.setOnClickListener {
                  val phone = binding.etPhone.text.toString()
                  val pwd = binding.etPwd.text.toString()
                  viewModel.login(phone, pwd)
              }
      
              // 观察状态变化(ViewModel -> View)
              lifecycleScope.launch {
                  repeatOnLifecycle(Lifecycle.State.STARTED) {
                      viewModel.uiState.collect {
                          when (it) {
                              UiState.Loading -> binding.progressBar.visibility = View.VISIBLE
                              is UiState.Success -> {
                                  binding.progressBar.visibility = View.GONE
                                  Toast.makeText(this@LoginActivity, "登录成功", Toast.LENGTH_SHORT).show()
                                  startActivity(Intent(this@LoginActivity, MainActivity::class.java))
                              }
                              is UiState.Error -> {
                                  binding.progressBar.visibility = View.GONE
                                  Toast.makeText(this@LoginActivity, it.msg, Toast.LENGTH_SHORT).show()
                              }
                          }
                      }
                  }
              }
          }
      }
  • ViewModel 层

    • 职责:接收 View 的事件、调用 Repository 获取数据、处理业务逻辑、维护 UI 状态;
    • 通信:通过 Flow/StateFlow 向 View 发送状态,不持有 View 引用(通过viewModelScope管理生命周期);
    • 核心原则:ViewModel 中不能出现任何 Android 系统类(如 Context、View),确保可测试性。
  • Repository 层

    • 职责:统一数据访问入口,屏蔽数据来源(网络 / 数据库 / 缓存),实现数据缓存策略;

    • 示例(数据缓存逻辑): kotlin

      复制代码
      class UserRepository {
          private val apiService = RetrofitClient.create(ApiService::class.java)
          private val userDao = AppDatabase.getInstance().userDao()
          private val cache = LruCache<String, User>(10) // 内存缓存
      
          // 优先从缓存获取,无则从数据库获取,最后从网络请求
          suspend fun getUserInfo(userId: Int): User {
              // 1. 内存缓存
              cache.get(userId.toString())?.let { return it }
              // 2. 数据库缓存
              userDao.queryUserById(userId)?.let { userEntity ->
                  val user = userEntity.toModel()
                  cache.put(userId.toString(), user)
                  return user
              }
              // 3. 网络请求
              val userResponse = apiService.getUserInfo(userId)
              val user = userResponse.toModel()
              // 缓存到数据库和内存
              userDao.insertUser(user.toEntity())
              cache.put(userId.toString(), user)
              return user
          }
      }
  • Model 层

    • 职责:定义数据结构,区分不同数据类型:
      • Entity:数据库实体类(与表结构对应);
      • Model:业务层数据模型(View 层使用);
      • Request/Response:网络请求参数与响应数据类。
2. MVVM 架构优势验证:单元测试

MVVM 的分层设计让单元测试变得简单,以 ViewModel 测试为例,无需依赖 Android 环境即可验证业务逻辑:

kotlin

复制代码
// 依赖:JUnit + MockK(模拟Repository)
dependencies {
    testImplementation 'junit:junit:4.13.2'
    testImplementation 'io.mockk:mockk:1.13.8'
    testImplementation 'org.jetbrains.kotlinx:kotlinx-coroutines-test:1.7.3'
}

// 测试类
class LoginViewModelTest {
    // 模拟Repository
    private val mockRepository = mockk<LoginRepository>()
    // ViewModel实例
    private lateinit var viewModel: LoginViewModel

    @Before
    fun setup() {
        // 注入模拟的Repository
        viewModel = LoginViewModel(mockRepository)
        // 初始化协程测试环境
        Dispatchers.setMain(StandardTestDispatcher())
    }

    @After
    fun teardown() {
        Dispatchers.resetMain()
    }

    @Test
    fun `login success should post Success state`() = runTest {
        // 1. 模拟Repository返回成功结果
        val mockUser = User(1, "测试用户", 25)
        coEvery { mockRepository.login("13800138000", "123456") } returns mockUser

        // 2. 调用ViewModel的login方法
        viewModel.login("13800138000", "123456")

        // 3. 验证UI状态是否为Success
        val uiState = viewModel.uiState.value
        assertTrue(uiState is UiState.Success)
        assertEquals(mockUser, (uiState as UiState.Success).data)
    }

    @Test
    fun `login failed should post Error state`() = runTest {
        // 1. 模拟Repository抛出异常
        coEvery { mockRepository.login("13800138000", "wrong_pwd") } throws Exception("密码错误")

        // 2. 调用ViewModel的login方法
        viewModel.login("13800138000", "wrong_pwd")

        // 3. 验证UI状态是否为Error
        val uiState = viewModel.uiState.value
        assertTrue(uiState is UiState.Error)
        assertEquals("密码错误", (uiState as UiState.Error).msg)
    }
}

三、完整项目架构搭建(附目录结构)

复制代码
app/
├── src/
│   ├── main/
│   │   ├── java/com/example/androidnewmode/
│   │   │   ├── App.kt                  // 应用入口,初始化Retrofit、数据库等
│   │   │   ├── data/                   // 数据层(Model+Repository)
│   │   │   │   ├── model/              // 数据模型
│   │   │   │   │   ├── entity/         // 数据库实体类
│   │   │   │   │   ├── model/          // 业务模型
│   │   │   │   │   └── request/        // 网络请求参数
│   │   │   │   ├── remote/             // 网络数据来源
│   │   │   │   │   ├── ApiService.kt   // 接口定义
│   │   │   │   │   └── RetrofitClient.kt // Retrofit初始化
│   │   │   │   ├── local/              // 本地数据来源(数据库、SP)
│   │   │   │   │   ├── AppDatabase.kt  // 数据库实例
│   │   │   │   │   └── UserDao.kt      // DAO接口
│   │   │   │   └── repository/         // Repository实现
│   │   │   ├── domain/                 // 领域层(可选,复杂项目使用)
│   │   │   │   ├── usecase/            // 业务用例(如LoginUseCase.kt)
│   │   │   │   └── repository/         // Repository接口(面向接口编程)
│   │   │   ├── presentation/           // 表现层(View+ViewModel)
│   │   │   │   ├── ui/                 // View层(Activity/Fragment)
│   │   │   │   │   ├── login/          // 登录页面
│   │   │   │   │   └── main/           // 主页面
│   │   │   │   └── viewModel/          // ViewModel层
│   │   │   └── util/                   // 工具类
│   │   └── res/                        // 资源文件
│   └── test/                           // 单元测试
│       └── java/com/example/androidnewmode/
│           └── presentation/
│               └── viewModel/          // ViewModel测试类
└── build.gradle                        // 项目配置

四、实战避坑与性能优化

1. 常见问题及解决方案

问题场景 解决方案
Flow 收集导致内存泄漏 使用repeatOnLifecycle(Lifecycle.State.STARTED),避免在onCreate中直接collect
协程取消不及时 优先使用viewModelScope/lifecycleScope,手动创建协程需关联生命周期
网络请求重复发起 在 Repository 层添加请求防抖(如Mutex锁),或使用distinctUntilChanged操作符
数据库与网络数据不一致 采用 "网络请求成功后更新数据库" 策略,通过 Flow 监听数据库变化,确保 UI 展示最新数据
ViewModel 数据丢失(屏幕旋转) 使用SavedStateHandle保存关键数据,或通过数据库 / 缓存恢复状态

2. 性能优化建议

  • 协程调度优化 :避免频繁切换调度器,IO 操作集中在Dispatchers.IO,UI 操作仅在Dispatchers.Main
  • Flow 背压处理 :大量数据发射时,使用bufferconflate等操作符控制数据流速度,避免 OOM;
  • 内存优化 :Repository 层使用LruCache限制内存缓存大小,数据库查询使用limit分页;
  • 启动速度优化 :通过CoroutineStart.LAZY延迟初始化非必要协程,减少启动时的异步任务。

五、总结

Kotlin + 协程 + Flow+MVVM 这套新开发模式,并非孤立技术的简单叠加,而是一套围绕 "高效、稳定、可维护" 构建的完整解决方案。它解决了传统 Android 开发的核心痛点,让开发者能够聚焦业务逻辑而非技术细节。

学习路径

  1. 基础阶段:掌握 Kotlin 核心语法(空安全、扩展函数、Lambda);
  2. 进阶阶段:学习协程原理与 Flow 操作符,理解异步编程思维;
  3. 实战阶段:基于 MVVM 架构搭建项目,实现完整业务流程(登录、列表展示、数据缓存);
  4. 优化阶段:学习单元测试、性能优化,掌握避坑技巧。

未来技术趋势

  • Compose 与 MVVM 结合:Jetpack Compose 作为新一代 UI 框架,与 MVVM 架构天然契合,未来会成为主流;
  • Kotlin Multiplatform:跨平台开发逐渐成熟,可复用业务逻辑到 iOS、Web 等平台;
  • AI 辅助开发:通过 AI 工具自动生成 Kotlin 代码、协程逻辑,进一步提升开发效率。

掌握这套新开发模式,不仅能提升当前项目的开发效率和稳定性,更能紧跟 Android 技术发展趋势,为未来技术升级奠定基础。

相关推荐
冷雨夜中漫步8 小时前
Claude Code源码分析——Claude Code Agent Loop 详细设计文档
java·开发语言·人工智能·ai
超龄编码人8 小时前
Qt Widgets Designer QTabWidget无法添加布局
开发语言·qt
直奔標竿8 小时前
Java开发者AI转型第二十六课!Spring AI 个人知识库实战(五)——联网搜索增强实战
java·开发语言·人工智能·spring boot·后端·spring
Python大数据分析@9 小时前
CLI一键采集,使用Python搭建TikTok电商爬虫Agent
开发语言·爬虫·python
@小码农9 小时前
2026年3月Scratch图形化编程等级考试一级真题试卷
开发语言·数据结构·c++·算法
这儿有一堆花9 小时前
住宅代理(Residential Proxy)技术指南
开发语言·数据库·php
一只大袋鼠9 小时前
Java进阶:CGLIB动态代理解析
java·开发语言
秦ぅ时9 小时前
保姆级教程|OpenAI tts-1-hd模型调用全流程(Python+curl+懒人用法)
开发语言·python
Eiceblue9 小时前
使用 C# 将 Excel 转换为 Markdown 表格(含批量转换示例)
开发语言·c#·excel
爱滑雪的码农9 小时前
Java基础十三:Java中的继承、重写(Override)与重载(Overload)详解
java·开发语言