尧图网络 高端网站定制 · 原创设计
免费咨询热线
400-888-6620
免费获取方案
KMM实战:Android+iOS共享业务逻辑的架构设计与踩坑指南
接手这个项目的时候我心里其实有句话憋着没说这活儿不该是一个团队写两套。产品要求 Android 和 iOS 的功能完全对齐网络层、数据缓存、状态管理几乎一模一样但工程代码却得各自维护。那段时间我审代码特别痛苦两边的逻辑命名不同、返回结构不同、异常处理习惯也不同一个规则改动要改两遍。后来我把核心业务逻辑抽出来用 Kotlin Multiplatform MobileKMM做了一个共享层Android 侧保留了原来的 MVVM 架构iOS 侧继续用原生 UI只在业务层接共享代码。这篇文章就是把那次重构中的选型思考、架构设计、完整实操步骤和踩过的坑整理出来给想入门 KMM、又不想放弃 Android 架构体系的开发者一个可参考的路线。文章适合三类人已经在做 Android 开发、想了解跨平台方案但不想把 UI 也交给渲染引擎的开发者团队有双端业务一致性需求、准备试点共享代码的技术负责人以及刚搭完 KMM 项目、正在困惑怎么设计包结构和依赖的实践派朋友。我会把共享代码的边界、Ktor 网络层、kotlinx.serialization 序列化、expect/actual 平台适配、Android ViewModel Compose 的对接方式以及 Xcode 接入流程都过一遍最后还附上实际开发中高频出现的问题清单。这不是一篇纯概念科普更多是我在真实项目里“用手摸出来的经验”。1. 项目背景与KMM选型思考1.1 双端开发的核心痛点先说背景。这个项目是一个内容聚合类的 AppAndroid 和 iOS 要展示几乎一样的信息流有搜索、收藏、离线缓存这些功能。早期团队规模不大为了快速交付Android 端用 Kotlin MVVMiOS 端用 Swift SwiftUI各自独立开发。业务跑起来之后维护成本开始直线上升。最典型的例子是数据解析。同一个接口后端返回同一份 JSON但 Android 端定义了Article数据类iOS 端又要定义一套Article结构体。字段映射、空值处理、类型转换两边各写一遍。这还只是数据层后面的网络状态机、缓存策略、分页逻辑、搜索关键词分词全都是重复劳动。更要命的是这类业务逻辑还没有太多 UI 差异纯粹是同一套规则用两种语言写了两遍。我在评估“要不要做一个跨平台方案”的时候先列了几个硬指标第一不能绑架原有 Android 架构现有 MVVM 和 Hilt 代码最好能保留第二iOS 端的 UI 不想用 Flutter 和 React Native 那套自绘引擎重写产品希望双端交互细节保持原生体验第三共享代码要能覆盖网络、序列化、数据库、协程这些核心基础能力而不是只能共享一点纯算法代码。这几条一列KMM 突然就变得很有吸引力。1.2 跨平台方案对比与KMM的位置那会儿我实际对比过几种主流方案。React Native 的优势是生态成熟、热更新方便但 JavaScript 和原生类型之间的桥接层很重复杂业务逻辑下类型安全没有保证。Flutter 确实能连 UI 一起共享渲染性能也做得好但等于把双端 UI 全部重写成 Dart原有 Android 架构基本要推倒而且不少公司对 Dart 技术栈有接受成本。KMM 的思路不太一样它只共享业务逻辑UI 仍然各写各的。KMM 在共享层直接使用 Kotlin这对 Android 团队特别友好。一个熟悉 Kotlin 的开发基本零成本上手。iOS 端虽然要保留 Swift但共享层编译成 Framework 后Swift 调用 Kotlin 的 API 并不会很别扭后续如果团队愿意还能用 Compose Multiplatform 把 Android/iOS UI 也逐步共享。从渐进式改造的角度来说KMM 是最容易嵌进现有 Android 项目的方案。当然 KMM 也有代价。它不像 Flutter 那样一个代码库搞定双端你仍然需要维护两个 UI 工程。Kotlin/Native 的编译速度也比较磨人初始编译和打包都比 JVM 慢不少。这其实就是在“共享程度”和“原生体验”之间做取舍KMM 把平衡点放在业务逻辑这层对我来说刚好合适。1.3 KMM的编译模型与expect/actual理解 KMM 怎么工作首先要理解 Kotlin Multiplatform 的编译目标和expect/actual机制。Kotlin 代码可以编译到多个目标平台Android 用的是 Kotlin/JVM最终打进 AAR 或 APKiOS 用的是 Kotlin/Native会编译成一个 Apple Framework再嵌到 Xcode 工程里。共享模块的源码主要放在commonMain里这部分代码在所有平台共用。但有些能力在 Android 和 iOS 上实现方式完全不同比如获取当前时间戳Android 可以用System.currentTimeMillis()iOS 则需要NSDate()。这种平台差异就靠expect和actual解决。共享层先写一个expect fun currentTimeMillis(): Long然后在androidMain和iosMain里分别提供actual实现。看起来很简单但设计上有个很容易犯的错不要把所有平台差异全堆到单个expect/actual里。我见过有人把网络配置、数据库驱动、日志打成一个大而全的Platform类结果每次扩展功能都要改两三个文件。更合理的做法是让expect/actual只暴露细粒度的能力比如“获取当前时间”“获取设备语言”“创建网络客户端”业务代码通过接口组合使用这些能力。抽象粒度越小平台实现越容易维护。2. 技术栈与共享代码架构设计2.1 共享代码的边界划分KMM 项目里最核心的问题不是怎么写代码而是“什么该放共享层什么不该放”。我的经验是领域模型、接口定义、网络请求、数据缓存、业务状态、纯逻辑工具类都放共享层所有和 UI 相关的代码比如 ViewModel 里跟生命周期绑定的部分、Composable、SwiftUI View不放共享层依赖系统底层的少量能力用expect/actual抽象后放共享层。大型 Android 项目习惯分层来到 KMM 后我依然保留了分层思想只不过把数据层和领域层挪进了共享模块。举个例子shared/ commonMain/ kotlin/ com.example.shared/ model/ // Article, User, PageResult network/ // Ktor client, API service repository/ // ArticleRepository 接口与实现 platform/ // expect 声明与公共抽象 com.example.shared/ di/ // 手动依赖容器或 Koin Module androidMain/ kotlin/ com.example.shared/ platform/ // actual 实现 di/ // Android 平台依赖配置 iosMain/ kotlin/ com.example.shared/ platform/ // actual 实现我用了一个叫di的包但整套工程没一上来就用 Hilt而是先用手动容器。原因很简单Hilt 是基于 JVM 的依赖注入框架没办法跑到 iOS 端。共享模块如果非要依赖一个 DI 框架那就得选 KMM 能支持的方案比如 Koin。但如果团队只是想在 Android 侧继续用 Hilt更稳妥的做法是共享模块保持纯代码不绑 DI由 Android 侧的 Hilt 负责把依赖注入进来。我后面会详细说这个对接方式。2.2 核心依赖选型网络、序列化、数据库KMM 共享层要跑通网络请求最常用的组合是 Ktor Client 加 kotlinx.serialization。Ktor Client 自带多平台引擎抽象在 Android 上可以用 OkHttp 引擎在 iOS 上可以用 Darwin 引擎业务层只面对统一的HttpClient。kotlinx.serialization 负责把 JSON 解析成 Kotlin 数据对象它通过编译期生成序列化器而不是靠运行时反射因此在 Kotlin/Native 上也能稳定运行。我在项目里还引入了 SQLDelight 做本地缓存。SQLDelight 的特别之处在于它并不直接写 SQL而是存储一个.sq文件编译时从同一个 SQL 定义生成 Kotlin 和 Swift 两边可以使用的类型安全 API。数据库逻辑写一次Android/iOS 都能直接用。不过它的驱动配置要分平台处理Android 用AndroidSqliteDriveriOS 用NativeSqliteDriver具体配置我会在第 3 章演示。选型时还有几个容易忽略的细节。比如三方库版本要确认是否支持 Kotlin/Native。有的库只支持 JVM强行放进 commonMain 会导致 iOS 无法编译。另一个细节是日志库我直接用 Kotlin 自带的println起步验证完链路后才接统一日志封装避免一开始就让日志库跨平台适配问题拖住进度。2.3 Android侧架构如何与KMM协作Android 侧架构我是这样设计的共享模块负责 Repository 和数据源Android App 模块负责 ViewModel 和 UI。ViewModel 需要调用共享模块时依赖被抽象成接口或具体类由 Hilt 在 App 模块装配。这样 Android 架构体系没有被破坏ViewModel 依然只管状态和事件不需要知道数据具体从哪个网络框架来。如果你从零开始一个 KMM 项目我更推荐让共享模块只暴露接口比如ArticleRepository把实现类留在共享层Android 侧通过 Hilt 把实现类绑定进依赖图。下面是一个简化版的思路interface ArticleRepository { suspend fun getArticles(): ListArticle }共享模块提供ArticleRepositoryImplAndroid App 模块里的 Hilt Module 负责把它提供出去Module InstallIn(SingletonComponent::class) object RepositoryModule { Provides Singleton fun provideArticleRepository( httpClient: HttpClient ): ArticleRepository { return ArticleRepositoryImpl( client httpClient, endpoint https://example.com/api/articles ) } }httpClient该由谁创建我把它放进androidMain的actual方法里确保 Android 侧使用 OkHttp 引擎iOS 侧使用 Darwin 引擎。这样共享代码不关心平台 HttpClient 细节Android 架构也依然干净。ViewModel 只依赖ArticleRepository接口用HiltViewModel拿到实例再通过StateFlow把状态暴露给 Compose UI。3. 搭建KMM工程从零实现共享模块3.1 工程结构与Gradle配置先说工程创建。用 Android Studio 的 KMP 项目模板生成后模块结构基本是shared加androidApp。第一步要改shared/build.gradle.kts把它配置成同时面向 Android 和 iOS。下面是我当时用的关键配置你可以直接参考着改plugins { kotlin(multiplatform) kotlin(plugin.serialization) id(com.android.library) } kotlin { androidTarget { compilerOptions { jvmTarget.set(org.jetbrains.kotlin.gradle.dsl.JvmTarget.JVM_11) } } listOf( iosArm64(), iosSimulatorArm64() ).forEach { target - target.binaries.framework { baseName Shared isStatic true } } sourceSets { commonMain.dependencies { implementation(org.jetbrains.kotlinx:kotlinx-coroutines-core:1.8.1) implementation(io.ktor:ktor-client-core:2.3.7) implementation(org.jetbrains.kotlinx:kotlinx-serialization-json:1.6.2) implementation(app.cash.sqldelight:runtime:2.0.1) } androidMain.dependencies { implementation(io.ktor:ktor-client-okhttp:2.3.7) implementation(app.cash.sqldelight:android-driver:2.0.1) } iosMain.dependencies { implementation(io.ktor:ktor-client-darwin:2.3.7) implementation(app.cash.sqldelight:native-driver:2.0.1) } } }有几处需要注意iosArm64对应真机iosSimulatorArm64对应 Apple Silicon 模拟器如果你的开发机还是 Intel Mac还要加上iosX64()。Framework 的isStatic true会生成静态库对大多数 App 来说更省心也避免了下游链接动态库的一系列问题。Gradle 版本和依赖版本我写的是实际可用的组合但你用自己的模板时最好以 IDE 默认版本为准不要盲目抄版本号。跨平台共享层很难避免“平台能力差异被写死在 commonMain”。比如前面说的expect fun createHttpClient(): HttpClient需要通过expect/actual把真正的 Ktor 引擎绑定到平台上。我在对应源集里这样实现// androidMain actual fun createHttpClient(): HttpClient { return HttpClient(OkHttp) { install(ContentNegotiation) { json(Json { ignoreUnknownKeys true }) } } } // iosMain actual fun createHttpClient(): HttpClient { return HttpClient(Darwin) { install(ContentNegotiation) { json(Json { ignoreUnknownKeys true }) } } }看到没有业务层完全不知道底层用了什么引擎只拿到一个配置好的HttpClient。这就是expect/actual最核心的用法让平台差异沉淀在个别函数里而不是渗透到整个业务层。3.2 在commonMain中定义业务模型和数据仓库接下来我以“文章列表”功能为例把共享模块的核心代码串联一遍。先在commonMain定义模型Serializable data class Article( val id: Int, val title: String, val summary: String? null, val url: String )summary和url都加默认值有讲究。后端数据结构不稳定如果某个字段偶尔缺失默认值能让解析不至于整体失败同时配合前面Json { ignoreUnknownKeys true }能大幅降低服务端新增字段导致的崩溃。然后定义数据仓库接口和实现interface ArticleRepository { suspend fun fetchArticles(): ListArticle fun observeArticles(): FlowListArticle } class DefaultArticleRepository( private val client: HttpClient, private val endpoint: String ) : ArticleRepository { override suspend fun fetchArticles(): ListArticle { return client.get(endpoint).body() } override fun observeArticles(): FlowListArticle { return flow { while (true) { emit(fetchArticles()) delay(30_000) } } } }observeArticles用 Flow 实现轮询在共享模块里定义状态流的形状Android 侧拿到后直接collect。这样做的好处是 iOS 端也能通过 Kotlin/Native 生成的回调接口拿到同样的数据流双端行为天然一致。3.3 用expect/actual处理平台差异除了 HttpClient还有一个高频差异就是时间获取和日志输出。我在共享层定义了两个 expect// commonMain expect fun currentTimeMillis(): Long expect val ioDispatcher: CoroutineDispatcherAndroid 实现很直接// androidMain actual fun currentTimeMillis(): Long System.currentTimeMillis() actual val ioDispatcher: CoroutineDispatcher Dispatchers.IOiOS 实现也简单// iosMain import platform.Foundation.NSDate import platform.Foundation.timeIntervalSince1970 actual fun currentTimeMillis(): Long (NSDate().timeIntervalSince1970 * 1000.0).toLong() actual val ioDispatcher: CoroutineDispatcher Dispatchers.Default注意Dispatchers.IO在 commonMain 里根本不存在它只在 JVM 平台提供。如果你直接在共享代码里写Dispatchers.IOAndroid 能编译iOS 会直接报未解析引用。这个坑非常隐蔽很多教程都没提。更严格的方案是跨平台代码统一用Dispatchers.Default或者像我这样用expect/actual暴露一个ioDispatcher在 Android 上保留 IO 调度器的并发优势在 iOS 上用 Default 调度器。顺带一提Dispatchers.Main在 KMM 里可用但 iOS 侧默认会绑定到主队列想在子线程操作网络请求务必用withContext(ioDispatcher)。3.4 Android UI层完整对接示例共享层就绪后Android UI 层开始接入。我用一个ArticleListViewModel观察共享层返回的Flow再暴露成 Compose 可观察的StateFlow。HiltViewModel class ArticleListViewModel Inject constructor( private val repository: ArticleRepository ) : ViewModel() { private val _uiState MutableStateFlowArticleListUiState(ArticleListUiState.Loading) val uiState: StateFlowArticleListUiState _uiState.asStateFlow() init { viewModelScope.launch { repository.observeArticles() .map { articles - ArticleListUiState.Success(articles) as ArticleListUiState } .catch { error - emit(ArticleListUiState.Error(error.message.orEmpty())) } .collect { state - _uiState.value state } } } }对应的 UI State 定义sealed interface ArticleListUiState { data object Loading : ArticleListUiState data class Success(val articles: ListArticle) : ArticleListUiState data class Error(val message: String) : ArticleListUiState }Compose UI 只需要观察uiState简单渲染三种状态。这一层我刻意没放任何业务逻辑只是状态的映射和展示。共享层负责“数据怎么来”ViewModel 负责“状态怎么变”UI 负责“界面怎么画”。Android 架构里的 MVVM 依然成立但数据来源已经跨平台了。iOS 端的接入逻辑类似只不过 Swift 调用 Kotlin 的 suspend 函数时会自动生成带回调的接口比如repository.fetchArticles { articles, error in ... }。在 SwiftUI 里我会包一层 ObservableObject然后再传给 SwiftUI View这样 iOS 端也能保持原生架构不动。4. 常见问题与排查实录4.1 iOS Framework接入与编译问题KMM 项目最挫败的时刻往往是 iOS 集成阶段。代码在 Android 上跑得好好的一接 iOS 就崩或者干脆编不过。我遇到的第一类问题是 Xcode 工程找不到 Framework。正确的做法是让 Xcode 在构建阶段先跑 Gradle输出并嵌入 Framework。常规的做法是在 Xcode 的 Build Phases 里加一个 Run Scriptcd $SRCROOT/.. ./gradlew :shared:embedAndSignAppleFrameworkForXcode这个任务会自动根据 Xcode 的环境变量决定编译哪个架构并嵌入到 App 包内。不加这个脚本你就算手动把 Framework 拖进工程设备运行也会报Framework not found。刚开始我用的是linkDebugFrameworkIosArm64这种固定任务只能在真机调试时用换模拟器又要重新编译非常低效。换成embedAndSignAppleFrameworkForXcode后Xcode 会自动适配目标架构省心太多。还有一类静态库链接问题。如果你把isStatic设为false生成动态库就需要确保 Framework 被正确地嵌入和签名否则上架审核容易被拒。我后来统一用它默认值也就是静态库问题基本清零。真机和模拟器共用一个静态 Framework 的方式配合embedAndSignAppleFrameworkForXcode就很稳妥。4.2 序列化、Ktor与协程的坑序列化是第二个高频雷区。Kotlin/Native 不支持 Java 序列化和常见的反射解析所以解析 JSON 一定要用kotlinx.serialization并且必须在模型类上加Serializable。我见过有人把 Gson 依赖拉到 commonMainAndroid 编译顺利iOS 直接失败。Gson 依赖大量 Java 反射在 Kotlin/Native 上根本不可用。这类问题在 Android 上不会爆发因为 JVM 环境太宽容了一旦切换到 Native 目标就会原形毕露。Ktor 的问题主要集中在引擎选择。commonMain 里只需要依赖ktor-client-core但 Android 和 iOS 必须分别加上各自的引擎依赖。如果只加 core 而没有加平台引擎运行时会出现找不到 engine 的异常提示“Failed to transform network response”或类似错误。我习惯用expect fun createHttpClient()把引擎绑定逻辑封装起来这样每个平台编译到的代码里Ktor 都知道该用哪个引擎。协程里还有一个编译期容易踩的坑就是我在 3.3 节提过的Dispatchers.IO。这算是最典型的 Native 平台差异之一。如果团队里有人习惯在共享层用Dispatchers.IOiOS 编译就会直接失败。解决办法有两个一是改成Dispatchers.Default二是用expect/actual暴露一个跨平台的ioDispatcher。前者改动快后者更精确。我更推荐后者因为桌面、JS 等其他目标平台未来扩展时也能保持统一入口。4.3 数据库与依赖注入的实践避坑SQLDelight 在 iOS 上接入时很多人会忘记添加native-driver依赖导致运行时NativeSqliteDriver找不到。另外.sq文件放在哪也影响编译建议统一放在commonMain/sqldelight/com/example/shared/db/目录下这样生成的代码路径稳定Android 和 iOS 都能引用。依赖注入的坑比较微妙。Koin 在 KMM 中确实是主流选择因为它是 Kotlin 实现的支持 Native但它会让你的共享模块增加一个运行时依赖并且初始化逻辑要放到各平台的启动点。如果团队 Android 侧已经习惯 Hilt 或其他 JVM 专用 DI我建议不要在共享层强行引入 DI 框架而是保持共享模块的纯函数和接口化设计把 DI 装配放在各平台 App 模块里。这本质上是一种架构决策共享层只提供“零件”由平台层决定“怎么组装”。我还吃过一个亏就是在共享模块里写全局单例对象。Android 端由于类加载机制通常没问题但 Kotlin/Native 的全局单例如果持有HttpClient或数据库驱动测试和并发环境下容易出现生命周期问题。更安全的方式是让共享层提供工厂函数由外部持有实例并注入。听上去“不够优雅”但换来的可测试性和可预测性非常值得。5. 双端集成与工程化建议5.1 Xcode集成流程与自动化任务KMM 工程虽然一份 Gradle 管理共享模块但最终还是要和 Xcode 联动。完整的集成流程大概是在 Xcode 中新建一个 App Target把shared生成的 Framework 链接到项目里然后在 Build Phases 最前面插入调用 Gradle 的脚本。注意脚本执行目录要切到项目根目录否则 Gradle 找不到工程。我建议再配一个简单的 CI 脚本分别验证 Android 和 iOS 的编译与单测。Android 侧可以跑./gradlew :androidApp:assembleDebug :shared:testDebugUnitTestiOS 侧可以先在本地跑./gradlew :shared:linkDebugFrameworkIosSimulatorArm64CI 里再通过xcodebuild打包。共享逻辑的单元测试直接跑 JVM 测试就好速度很快UI 测试仍然要分别跑 Android instrumentation 和 iOS XCTest。这套结构搭好后双端版本可以并行开发共享层代码变动会同时影响两边的编译结果相当于把双端一致性从“口头约定”变成“编译期保障”。5.2 团队协作与代码评审关注点KMM 项目在团队协作上和纯 Android 项目很不一样的体验在于共享层的代码评审标准必须更高。因为一行烂代码只改一次影响的是双端成本。我们团队后来列了几条硬规则commonMain里不允许出现if (Platform.isAndroid())这类直接判断平台的分支必须抽象成接口或expect/actual平台源集里的actual实现必须尽量薄只做能力适配不写业务逻辑新增一个expect前先讨论是否有必要继续跨平台还是直接让双端各写各的。“非必要不入共享层”这条原则我用下来非常有效。很多功能看起来两边逻辑差不多但实际上只是“恰好当前版本相似”未来很容易分叉。比如 iOS 端依赖了某个本地系统能力Android 端根本没有对应能力那这块业务逻辑就留在各端不要为了共享而共享。KMM 不是银弹它是一把需要克制使用的好刀。我最后再分享一点KMM 项目里最该舍得投入的就是共享层测试我用普通 JUnit 给 Repository 和序列化写测试双端稳定性提升非常明显。只要共享层核心逻辑稳了Android 和 iOS 的后续迭代就不会被底层反复拖累。
RELATED

相关推荐

D2D通信模式选择原理与MATLAB仿真实践指南

D2D通信模式选择原理与MATLAB仿真实践指南

1. D2D模式选择到底在选什么1.1 三种模式与系统模型做通信方向的同学,大概率绕不开一个词——D2D通信,也就是Device-to-Device,终端直通。简单说,你手里的手机不发到基站,而是直接发给旁边另一台手机,这就是…

📅 2026/10/21 3:20:51
NFA转DFA实战:从正则引擎到词法分析的确定性自动机构建

NFA转DFA实战:从正则引擎到词法分析的确定性自动机构建

1. 项目概述:为什么“NFA到DFA的转化”不是一道作业题,而是一把打开编译器与正则引擎底层的钥匙你第一次在《形式语言与自动机》课本里看到“NFA转DFA”这六个字时,大概率是在期末前两周的深夜,对着状态转移表发呆,手边…

📅 2026/10/21 3:20:51
策略梯度到PPO:降低方差、步长约束与连续控制全解析

策略梯度到PPO:降低方差、步长约束与连续控制全解析

学策略梯度的时候,很多人都会经历同一个阶段:看公式觉得理所当然,REINFORCE 就两行,策略梯度定理也清清楚楚,但一旦自己动手训练一个智能体,奖励曲线抖得像心律失常。我当初跟这门强化学习公开课的时候&…

📅 2026/10/21 3:20:51
MORE NEWS

更多资讯

📰

轻量级实时事件分析工具rea:从事件采集到告警可视化的实践

后台数据越堆越多,每次想临时查个指标都得现写脚本,查完又扔。后来实在受不了,我就花了两周时间做了个代号叫“rea”的小东西——一个轻量级的实时事件分析工具。简单说,就是给日志和用户行为事件装了一根“实时水管”&#xff0c…

📰

用先知AI精炼男装产品语言:从概念到爆款的实战方法

做男装产品这么多年,我见过太多“设计师自我感动、销售卖不动、老板压库存”的循环。新版型打出来了,面料选好了,但消费者就是看不懂,说不出你比隔壁那家好在哪。问题往往不在产品本身,而在你没把概念翻译成市场上的语…

📰

车道线检测从训练到TensorRT部署实战:轻量化模型与INT8量化全流程

车道线检测这个题目,做视觉的同行基本都绕不开。尤其是自动驾驶场景,车道线是感知链路里最基础也最关键的一环,它直接决定车辆能不能稳稳待在自己这条车道里,也影响后续换道、转弯等决策。我这次做的不是单纯训练一个模型&#xf…

📰

2026年起号新逻辑:AI工具一分钟生成原创图文与多平台分发实操

说实话,2026年再谈“起号”,玩法跟前几年已经完全不是一回事了。过去那种“搬运伪原创批量发”的野路子,现在基本是发一个死一个;而真正能稳定起号的内容,反而回到了最基础的“原创图文”赛道。这篇东西想聊的就是我最…

📰

从零手写字符设备驱动:安卓底层驱动的灵魂与实战

1. 为什么说字符设备驱动是安卓驱动的灵魂1.1 安卓系统里驱动到底站在哪一层我在之前的系列文章里反复强调过一件事:安卓系统从上到下是一条完整的调用链,应用层跑在虚拟机上,框架层提供各种服务,再往下是系统服务和硬件抽象层&am…

📰

CoppeliaSim+Python小车视觉追踪:从仿真到真机移植的完整实现指南

简介:面向机器人仿真与控制学习的CoppeliaSim(原Vrep)Python小车追踪工程,聚焦如何在仿真环境中驱动小车实时追踪目标点,覆盖环境搭建、API调用、路径规划与控制算法联调等关键环节。压缩包共17个文件,包含…

TODAY

今日更新

THIS WEEK

本周精选

THIS MONTH

本月热门

读完文章,想聊聊您的网站?

告诉我们您的行业与需求,资深顾问一对一梳理方案与报价,全程免费。

📞 💬