深入 Android Studio Gemini 代码助手:从上下文感知补全到多文件重构的 AI 辅助开发全链路
上周在重构一个 Compose 页面的状态管理时,我把 ViewModel 里的 MutableState 全部切成了 StateFlow。光标移到 Composable 函数里,Gemini 直接补全了整段 collectAsStateWithLifecycle() 调用链,连 import 都自动加好了。更让我意外的是,它生成的 API 匹配了项目里的 lifecycle 2.8.x,没塞进来一个已废弃的旧签名。
这背后是 Gemini 的上下文窗口在起作用。它比你单次补全看到的范围大得多,只是大部分场景下我们没把它用透。
上下文窗口:Gemini 到底”看”到了什么
搞清楚 Gemini 每次推理的输入范围,就能解释补全质量为什么忽高忽低。我通过一些反向测试,把上下文拆成三层:
第一层:当前文件全量。 不只是光标附近的几行代码,而是整个文件的 AST、注释和 import 列表。所以你在文件开头 import 了 kotlinx.coroutines.flow.StateFlow,Gemini 在文件末尾写代码时会自然引用它。
第二层:项目语义索引。 IDE 后台维护了一个项目级代码索引,Gemini 可以从中查询:
- 依赖库的 API 签名(从 Gradle 依赖树提取)
- 项目中其他类的公开接口
- 最近的 Git 变更记录
我验证过这个机制:新建一个 UserRepository 类,然后在另一个文件里敲 val repo = UserRep,Gemini 不仅补全了类名,还提示了构造函数参数。这不是模糊匹配,它确实查询了代码索引。
第三层:对话历史与编辑器操作序列。 如果你先让 Gemini 生成了一段 DataStore 存取方法,之后在别处写 val settings =,它会倾向于给 DataStore 相关的补全,而不是 SharedPreferences。这个短期记忆窗口大概维持 10 到 15 次交互。
// 如果你之前让 Gemini 生成了这段代码:
private val _userFlow = MutableStateFlow<User?>(null)
val userFlow: StateFlow<User?> = _userFlow.asStateFlow()
// 然后在另一个函数里写:
fun updateUser(/* 光标在此,Gemini 会自动建议参数类型和赋值逻辑 */)
三层上下文叠加在一起,补全质量最终取决于代码库本身的一致性。命名规范、架构清晰的项目,Gemini 表现比混乱的遗留代码好一个数量级。这一点在多个项目的对比中反复验证过。
Prompt 工程:注释就是 Prompt
常听到同事抱怨 Gemini 生成的东西不对,但很少有人反过来检查自己给它的输入信号够不够好。在 Android Studio 里,你写的注释就是 Gemini 的 prompt。
Gemini 对三种信号响应最准:
信号一:函数签名和注释的语义组合。 花时间写好函数名和 KDoc,比先写一堆半成品代码再让它猜意图高效得多。
/**
* 从本地 DataStore 和远端 API 获取用户设置,优先使用缓存,
* 缓存超时 30 分钟后触发后台刷新,刷新失败不阻塞 UI。
*/
suspend fun getSettings(): Flow<SettingsState> {
// 留空,让 Gemini 生成实现
}
注释里的 “DataStore”、“30 分钟超时”、“后台刷新”、“不阻塞 UI” 这些关键词,能让 Gemini 生成包含 combine、flatMapLatest 和缓存时间戳判断的完整实现。
信号二:类型先行。 先定义数据类和接口,后让 Gemini 写实现。类型系统本身就是一种强约束。
sealed interface SettingsState {
data object Loading : SettingsState
data class Success(val settings: UserSettings) : SettingsState
data class Error(val message: String, val cached: UserSettings?) : SettingsState
}
// Gemini 会根据 sealed interface 的结构生成完整的 when 分支
信号三:约束性注释。 “不要用 GlobalScope”、“用 Flow 而非 LiveData”、“保持和现有代码风格一致”——这类否定式和偏好式指令,Gemini 理解得比通用大模型精准。Android Studio 对 Gemini 做了 Android 领域微调,这是 GitHub Copilot 这类通用方案做不到的。
一个踩过的坑:避免中英文混写注释。Gemini 的训练语料以英文代码库为主,英文注释的意图识别准确率明显高于中文。实测同一段逻辑用英文写 KDoc,生成代码的首选匹配率能高出 30% 左右。
多文件重构:边界和策略
2024 年底 Gemini 加入了多文件重构能力,实际用下来比单文件补全复杂不少。用一个真实案例来说明。
场景:把项目里 12 个 ViewModel 的 LiveData 批量迁移到 StateFlow。
手动改要动四处:ViewModel 字段声明、所有 observe() 调用点、测试代码里的 LiveData 断言、Hilt 模块里的绑定。右键菜单「Gemini > Modify with Gemini」可以接这个活:
// 在任意一个 ViewModel 文件里选中 LiveData 相关代码,输入重构指令:
// "将该 ViewModel 以及所有引用它的地方的 LiveData 改为 StateFlow,
// 同步更新测试文件的断言方式"
Gemini 会分析引用链,产出一份修改计划。但它不会自动执行全部变更,而是逐个文件弹出 diff,需要手动确认。这个”半自动”设计其实是对的——跨文件重构容易引入隐式依赖变更,全自动执行风险太高。
实际踩过的坑:
- ViewModel 里用了
MediatorLiveData做合并的,Gemini 会直接用一个combine替代,但addSource的动态注册行为丢了 - Room 返回
LiveData<List<T>>改成Flow<List<T>>时,Gemini 不会提醒你改 DAO 的返回类型。这需要跨模块感知,目前还做不到
实践策略:把跨文件重构当作”脚手架生成”而不是”精确替换”。让 Gemini 先跑一遍,生成所有文件的修改建议,然后人工审查不可逆的逻辑变更。别指望一键完成——把它当成一个能快速出候选方案、省掉 80% 打字工作的工具。
工程落地:三条建议
第一,.geminiignore 和 context 配置值回票价。 在项目根目录建 .gemini/context 文件,写入项目背景描述(架构模式、命名约定、关键依赖版本),Gemini 每次推理都会读这段上下文。比每次对话开头重复描述项目背景管用得多。
# .gemini/context
本项目使用 MVVM + Clean Architecture。View 层使用 Jetpack Compose + Navigation,
ViewModel 通过 StateFlow 暴露状态,Repository 层返回 Flow<T>。
依赖注入使用 Hilt。网络层使用 Retrofit + Kotlinx Serialization。
命名约定:Repository 以 -Repository 结尾,UseCase 以 -UseCase 结尾。
测试使用 MockK + Turbine。
第二,代码生成用内联聊天,别用侧边栏。 代码编辑区 Ctrl + \(Mac 是 Cmd + \)唤出的内联聊天窗口,上下文限定在选中代码块,推理更聚焦。侧边栏对话适合开放式需求讨论,但不适合生成具体代码。
第三,把它当作结对编程的初级伙伴。 Gemini 擅长样板代码、重复模式补全、API 速查、简单重构。架构决策、并发安全分析、领域建模这些,还是你自己的活。我现在的习惯是让 Gemini 出第一版,我来审查修正——它省的是打字时间,不是思考时间。