1. 项目概述如果你和我一样在过去几年里深度使用过各种AI编程助手来写Jetpack Compose代码那你一定经历过那种“看起来对跑起来崩”的尴尬时刻。助手生成的代码能编译但一运行就发现remember用得不对重组性能一塌糊涂Modifier的顺序乱七八糟甚至还会给你编造一些根本不存在的API参数。这感觉就像请了个“懂王”助手它自信满满地给你一堆似是而非的建议最后还得你自己花半天时间去Debug。今天要聊的这个aldefy/compose-skill项目就是为了彻底解决这个问题而生的。它不是另一个教你写Compose的教程而是一个直接“武装”你AI助手的技能包让AI从“凭感觉猜”变成“看源码答”。简单来说compose-skill是一个面向AI编程助手如Claude Code、GitHub Copilot、Cursor等的插件或技能集。它的核心价值在于为这些大语言模型提供了两样关键东西一是20份覆盖Compose全领域的实战参考指南从状态管理到性能优化从多平台适配到生产环境避坑二是6份直接从androidx/androidx和compose-multiplatform-core官方源码库中提取的Kotlin源代码文件。当你的AI助手在处理Compose相关问题时它会优先查阅这些经过整理的、高质量的参考资料和真实的源代码而不是依赖其训练数据中可能过时、模糊或错误的记忆从而生成出更准确、更符合最佳实践、甚至能规避常见生产问题的代码。这个项目适合所有正在或打算使用Jetpack Compose进行开发的Android、桌面、iOS或Web开发者尤其是那些希望借助AI提升编码效率但又苦于AI生成代码质量不稳定的朋友。无论你是刚接触Compose的新手还是正在将大型项目迁移到Compose的资深工程师这个技能包都能让你的AI助手变成一个更可靠的“结对编程”伙伴。2. 核心设计思路为什么AI需要“技能包”2.1 大语言模型在代码生成上的固有缺陷要理解compose-skill的价值首先得明白当前AI编程助手在生成Compose代码时的痛点。大语言模型LLM本质上是基于概率的文本生成器。它们在训练时“阅读”了海量的公开代码包括GitHub上的项目、教程、甚至是一些过时或有错误的代码片段。当被问及Compose问题时模型会基于这些训练数据中的模式进行联想和生成。这就导致了几个典型问题API幻觉模型可能会“记住”一个模糊的函数签名或者将不同版本、不同库的API混在一起生成出语法正确但实际不存在的代码。比如它可能给Modifier.clickable添加一个不存在的pressedColor参数。模式过时Compose发展非常快最佳实践也在不断演进。模型训练数据可能包含大量基于早期Compose版本如1.0.x或已废弃导航库如navigation-compose的旧字符串路由方式的代码。缺乏上下文感知模型很难理解特定场景下的最优选择。例如它知道要用remember但分不清何时该用remember { mutableStateOf() }何时该用rememberSaveable来保存状态何时又该用derivedStateOf来派生状态以避免不必要的重组。忽略性能陷阱生成功能正确的代码相对容易但生成高性能的代码则难得多。模型可能不会主动为LazyColumn的项添加key可能使用不稳定的参数导致重组范围扩大也可能在LaunchedEffect中错误地管理协程生命周期。compose-skill的设计哲学就是用确定性的、高质量的知识源去约束和引导概率性的模型生成。它不试图取代模型而是为模型提供一个精准的“外置知识库”。2.2 双层知识体系指南与源码的结合项目的架构非常清晰采用了“指南源码”的双层设计这模仿了优秀工程师解决问题的思路先查最佳实践文档搞不定再深挖源码。第一层实战参考指南Guidance Docs这20个Markdown文件是经过提炼的、面向任务的“操作手册”。每个文件聚焦一个核心主题例如state-management.md、performance.md。它们的内容不是API文档的复述而是充满了“该怎么做”和“不该怎么做”的对比、常见的陷阱、以及针对特定场景的代码模式。例如在modifiers.md中它会明确告诉你Modifier的调用顺序为什么重要比如clickable必须在padding之前并给出记忆口诀。在production-crash-playbook.md中它直接列出了6种在生产环境中真实出现过的崩溃模式及其修复方法比如如何防御DrawScope尺寸为零的情况。这些指南的作用是让AI助手快速获得经过验证的、上下文相关的解决方案跳过“摸索”和“试错”的过程直接输出符合最佳实践的代码建议。第二层源代码收据Source Code Receipts这是compose-skill最具杀伤力的特性。它包含了从官方仓库直接提取的、真实的Kotlin源码片段。文件如runtime-source.md、material3-source.md。当AI助手遇到模糊的API行为问题或者需要确认某个内部机制时比如remember的键比较逻辑、LazyList的布局算法它可以“查阅”这些源码文件。这相当于给了AI助手“查看官方实现”的能力。例如当用户问“derivedStateOf会不会在每次重组时都重新计算”时助手不再需要基于训练数据猜测而是可以直接引用runtime-source.md中的相关源码逻辑来给出确切答案。这种基于源码的推理极大地提高了回答的准确性和权威性从根本上杜绝了API幻觉。2.3 技能包的工作流程当你向集成了compose-skill的AI助手提出一个Compose相关问题时触发的工作流程如下意图识别助手检测到问题中包含“Compose”、“Composable”、“LazyColumn”等关键词触发compose-skill技能。查阅总纲助手首先读取SKILL.md文件。这个文件定义了整个技能的工作流程并包含一个类似“检查清单”的映射表将常见问题类型关联到具体的参考文件。定位专题根据问题类型助手加载对应的参考指南。例如关于列表卡顿的问题会同时加载lists-scrolling.md和performance.md。生成建议助手综合参考指南中的模式、禁忌和代码片段生成初步的解决方案。源码验证可选如果问题涉及底层行为或模糊的API细节助手会进一步查询source-code/目录下的相关源码文件以确保建议与官方实现一致。输出答案最终助手会给出一个融合了最佳实践和源码依据的回答通常包含解释、代码示例以及关键的注意事项。这个过程将一次普通的问答升级为一次有据可查、有最佳实践指导的“代码审查”和“方案设计”。3. 技能包内容深度解析compose-skill覆盖的范围之广、内容之深远超一个普通的“备忘清单”。我们来拆解几个关键模块看看它到底提供了什么硬核内容。3.1 状态管理与副作用从“能用”到“精通”状态管理是Compose中最核心也最容易出错的部分。普通的AI助手可能会千篇一律地使用remember { mutableStateOf() }。但compose-skill的state-management.md和side-effects.md指南会教会AI助手如何做精确的选择。状态类型的选择决策树 技能包内化了一套决策逻辑指导AI根据场景选择正确的状态原语remembermutableStateOf用于组件内部的、非持久化的、可变状态。这是最基础的。rememberSaveable当状态需要在配置更改如屏幕旋转后存活时使用。AI会学习到如何使用Saver接口处理自定义对象的保存。derivedStateOf当状态是由一个或多个其他状态计算派生而来且计算成本较高时使用。这是优化重组的关键。技能包会强调derivedStateOf内部的计算应该纯净且快速并且其结果是State对象需要被remember。状态提升State HoistingAI会学习识别何时应该将状态提升到父组件以实现更好的可测试性和复用性。它会建议将状态作为参数传入而不是在内部直接获取。snapshotFlow用于将State的变化转换为冷流Flow以便在协程中处理更复杂的逻辑。副作用管理的生命周期意识side-effects.md让AI助手深刻理解Compose的生命周期LaunchedEffect用于在可组合项中启动协程并管理其生命周期当键变化或可组合项退出组合时取消。关键点是正确设置key。AI会学会根据依赖项如某个ID来设置key而不是滥用Unit。DisposableEffect用于注册和清理需要与组合生命周期绑定的资源如监听器、订阅。AI生成的代码会包含完整的onDispose块。SideEffect用于将Compose状态同步到非Compose管理的对象上且每次成功重组后都会运行。AI会明白它不适合执行耗时操作。通过这份指南AI生成的代码会从“能运行”升级为“生命周期安全、性能优化”的工业级代码。3.2 性能优化告别“Janky”的列表列表卡顿是Compose开发中最常见的性能问题。performance.md和lists-scrolling.md为AI提供了系统的优化工具箱。重组优化的核心武器Stable和Immutable注解AI会学习为数据类或接口添加这些注解帮助Compose编译器推断稳定性从而跳过不必要的重组。它会解释Immutable意味着所有属性都是val且深度不可变Stable意味着属性值可变但equals能正确反映变化。key的重要性在LazyColumn/LazyRow中为每个项提供稳定且唯一的key是保证高效增删和避免状态错乱的生命线。AI生成的代码绝不会遗漏这一点。contentType对于具有多种项类型的列表AI会建议使用contentType参数。这允许Compose在相同类型的项之间复用组合进一步提升性能。延迟读取Deferred ReadsAI会学会将状态读取尽可能推迟到Lambda表达式内部以缩小重组范围。例如将if (state.value) { Text(A) } else { Text(B) }改为Text(if (state.value) A else B)。列表项内部的优化避免在项内容中执行耗时操作AI会提醒不要在LazyColumn的项内容中直接进行图片解码、网络请求或复杂计算。使用derivedStateOf处理滚动状态当需要根据滚动偏移量派生状态如隐藏/显示FAB时AI会正确使用derivedStateOf来避免在每一帧滚动时都触发重组。当用户抱怨“列表滚动卡顿”时集成了技能包的AI助手会像资深性能调优专家一样提供一份包含上述所有检查点的诊断报告和修改建议。3.3 多平台开发Compose Multiplatform跨越平台的鸿沟Compose MultiplatformCMP让代码共享成为可能但也引入了新的复杂性。multiplatform.md和platform-specifics.md指南确保了AI助手能生成正确的跨平台代码。expect/actual模式的正确使用 AI会理解何时该使用平台特定实现。例如获取设备ID// Common expect fun getDeviceId(): String // Android actual fun getDeviceId(): String Settings.Secure.getString(...) // iOS actual fun getDeviceId(): String UIDevice.currentDevice.identifierForVendor?.UUIDString ?: 技能包会指导AI将UI逻辑尽量放在common模块仅将真正的平台交互如传感器、特定API调用抽象为expect/actual。资源管理的统一方式 AI会学会使用Res.*由compose-resources插件提供来引用图像、字符串等资源而不是硬编码路径或使用平台特定的资源系统从而保证资源在Android、iOS和桌面端的一致性。平台特定UI的集成iOS当需要在Compose中嵌入原生UIKit组件时AI会正确使用UIKitView并处理好生命周期和尺寸。桌面对于窗口管理、系统托盘、菜单栏等桌面端特性AI会参考platform-specifics.md中关于Window、Tray、MenuBar的API用法。WebAI会了解WASM环境的限制比如对Canvas操作的特殊考量。有了这些知识AI助手生成的CMP代码将更加健壮避免出现“在iOS上调用Android专属API”这类低级错误。3.4 生产环境崩溃防御手册这是compose-skill中最具实战价值的部分之一。production-crash-playbook.md不是理论而是从真实线上崩溃中总结出的血泪教训。AI助手学习了这些模式后能在代码生成阶段就主动规避风险。六大崩溃模式示例零尺寸DrawScope崩溃在Canvas或DrawModifier中绘制时未考虑size可能为Size.Zero的情况例如在布局测量期间。防御性代码是if (size.width 0 size.height 0) { /* 绘制逻辑 */ }。derivedStateOf的陈旧依赖在derivedStateOf的派生计算中直接捕获了可变的引用如var或MutableState的引用而非其.value导致派生状态无法感知变化。AI会学会始终捕获.value。列表重复key导致的状态混乱在动态列表中为不同项赋予了相同的key导致项的状态如文本输入内容在项之间“乱窜”。AI会强制要求使用唯一且稳定的key。协程作用域生命周期泄漏在LaunchedEffect中启动了一个未正确绑定key的长期运行协程或者在rememberCoroutineScope中启动协程但未在DisposableEffect中清理导致Activity/Fragment泄漏。AI会确保协程的启动与组合生命周期绑定。导航堆栈中的参数传递错误使用类型安全路由时错误地处理了可空参数或复杂对象导致恢复时崩溃。AI会建议使用NavType序列化或Parcelable等安全方式。Modifier顺序导致的触摸事件冲突错误地将pointerInput或clickable放在padding等调整布局的修饰符之后导致点击区域错位。AI会牢记修饰符的应用顺序是从左到右、由外到内。当开发者编写相关代码时AI助手会像一个经验丰富的代码审查员主动插入这些防御性代码或提出警告将潜在的崩溃扼杀在摇篮里。4. 主流AI工具集成实操指南compose-skill的设计是平台无关的它只是一套Markdown文件。集成到不同AI工具中的方法大同小异核心都是让工具在遇到Compose问题时能读取到skills/compose-expert/目录下的这些文件。下面我以几个最主流的工具为例详细说明安装和配置过程。4.1 Claude Code最无缝的集成体验Claude Code原Claude for VS Code对技能Skills的支持非常原生和友好。技能本质上是放在特定目录下的Markdown文件集合。个人技能全局可用 这是最推荐的方式安装一次在你所有的项目里都能用。打开终端。执行以下命令克隆仓库并复制技能文件# 克隆技能仓库到临时目录 git clone https://github.com/aldefy/compose-skill.git /tmp/compose-skill # 确保个人技能目录存在 mkdir -p ~/.claude/skills # 将技能文件夹复制过去 cp -r /tmp/compose-skill/skills/compose-expert ~/.claude/skills/重启你的VS Code和Claude Code插件。完成之后在任何Kotlin项目中当你向Claude Code提问关于Compose的问题时它都会自动加载并使用compose-skill的知识来回答。你不需要在每个项目里做任何配置。项目特定技能 如果你希望技能只对某个特定项目生效比如团队项目你想确保所有成员使用同一份技能可以将技能文件夹放在项目根目录的.claude/skills/下。# 在项目根目录执行 git clone https://github.com/aldefy/compose-skill.git /tmp/compose-skill mkdir -p .claude/skills cp -r /tmp/compose-skill/skills/compose-expert .claude/skills/注意Claude Code会优先读取项目目录下的技能然后才是全局目录。这种方式适合团队共享配置。4.2 Cursor通过规则文件集成Cursor是另一个深度集成AI的编辑器。它通过.cursor/rules/目录下的规则文件来指导AI行为。在你的项目根目录下创建文件夹和规则文件mkdir -p .cursor/rules touch .cursor/rules/compose-skill.mdc编辑.cursor/rules/compose-skill.mdc文件内容如下--- description: Jetpack Compose expert guidance globs: **/*.kt --- Follow the instructions in skills/compose-expert/SKILL.md for all Compose-related code. Consult reference files in skills/compose-expert/references/ before suggesting patterns.globs: **/*.kt表示这个规则对所有Kotlin文件生效。接下来你需要把compose-skill的技能文件放到项目里。推荐使用Git子模块便于后续更新git submodule add https://github.com/aldefy/compose-skill.git skills/compose-skill或者直接克隆到项目目录git clone https://github.com/aldefy/compose-skill.git skills/compose-skill现在当你在Cursor中打开一个Kotlin文件并询问Compose相关问题时Cursor的AI就会遵循这条规则去查阅skills/compose-expert/下的指南来回答你。替代方案快速测试 如果你不想克隆整个仓库也可以直接将SKILL.md的核心工作流程和检查清单内容复制粘贴到Cursor的Settings Rules for AI文本框中。但这只能包含核心逻辑无法享受20个详细参考文件和源码查询的完整能力。4.3 GitHub Copilot修改项目级指令GitHub Copilot可以通过项目根目录下的.github/copilot-instructions.md文件来接收自定义指令。在项目根目录创建或编辑.github/copilot-instructions.md文件。在文件中添加关于Compose的指令段落指向技能目录## Jetpack Compose For Compose/Android UI work, follow the skill instructions in skills/compose-expert/SKILL.md. Consult reference files in skills/compose-expert/references/ for patterns, pitfalls, and source-code-backed guidance.同样你需要将技能文件放入项目。使用子模块是保持更新的好方法git submodule add https://github.com/aldefy/compose-skill.git skills/compose-skill重启你的IDE如VS Code以确保Copilot重新读取指令文件。之后Copilot在为你编写Compose代码补全或聊天回答时就会参考这些指令和技能文件。不过需要注意的是Copilot的指令系统相对简单其遵循复杂指令的能力可能不如Claude Code或Cursor的规则系统那么强但依然能显著改善其生成代码的相关性和准确性。4.4 通用方法AGENTS.md 与子模块对于其他支持自定义指令但无特定配置文件的AI工具如一些命令行AI助手一个通用的方法是使用AGENTS.md文件。在项目根目录创建AGENTS.md文件。内容如下# AGENTS.md ## Jetpack Compose For all Compose/Android UI tasks, follow the instructions in skills/compose-expert/SKILL.md and consult the reference files in skills/compose-expert/references/ before answering.将compose-skill添加为Git子模块git submodule add https://github.com/aldefy/compose-skill.git .compose-skill注意这里用了.compose-skill作为隐藏目录避免污染项目根目录视觉在你的AI工具中确保其配置为会读取当前目录或父目录中的AGENTS.md文件作为上下文。这种方法的好处是标准化任何能识别AGENTS.md约定的工具都可以工作。compose-skill的文档中也提到了对Codex CLI和Gemini CLI的支持其原理与此类似。安装后的验证 安装完成后一个简单的验证方法是向你的AI助手提问一个经典的、容易出错的Compose问题例如“如何实现一个在滚动到顶部时隐藏、滚动时显示的底部应用栏”观察其回答。一个集成了技能包的助手应该会提到使用LazyColumn的LazyListState。正确使用derivedStateOf来根据滚动偏移量派生一个布尔状态而不是在Composable函数中直接计算。可能还会提醒你添加key和考虑性能。 如果回答包含了这些细节并且代码格式规范说明技能包已经成功加载并发挥作用。5. 实战场景与效果对比理论说得再多不如看实际效果。我们通过几个具体的编码场景来对比一下使用compose-skill前后AI助手给出的建议有何天壤之别。5.1 场景一实现一个可折叠的卡片用户提问“帮我写一个可以展开/折叠的卡片Composable点击标题可以切换内容显示。”普通AI助手可能给出的代码Composable fun ExpandableCard(title: String, content: String) { var expanded by remember { mutableStateOf(false) } // 问题1状态未提升 Column( modifier Modifier .fillMaxWidth() .padding(16.dp) .clickable { expanded !expanded } // 问题2点击修饰符顺序可能影响padding区域 ) { Text(text title, style MaterialTheme.typography.headlineSmall) if (expanded) { Spacer(modifier Modifier.height(8.dp)) Text(text content) } } }问题分析expanded状态被定义在组件内部这降低了可测试性和复用性无法从外部控制初始状态或监听状态变化。clickable放在了padding之后这意味着只有卡片的内边距区域可以点击而不是整个卡片背景区域这不符合用户直觉。集成compose-skill后的AI助手回答/** * 一个可展开/折叠的卡片组件。 * param title 卡片标题 * param content 卡片展开时显示的内容 * param expanded 控制卡片是否展开的状态。应通过状态提升由父组件管理。 * param onExpandedChange 当展开状态变化时的回调。 */ Composable fun ExpandableCard( title: String, content: String, expanded: Boolean, // 状态提升为参数 onExpandedChange: (Boolean) - Unit, modifier: Modifier Modifier ) { // 使用 Card 组件提供 Material 设计外观 Card( modifier modifier .fillMaxWidth() .clickable { onExpandedChange(!expanded) } // 点击事件触发回调 .padding(16.dp), // 注意clickable 在 padding 之前确保整个卡片区域可点击 elevation CardDefaults.cardElevation(defaultElevation 4.dp) ) { Column( modifier Modifier.padding(16.dp) ) { Row( verticalAlignment Alignment.CenterVertically, horizontalArrangement Arrangement.SpaceBetween ) { Text( text title, style MaterialTheme.typography.headlineSmall ) // 提供一个视觉指示器 Icon( imageVector if (expanded) Icons.Default.ExpandLess else Icons.Default.ExpandMore, contentDescription if (expanded) Collapse else Expand ) } // 使用 AnimatedVisibility 提供平滑的展开/折叠动画 AnimatedVisibility( visible expanded, enter expandVertically() fadeIn(), exit shrinkVertically() fadeOut() ) { Spacer(modifier Modifier.height(16.dp)) Text( text content, style MaterialTheme.typography.bodyMedium ) } } } } // 在父组件中的使用示例 Composable fun ParentScreen() { var isCardExpanded by remember { mutableStateOf(false) } // 状态在父组件管理 ExpandableCard( title 详细说明, content 这里是卡片的详细内容..., expanded isCardExpanded, onExpandedChange { newValue - isCardExpanded newValue } ) }改进点分析状态提升将expanded状态作为参数传入并由父组件通过onExpandedChange回调来管理。这遵循了Compose的“单向数据流”和“状态提升”最佳实践使组件更纯粹、可测试。正确的Modifier顺序clickable在padding之前确保整个Card的填充区域都是可点击的。使用标准组件外层使用了Card组件提供了阴影和圆角更符合Material Design。添加了视觉反馈增加了图标来指示展开/折叠状态并提供了无障碍描述。平滑动画使用AnimatedVisibility替代简单的if语句为用户提供了流畅的展开/折叠动画体验。完整的文档添加了KDoc注释说明了参数用途和设计意图。这个回答不仅功能正确而且在用户体验、代码结构和可维护性上都达到了生产级标准。5.2 场景二优化一个性能不佳的列表用户提问“我的LazyColumn在快速滚动时很卡列表项比较复杂里面有图片和文本。”普通AI助手可能给出的建议“尝试使用remember缓存图片。”“确保你的Composable函数是稳定的。”集成compose-skill后的AI助手回答 它会生成一份详细的诊断和优化清单并引用performance.md和lists-scrolling.md“根据compose-skill的性能指南LazyColumn卡顿通常由以下几个原因导致请按顺序检查你的代码缺失或不当的key这是首要原因。请确保为LazyColumn的items或itemsIndexed提供了稳定且唯一的key。key应该基于项的数据ID而不是索引。// 错误使用索引作为key增删项时会导致状态混乱和性能问题 items(items.size) { index - ... } // 正确使用数据项的唯一ID items(items, key { it.id }) { item - ... }不稳定的项类型缺少contentType如果你的列表有多种项类型如标题、内容、图片使用contentType可以让Compose复用相同类型的组合。LazyColumn { items( items listItems, key { it.id }, contentType { it.type } // 假设每个项有一个type字段 ) { item - when (item.type) { ItemType.HEADER - HeaderItem(item) ItemType.CONTENT - ContentItem(item) } } }列表项内部有昂贵的操作图片加载不要在Composable函数内部直接调用BitmapFactory.decodeResource或同步网络请求。必须使用异步图片加载库如Coil、Glide的Compose支持它们内部会处理缓存和异步加载。复杂计算将任何非UI逻辑如数据格式化、字符串处理移出Composable函数或者用remember缓存计算结果。避免在项内容中创建新的Lambda或对象这会导致项的不稳定。尽量将回调提升到父组件或使用Stable注解标记持有回调的类。检查项的可组合函数是否稳定确保传递给列表项的数据类使用Immutable如果所有属性都是val或Stable注解。检查项的可组合函数的参数是否都是稳定的基本类型、String、函数类型默认是稳定的但自定义类需要注解。使用derivedStateOf处理滚动衍生状态如果你根据滚动位置来更新其他UI如隐藏FAB务必使用derivedStateOf。val listState rememberLazyListState() val isScrollingUp by remember { derivedStateOf { // 只在滚动方向真正改变时计算避免每帧重组 // ... 计算逻辑 } }考虑使用Placeholder对于网络图片使用Coil的placeholder或Glide的placeholder在加载时显示占位图避免布局抖动。性能分析最后使用Android Studio的Compose布局检查器或性能分析工具查看重组次数和范围定位具体的性能瓶颈。”这个回答是结构化的、可操作的并且直接指向了Compose列表性能优化的核心要点而不是泛泛而谈。5.3 场景三处理平台特定的代码Compose Multiplatform用户提问“我在Compose Multiplatform的common模块里需要获取设备的唯一标识在Android和iOS上分别怎么实现”普通AI助手可能给出的回答 可能会直接给出两段平台代码但不知道如何组织到CMP的expect/actual模式中或者给出的API可能是过时的。集成compose-skill后的AI助手回答 它会参考multiplatform.md给出符合CMP最佳实践的结构“在Compose Multiplatform中处理平台特定代码的标准方式是使用expect/actual声明。以下是获取设备ID的完整实现在commonMain源集中声明expect函数// commonMain/kotlin/com/yourpackage/platform/DeviceId.kt expect fun getDeviceId(): String在androidMain源集中提供actual实现// androidMain/kotlin/com/yourpackage/platform/DeviceId.kt import android.content.Context import android.provider.Settings import androidx.compose.ui.platform.LocalContext import org.koin.core.component.KoinComponent import org.koin.core.component.inject // 假设使用Koin依赖注入 // 方式一通过CompositionLocal获取Context适用于UI层 actual fun getDeviceId(): String { val context LocalContext.current return Settings.Secure.getString( context.contentResolver, Settings.Secure.ANDROID_ID ) ?: } // 方式二通过依赖注入更推荐便于测试 class AndroidDeviceIdProvider : KoinComponent { private val context: Context by inject() actual fun getDeviceId(): String { return Settings.Secure.getString( context.contentResolver, Settings.Secure.ANDROID_ID ) ?: } }在iosMain源集中提供actual实现// iosMain/kotlin/com/yourpackage/platform/DeviceId.kt import platform.UIKit.UIDevice import platform.Foundation.NSUUID actual fun getDeviceId(): String { // 注意从iOS 14开始获取设备唯一标识符需要特殊权限。 // 这里使用identifierForVendor它在同一供应商的应用间是相同的。 return UIDevice.currentDevice.identifierForVendor?.UUIDString ?: // 对于真正的唯一ID你可能需要钥匙串Keychain方案这需要更多平台特定代码。 }在common的UI代码中使用Composable fun SomeScreen() { val deviceId remember { getDeviceId() } // 注意这可能在UI线程中调用平台IO实际项目中应考虑异步获取 Text(text Device ID: ${deviceId.take(8)}...) // 出于隐私考虑不要显示完整ID }重要注意事项来自platform-specifics.md隐私与权限在Android上ANDROID_ID在Android 8.0以上是每个应用独立的。在iOS上identifierForVendor在用户删除所有该供应商的应用后会重置。获取真正的、持久的设备ID通常需要用户授权并且实现更复杂。异步操作getDeviceId()的Android实现涉及系统服务调用iOS实现涉及Objective-C桥接。在UI线程直接调用可能会卡顿。考虑将其封装在一个suspend函数或Flow中并通过viewModelScope或类似机制在后台调用。依赖注入强烈建议通过接口注入平台服务而不是在common代码中直接引用expect函数。这使单元测试更容易你可以在测试中注入一个模拟实现。这个回答不仅提供了代码还附带了平台差异说明、隐私考量、性能建议和最佳实践体现了对生产环境复杂性的深刻理解。6. 常见问题与排查技巧实录在实际使用compose-skill或集成过程中你可能会遇到一些问题。以下是我在实践和社区交流中总结的一些常见情况及解决方法。6.1 技能包未生效或效果不明显症状按照指南安装了技能包但AI助手生成的Compose代码似乎没有改善还是会出现旧的模式或错误。排查步骤确认安装路径首先检查技能文件是否被复制到了正确的位置。对于Claude Code检查~/.claude/skills/compose-expert/SKILL.md文件是否存在且内容完整。对于Cursor检查.cursor/rules/compose-skill.mdc文件内容和skills/compose-skill/目录是否存在。检查AI工具上下文大多数AI工具尤其是聊天形式的有上下文长度限制。compose-skill的指南内容非常详细可能不会被完整纳入每次对话的上下文。尝试在提问时明确要求助手“参考compose-skill技能”或“根据compose最佳实践”。对于Cursor或Claude Code的规则集成这通常是自动的。提问方式尝试提出更具体、更复杂的问题。对于非常基础的问题如“怎么写一个Text”AI可能直接从其基础训练数据中回答不会触发深度查阅技能文件。问一些涉及性能、状态管理、多平台的具体场景问题更能激发技能包的作用。工具支持度不同AI工具对外部技能/规则的支持程度不同。Claude Code和Cursor的集成最为深入和可靠。GitHub Copilot等工具主要依赖项目级指令其“遵守度”可能因模型版本和上下文而异。确保你使用的是最新版本的AI工具插件。查看AI的“思考过程”一些高级的AI工具如Claude Code的高级模式可以显示其参考了哪些文件。开启这个功能观察在你提问时它是否真的加载了SKILL.md或相关的references/*.md文件。6.2 技能包内容与当前Compose版本有差异症状技能包建议的某个API在你的Compose版本中找不到或已废弃。原因与解决compose-skill项目是基于特定时间点的androidx和compose-multiplatform-core源码生成的。Compose库在快速迭代新版本可能会引入新API或废弃旧API。检查版本首先查看compose-skill项目首页的徽章它标明了所基于的Compose如1.7和CMP如1.8版本。确保你的项目使用的Compose版本与之匹配或更高。查阅官方迁移指南如果遇到API废弃警告优先参考Android官方开发者网站的Compose版本迁移说明。贡献更新compose-skill是一个开源项目。如果你发现了版本差异可以查阅最新的官方源码并向项目提交Pull RequestPR更新相关的指南和源码片段。项目维护者通常欢迎这类贡献。临时处理在技能包更新前你可以将技能包中的建议作为“最佳实践思路”来参考但具体API用法需要结合当前版本的官方文档进行微调。6.3 在大型项目中集成遇到路径问题症状在大型单体仓库Monorepo或多模块项目中技能包的相对路径指令如skills/compose-expert/SKILL.md可能失效因为AI工具的工作目录或上下文根目录不确定。解决方案使用绝对路径不推荐在规则文件中使用绝对路径但这破坏了项目的可移植性。子模块放置策略将compose-skill子模块添加到整个代码库的根目录而不是某个子模块内。然后在各子项目的规则文件中使用相对于项目根目录的路径如../skills/compose-expert/。这需要仔细规划。环境变量或配置一些高级的AI工具允许在规则中使用环境变量。你可以设置一个环境变量如COMPOSE_SKILL_PATH指向技能目录然后在规则中引用该变量。但这需要工具支持且配置复杂。复制技能文件最稳妥但最不优雅的方式是将技能包的核心参考文件直接复制到每个需要它的子项目目录中并更新规则文件中的路径。这会导致重复且更新困难。联系工具支持对于Claude Code或Cursor如果遇到路径问题可以查看其官方文档看是否有设置“工作区根目录”或“技能搜索路径”的配置选项。个人建议对于大多数标准结构的Android或Kotlin多平台项目将技能包放在项目根目录并使用相对路径skills/compose-expert/是可行的。确保你的AI工具是从项目根目录启动或识别根目录的。6.4 技能包与团队编码规范冲突症状团队有自己严格的Compose编码规范而compose-skill的某些建议如特定的命名约定、代码结构可能与团队规范不一致。处理原则compose-skill提供的是技术正确性和性能最佳实践的指导而不是代码风格规范。它关注的是“是否会产生bug”、“性能是否最优”、“是否遵循Compose范式”。区分关注点向团队说明技能包解决的是“做什么”和“为什么”的问题例如必须用key必须用derivedStateOf而不是“怎么做”的格式问题例如函数名是camelCase还是snake_case缩进是4空格还是2空格。定制技能包你可以fork一份compose-skill仓库在保留其核心技术和源码内容的基础上修改其中的代码示例使其符合团队的代码风格如修改命名、调整格式。然后将这个定制版技能包集成到团队项目中。分层指导在团队的项目级指令文件如.cursor/rules/team-style.mdc中先包含团队编码规范再引入compose-skill。并说明当技术建议与风格规范冲突时以技术建议的实质内容为准但输出格式应符合团队规范。6.5 对AI助手的过度依赖与代码理解一个重要的提醒compose-skill是一个强大的辅助工具但它不能替代开发者自己对Compose原理的理解。知其然更要知其所以然当AI助手基于技能包给出一个优化建议时例如“这里应该用derivedStateOf”不要盲目接受。花点时间理解derivedStateOf解决了什么问题避免不必要的昂贵计算以及它的使用场景和限制。技能包附带的源码和解释就是为了帮助你理解。技能包是起点不是终点它总结了常见的模式和问题但不可能覆盖所有边界情况。你仍然需要培养自己阅读官方文档、调试代码和解决复杂问题的能力。审查AI生成的代码即使有了技能包也务必对AI生成的所有代码进行人工审查。检查逻辑是否正确是否引入了安全或隐私问题是否符合项目的特定业务需求。compose-skill的最佳使用方式是让它成为你的“第一道防线”和“实时代码审查员”帮你捕捉低级错误和性能反模式从而让你能更专注于高层次的业务逻辑和架构设计。它解放了你记忆琐碎细节的脑力但思考和决策的核心仍然在你手中。