ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

AI写Android代码的6大常见错误及修复方法

AI写Android代码的6大常见错误及修复方法 先给一个明确判断AI 写 Android 代码真正的问题不是“写不出来”而是“写出来看着合理放进项目就崩”。Philipp Lackner 在做 Android 开发经验分享时专门把这几年 AI 辅助编程里最常见的错误收敛成了 6 条。我按这 6 条逐个在 Android Studio 里复现、修复、验证了一遍发现绝大多数翻车点跟模型能力无关而是上下文、SDK 版本、生命周期、配置文件和验证习惯这五件事没跟上。这篇文章适合两类人。一类是已经在用 AI 补代码、但经常被 Gradle 同步失败和运行时闪退折磨的开发者另一类是团队里准备引入 AI 编程工具、但又担心代码质量失控的负责人。下面每一节都会说清楚错误长什么样、为什么会犯、怎么修、修完怎么判断真的好了。1. 先说清楚AI 写 Android 代码问题出在哪一层1.1 Android 项目为什么比普通脚本更容易翻车AI 写 Python、写 JavaScript通常把文件保存下来就能跑顶多缺两个包。Android 不是这样。一段 Android 代码要真正运行牵涉到 Gradle 构建、SDK 版本、Manifest 声明、资源文件、依赖坐标系以及设备上的真实生命周期。这意味着 AI 生成的代码“能不能跑”不取决于它自己写得多完整而取决于它对你的项目了解多少。它对项目一无所知时只能用训练数据里最常见、最“标准”的方式来写。问题是Android 开发里几乎不存在“完全标准”的项目有人用 Java有人用 Kotlin有人还在用 View 体系有人已经切到了 Compose有人 minSdk 是 21有人已经 26 以上。所以你会看到一个很奇怪的现场AI 给的代码单独看没有任何问题类名规范、注释齐全、逻辑也完整但贴进项目之后不是找不到依赖就是方法被标记废弃或者干脆编译不过。1.2 这 6 个错误其实可以分成三类按我的理解Philipp Lackner 这 6 条错误不是随手列的背后是一条清晰的因果链类别对应错误核心问题上下文问题错误一、错误二AI 不了解项目基线生成代码与项目脱节环境配置问题错误三、错误四只给了代码没给生命周期处理和资源权限配置工程规范问题错误五、错误六只写正常路径缺少边界处理和验证闭环这个分类很重要。因为修复思路完全不同上下文问题靠“喂信息”配置问题靠“查清单”工程规范问题靠“改习惯”。如果你从头到尾都在纠结“换个大模型能不能解决”方向就错了。1.3 建议先准备的验证环境我复现这些错误时用的是一套很普通的条件一台日常开发电脑、Android Studio、一个 minSdk 21 左右的老项目和一个新建的 Compose 项目。这里不给具体版本号因为不同时期安装的 Android Studio、AGP 和 Gradle 差异很大你直接照抄版本反而容易出错。你只需要确认三件事本机能正常新建并运行一个 Android 项目说明基础环境是好的项目能稳定通过 Gradle Sync说明依赖和网络没有问题有真机或模拟器可以跑运行时错误因为不少问题只在运行那一刻才会暴露。满足这三条下面每个错误都能在一个小时内复现和修复。2. 错误一不给上下文让 AI 在“真空”里写代码2.1 现象看起来合理的代码放进项目全是问题最常见的提问方式是“帮我写一个网络请求工具类”“给我一个 RecyclerView 适配器”“封装一个图片加载工具”。这种问题看起来目标明确但对 AI 来说信息严重不足。我实测过一个例子。让 AI 写一个图片加载工具类它默认返回了基于 Coil 的实现而项目里根本没有引入 Coil也没有任何 Coil 的依赖。更麻烦的是项目现有的图片加载都用 Glide如果直接替换二十多个界面全部要跟着改。这种问题不是 AI“能力不行”而是它根本不知道项目已经做过技术选型。同类现象还有项目统一使用 ViewModel StateFlowAI 却生成了一堆 LiveData项目规范要求 Repository 层做数据缓存AI 却把网络请求直接写进了 Activity项目还在用 XML 布局AI 却给了 Compose 组件。2.2 修复把项目档案喂给 AI再开始提问与其让 AI 猜不如把项目的关键信息打包给它。我一般会维护一个“项目上下文”文本每次提问前直接贴进去。内容包括项目是 Java 还是 Kotlin是 View 体系还是 ComposeminSdk、targetSdk、compileSdk 是多少主要的依赖和版本尤其是网络库、图片库、协程库、DI 框架项目采用的分层结构比如 MVVM 还是 MVI是否使用了统一基类比如 BaseViewModel、BaseFragment代码风格要求比如禁止在 ViewModel 里持有 Activity 引用、统一用 StateFlow 等。这段信息不需要非常严格把最影响代码形态的几项写清楚就够了。AI 拿到这些之后生成的代码会明显更贴项目风格。2.3 一个可以直接改用的最小 Prompt 示例下面这个模板是我常用的你可以根据自己的项目调整项目环境KotlinView 体系minSdk 21targetSdk 34。 架构MVVMViewModel 中使用 StateFlowRepository 负责数据请求。 依赖Retrofit OkHttpCoil 加载图片Hilt 做依赖注入。 统一基类BaseViewModel、BaseFragment禁止在 ViewModel 中持有 Activity 引用。 任务帮我封装一个网络请求工具类要求支持超时设置、失败重试、统一错误提示。 输出要求只输出核心代码和需要新增的依赖不要解释 API 原理。你会发现加了上下文之后AI 不再自由发挥技术选型而是先判断项目里有没有可以复用的基础能力。比如它可能直接说“建议用 Hilt 注入一个 OkHttpClient而不是再写一个静态工具类”这个结论就已经接近项目里一个初级开发者会给出的方案了。2.4 怎么判断这次生成“能用”而不是“能看”代码贴进项目之前先过三个快速判断是否引入了项目里没有的新库如果是这个库有没有必要没有必要就让 AI 改用现有依赖。是否用到了项目里不存在的基类、工具类或命名规范说明它还在凭“通用经验”写。生成代码的调用方式是否和项目现有代码一致比如项目里所有接口都走 Repository它却让你在 Fragment 里直接调网络这就是上下文缺失的信号。这三条只要有一条不满足就别急着往项目里贴先把上下文补全再生成一次。3. 错误二把最新 API 直接套到现有项目上3.1 现象依赖版本、SDK 版本、构建配置三处同时报错这是比缺上下文更隐蔽的问题。AI 的训练数据里最新框架的占比很高所以它倾向于生成基于最新稳定版的代码。麻烦的是你的项目不一定停在最新版。我复现时遇到过一个典型场景项目还在用老版本的 Kotlin 协程AI 生成的代码里使用了repeatOnLifecycle这个 API 在低版本 lifecycle-runtime-ktx 里根本没有。编译时直接报 Unresolved reference。你以为加一行依赖就解决结果一查项目的 lifecycle 版本不够连带要升 Kotlin 插件版本升完 Kotlin 插件又发现 AGP 版本不匹配最后演变成本次提交里混进了一大堆跟需求无关的升级。3.2 原因不是“AI 菜”而是基线差异AI 的默认基线是“最新稳定生态”你的项目基线是“当前能编译通过的依赖组合”。这两条线的差距越大爆出来的编译错误就越多。这种问题最容易误导人的地方在于报错信息不在同一个文件里。可能第一个报错在 build.gradle第二个在某个类文件第三个在资源文件。新手容易把它理解成“三个独立问题”逐个搜索去改最后越改越乱。实际上它们往往是同一个根因基线不匹配。3.3 修复锁版本、锁目标、锁构建开关三件事可以做。第一把版本信息写进每次提问的上下文里。尤其是 Kotlin 版本、AGP 版本、compileSdk、minSdk、协程版本、lifecycle 版本。你不用全部写写几个关键的就够了。因为 AI 生成大多数代码只会碰到这几类 API。第二要求 AI 在生成代码之前先判断 API 兼容性。直接加一句“如果某个 API 的引入要求提升依赖版本请先说明不要直接生成编译不过的代码。”这句话非常有效它会把潜在的依赖升级成本提前暴露出来。第三如果项目确实很老建议把隔离做到模块里。老项目维护过程中可以新建一个使用较新 Kotlin 版本的模块新代码放在新模块里通过接口和老模块对接。这样 AI 生成的代码可以面向新基线不必为了兼容老模块而处处让步。3.4 Gradle Sync 之前先过一遍这张表每次 AI 生成代码后我都会按顺序检查几个位置检查项具体内容出问题时的表现compileSdk / targetSdk是否符合项目当前值资源访问或 API 调用受限buildFeaturesViewBinding、Compose 是否开启找不到绑定类、找不到 Compose 编译器namespace项目包名是否正确资源 ID 引用异常Kotlin 插件版本是否支持生成的语法Unresolved reference依赖版本是否存在、是否冲突依赖解析失败这个检查不复杂但每次贴代码之前做一次能省掉一大半 Gradle 同步时间。别等到 Sync 报错了才开始看配置那时候报错信息可能同时来自十几个文件。4. 错误三生命周期、协程作用域和主线程被当成“可选项”4.1 现象闪退、泄漏、后台任务失控AI 生成的代码里生命周期相关的问题最隐蔽因为它在编译期完全看不出来一旦运行就会以各种奇怪方式爆发。我复现过最典型的是这段逻辑AI 在 Activity 里直接用了GlobalScope.launch发起网络请求成功之后跳转页面。单独跑一次没有任何问题但快速退出再进入就会看到任务还在执行甚至出现对已销毁界面的更新。另外一类常见问题是网络请求写在了Dispatchers.Main上界面直接卡顿低配机器上几分钟后出现 ANR。还有一类更隐蔽ViewModel 里 leak 了一个 Activity 引用退出后内存一直不释放。4.2 修复强制指定作用域和线程不让 AI 自由发挥在 Prompt 里直接写清楚项目的线程和生命周期规范是最有效的办法。比如协程必须在viewModelScope或lifecycleScope中启动使用repeatOnLifecycle收集 Flow 时必须指定Lifecycle.State.STARTED网络请求放入Dispatchers.IO回到主线程更新 UI 必须在Dispatchers.Main或者直接由collect负责不允许使用GlobalScopeViewModel 中不允许持有 Context、Activity、View 的强引用。这些规则不是 AI 特有的要求而是 Android 项目本来就该有的规范。你只要把它们写进上下文AI 生成代码时通常会遵守因为它本身很清楚这些是 Android 开发的常见最佳实践。4.3 常见的三类生命周期问题与改法第一类是任务不取消。Activity 已经销毁协程还在跑。修法是改用lifecycleScope并且不要在onCreate里用裸协程启动不需要跨页面存活的任务。第二类是 Flow 收集时机不对。直接在onCreate里collect会导致界面不可见时还在接收状态更新。修法是lifecycleScope.launch { repeatOnLifecycle(Lifecycle.State.STARTED) { viewModel.uiState.collect { state - // 更新 UI } } }第三类是线程错位。网络请求、数据库操作、文件读写必须放在 IO 线程。如果发现 AI 生成的代码把耗时操作直接写在方法体里要求它明确使用withContext(Dispatchers.IO)并且解释为什么要这么做。4.4 用报错信息定位生命周期问题运行期出现下面几类报错优先怀疑生命周期Only the original thread that created a view hierarchy can touch its views线程错位UI 更新不在主线程。Cant access the Fragment Manager after being destroyed异步任务返回后访问了已销毁的 Fragment。Leaked XxxActivity内存泄漏多半是协程或回调持有外部引用。看到这些报错不要先改代码业务逻辑先把作用域和线程摆正。绝大多数情况下修完这两样问题自动消失。5. 错误四只给代码不给配置——权限、Manifest、依赖和资源5.1 现象方法找得到运行却各种被拦这类错误最典型的画面是代码顺利编译真机点击功能按钮直接闪退或白屏。打开 logcat 一看是Permission Denial或者Unable to find explicit activity class再或者Network request failed: UnknownServiceException: CLEARTEXT communication not permitted。AI 生成代码时默认认为这些“前置条件”你已经配置好了。它不会主动提醒你这个功能需要INTERNET权限、需要注册 Activity、需要开启 ViewBinding、需要添加某个依赖。因为对 AI 来说这些属于项目配置它看不到自然也不会写。这其实暴露了一个更本质的问题很多人把 AI 当成“写代码的工具”但 Android 开发中代码只是程序面的一部分Manifest、Gradle、资源文件这些配置文件同样重要。5.2 修复让 AI 输出“改动清单”而不是一段代码最有效的做法是改指令。不要只说“实现这个功能”而是说实现一个带摄像头拍照并显示图片的功能。 输出要求 1. 核心代码 2. 需要在 AndroidManifest 中新增的权限和声明 3. 需要在 build.gradle 中新增的依赖 4. 需要新增的资源文件 5. 每一步改动涉及的完整文件路径。这个输出格式会逼着 AI 把隐藏的前置条件列出来。实测下来它至少会补上CAMERA权限、FileProvider 配置以及依赖项。即便有遗漏你也能通过“改动清单”快速检查。5.3 每段生成代码落地前先检查这四个文件我养成了一个习惯AI 生成的代码只要涉及系统能力就先过一遍四件套。AndroidManifest.xml权限、Activity/Service 注册、FileProvider、网络配置有没有遗漏build.gradle新增依赖是否存在版本是否冲突buildFeatures 是否开启res 资源目录布局、字符串、图片、xml 文件是否都存在文件名是否匹配proguard 配置如果 release 包有问题优先检查混淆规则是否覆盖了新引入的类。如果四件套都没问题再考虑业务代码本身。5.4 判断标准不用跑完整流程先做“配置核查”不需要每次都在真机上点完所有按钮。你可以先把 AI 生成的“改动清单”当作验收标准逐条核对项目的实际文件。只要清单里的每一项都真实存在于项目里再去做运行验证。这个步骤虽然不起眼却能把运行期的问题砍掉一大半。这里还值得提醒一句项目里打开 debug 模式跑通不等于 release 模式没问题。涉及混淆、网络协议、系统权限的功能建议至少在 release 模式下做一次冒烟验证。6. 错误五只写快乐路径把边界条件全都交给运气6.1 现象正常数据能用异常数据直接崩AI 训练数据里的示例代码绝大多数都在展示“功能怎么实现”而不是“功能怎么在各种输入下存活”。所以 AI 生成的代码天然倾向快乐路径接口返回正常 JSON 时逻辑没问题接口返回空数组、字段缺失、类型不符时页空白、崩溃、死循环都有可能。我实测过一个列表页。AI 生成的解析代码假设data字段永远存在结果后端异常时返回了{data: null}页面直接 NPE。如果是真实用户遇到就是一个事故工单如果是自己调试可能还会想半天“AI 代码是不是错了”。6.2 修复把边界条件写进 Prompt与其事后给代码补 try-catch不如一开始就在 Prompt 里声明边界条件。我常用的一段话是实现时要求 1. 输入可能为 null、空字符串、空列表 2. 网络请求可能超时、失败、返回异常格式 3. 按钮需要防重复点击 4. 页面处于 loading、error、empty、success 四种状态 5. 切换到后台再回来时状态需要正确恢复。这道约束会让 AI 生成的代码明显多出很多 null 判断、空态页面和错误处理。代码量会变多但这才是能上线的东西。6.3 最容易被忽略的 5 类边界场景整理我实际踩过的坑下面几类最容易漏空数据列表接口返回空数组页面应该展示空态而不是白屏网络异常断网、超时、服务器 5xx用户应该看到重试入口重复触发双击提交按钮不能生成两条一样的订单进程重建应用被系统杀掉再恢复到原页面状态要用SavedStateHandle或ViewModel恢复大文本和超长列表没有分页的接口一次性渲染几千条数据会导致卡顿。每一条都可以在生成代码后主动问 AI“针对以上场景这些代码的隐藏风险在哪里”让它自己帮你审一遍。这个方法经常能发现连阅读代码时都不容易注意的问题。6.4 验证用脏数据、慢网络、重复点击来测判断边界处理是否到位不能只看代码要动手测。我一般会准备一组“脏数据”空字符串、null 值、超长字符串缺字段的 JSON类型不符的字段比如字符串里混入数字超大列表比如一次性返回 5000 条。然后再配合工具模拟慢网络和断网。最后快速双击按钮几十次看会不会产生重复请求。这些测试不复杂却能很快筛出 AI 代码里真正会出事的漏洞。7. 错误六把 AI 生成代码当成“最终结论”不做验证就合并7.1 现象能运行但不代表正确、稳定、适合长期维护这一条不是代码层面的错误而是工作习惯层面的错误。很多人把 AI 生成的代码复制进项目跑一次看没有闪退就提交合并了。但“能运行”和“正确”“稳定”之间差距很大。我见过几种典型情况代码能跑但 lint 有一堆警告代码能跑但和项目架构完全不符后续维护者根本看不懂代码能跑但连一个单元测试都没有后面的修改根本不敢碰。这些问题的共性是AI 只负责生成不负责保证质量验证责任始终在开发者身上。7.2 修复最小验证三步走我建议把验证拆成三步每步都有明确出口条件。第一步编译验证。代码贴入项目后先做 Gradle Sync再执行一次编译。这个环节解决的是基线问题、依赖问题、语法问题。第二步运行验证。在真机或模拟器上把功能完整走一遍包含正常流程和异常流程。这个环节解决的是生命周期、线程、权限、边界问题。第三步测试验证。至少给关键逻辑补充单元测试或 instrumented test。如果项目里还没有测试基础设施先把一个核心业务类的测试搭起来这个过程本身就在检验代码的可测试性。7.3 合并前要回答的三个问题提交代码之前我会拿这三个问题过一遍这段代码是否和团队现有架构一致而不是“能在项目里跑通但风格完全不同”是否理解每一行的作用能否在 Review 时解释清楚如果这个功能出问题日志是否足够定位测试是否覆盖了最核心的路径。三个问题里只要有一个回答不上来就不要提交。让 AI 继续解释、补测试、调整结构直到这三个问题都能正面回答。7.4 什么时候可以信任 AI 生成代码不是所有代码都要用同一套严格标准。一个基本原则是越独立、越封闭、越没有外部依赖的代码越可以信任越依赖项目上下文、系统生命周期、团队规范的代码越要人工复核。适合交给 AI工具函数、数据解析、状态机逻辑、单元测试、样板代码需要重点复核涉及 Activity/Fragment 生命周期、权限、跨模块调用、进程恢复、自定义 View 渲染的代码尽量不要完全交给 AI核心架构设计、模块拆解、数据库迁移、加密和计费逻辑。这个边界不是固定的但方向很明确AI 处理“局部复杂度”更靠谱处理“全局约束”时必须有一个人来兜底。8. 六个错误的汇总表与通用排查顺序8.1 错误、现象、修复、验证一条线过一遍错误典型现象修复思路验证方式错误一缺上下文引入新库、风格不一致、调用方式与项目相悖把项目环境、架构、依赖写进 Prompt代码风格与现有模块对照错误二新 API 套旧项目编译报 Unresolved reference、依赖链升级锁版本、锁基线、新增模块隔离Gradle Sync 一次通过错误三生命周期与线程错位闪退、ANR、内存泄漏指定作用域和线程禁止 GlobalScope反复进出页面观察内存错误四只给代码不给配置权限拒绝、类找不到、网络明文限制要求输出 Manifest/依赖/资源改动清单四件套配置逐项核查错误五只写快乐路径空数据、异常数据崩溃、重复提交把边界条件写进 Prompt 约束用脏数据和慢网络实测错误六不验证就合并能跑但没测试、无法维护编译、运行、测试三步走合并前回答三问Code Review 单测通过这张表不需要背下来排查时能对标到具体错误就够了。8.2 通用排查顺序从编译环境到运行时数据代码出问题时我一般按下面的顺序排查而不是看到报错就改代码先看现象是编译失败、运行闪退、卡顿还是结果错误再跑一次 Gradle Sync排除依赖和构建配置问题看 logcat 的关键异常栈把第一行异常类名读清楚不要被十几行堆栈带偏回到输入数据确认接口返回的字段、格式和长度是否符合代码的假设检查权限和 Manifest排除“代码是对的但配置拦截了”的情况最后才调整代码逻辑。这个顺序的套路是先环境、再输入、后逻辑。大部分 AI 代码运行失败都失败在前面两步而不是最后的业务逻辑。8.3 实测时最容易被坑的三个点第一路径和文件名。AI 生成资源文件时命名和你项目现有规则不一致导致运行时找不到资源。报错不是编译错误而是落地的资源引用错误。第二中文字符串硬编码。AI 经常把提示文字直接写进代码而不是放在 strings.xml。代码能跑但不符合 Android 项目的国际化规范后续做多语言时非常痛苦。第三混淆规则。AI 不会主动考虑 release 包的问题。如果你只在 debug 下验证永远发现不了 release 包里的反射类被混淆的问题。所以涉及反射、序列化、接口动态代理的功能release 包一定要单独跑一次。9. 让 AI 真正变成 Android 开发的“第二双手”而不是“事故源头”9.1 把团队上下文沉淀成可复用的文件与其每次提问前临时写一段很长的项目背景不如把上下文沉淀成一个文件比如AGENTS.md或项目说明文档。里面记录项目架构、依赖、代码风格、禁止事项和常用模块入口。AI 工具支持读取项目文件时它会自动读到这些内容不支持时你也可以把这段内容复制粘贴进对话。这个文件的好处是一次整理多次使用。团队新成员也能通过它快速了解项目约定算是一份低成本的开发文档。9.2 用 AI 生成测试代码反过来约束实现代码一个反直觉但非常好用的技巧是先让 AI 写测试再让它写实现。测试代码会把输入输出边界定死AI 接着写实现时为了通过测试就不得不处理那些容易被忽略的边界条件。这种方式还有个额外收益你手上会留下一套回归测试。以后 AI 再改这段代码只要跑一遍测试就知道有没有改坏东西。这比反复“让 AI 自查”靠谱得多。9.3 我目前的个人工作流最后分享一下我现在是怎么用 AI 写 Android 代码的不复杂但能少踩很多坑第一步小步提问。一次只让 AI 做一个模块或一个方法不让它一口气生成十几个文件第二步固定上下文。先贴项目背景再提需求最后提输出要求第三步代码进项目前后各做一次检查。进之前看配置四件套进之后跑编译和运行验证第四步能用测试验证的一定补测试。测试既是为了这次代码也是为了下次修改第五步保持审阅心态。AI 生成的代码只是初稿它更像一个经验丰富但容易忽略项目实际语境的初级协作开发者而不是最终的权威结论。我踩过几次坑之后最大的感受是AI 写 Android 代码这件事真正决定成败的不是生成那一瞬间而是生成之前你的上下文够不够完整、生成之后你的验证到不到位。这两头补齐了AI 就能实实在在帮你省时间两头缺一个它就会帮你制造一个又一个凌晨修 bug 的夜晚。
返回列表