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 技术发展趋势,为未来技术升级奠定基础。

相关推荐
2501_944448472 小时前
数据可视化 Kotlin KMP OpenHarmony图表生成
开发语言·信息可视化·harmonyos
xinhuanjieyi2 小时前
MCP分析某wordpress网站 时间所在的背景动画,并用php框架webman复刻下来
开发语言·php
jwn9992 小时前
Laravel1.x:PHP框架的初心与革新
开发语言·php
蜡台2 小时前
JavaScript async和awiat 使用
开发语言·前端·javascript·async·await
蹦哒2 小时前
Kotlin DSL 风格编程详解
android·开发语言·kotlin
枫叶丹42 小时前
【HarmonyOS 6.0】ArkWeb 深度解读:getPageOffset20 与网页滚动偏移量获取能力的演进
开发语言·华为·harmonyos
独特的螺狮粉2 小时前
开源鸿蒙跨平台Flutter开发:室内探险游戏应用
开发语言·flutter·游戏·华为·开源·harmonyos·鸿蒙
坏小虎2 小时前
~/.zshrc 和 ~/.bash_profile 详细介绍与区别
开发语言·bash
独特的螺狮粉2 小时前
开源鸿蒙跨平台Flutter开发:喝水时间提醒应用
开发语言·flutter·华为·信息可视化·开源·harmonyos·鸿蒙