今天在整理 Hermes Studio App 的每日开发进度时,我把整体完成度标记为 90%。这个数字看起来非常接近终点,但在移动端项目里,90% 往往是最容易产生误判的阶段:功能列表上的需求都开发完了,界面也能正常跑通,可真要发布到应用市场,或者交给一批真实用户去使用,总能在边界场景、弱网环境、老旧设备、权限异常里发现一堆待补的问题。
这篇文章不打算重复“今天改了什么 Bug”式的流水账,而是借 Hermes Studio App 这个演示项目,把移动应用进入 90% 完成度后最应该做的一轮系统性收尾拆开讲清楚。无论你手里的是一个原生 Android 项目、Flutter 项目,还是基于 uni-app 的跨端应用,最后这 10% 的工程方法基本都是通用的:需求冻结、边界补全、性能检查、崩溃治理、自动化回归、打包上架准备。如果你的项目正处于功能开发完、但还不敢点发布的阶段,可以直接拿这份清单对照自查。
1. 90% 完成度阶段意味着什么
1.1 Hermes Studio App 当前所处的阶段
从功能进度来看,Hermes Studio App 的核心模块已经全部跑通。基础信息流、用户登录、关键业务操作、消息推送这些主链路都已经联调完成。界面上的主要页面也完成了设计稿还原,视觉走查问题不多。按照团队日常的进度统计口径,这些工作折算成 90% 并不夸张。
但这里要分清楚两个概念:“开发完成”和“交付完成”。开发完成表示代码层面的功能已经实现,而交付完成要求应用在真实设备、真实网络、真实用户操作习惯下仍然保持稳定、流畅、不崩溃。90% 这个阶段最尴尬的点是,新功能已经不适合再继续大规模堆叠,但距离可发布又还差着一轮严格的验证收尾。
我在项目里通常会做一件很具体的事:把“剩余 10%”转译成一张带检查项的任务列表。比如核心链路回归、Android 各版本兼容、弱网表现、低端机内存占用、崩溃日志体系是否完善、上架材料是否齐全。只有把这些不是“功能”但决定体验的事项全部纳入进度统计,90% 才不会被误读成“快完了”。
1.2 最后 10% 真正要解决的问题
最后这 10% 的工作,分布在几个容易踩坑的方向上。
第一是需求边界逐渐模糊。开发阶段大家盯着主流程写代码,很少会花时间处理登录态过期、网络超时、重复提交、空数据展示这些反常识的场景。这些问题在演示环境里几乎不出现,但在真实用户手里很容易触发。
第二是设备与系统版本碎片化。同一个页面在开发测试机上显示正常,换到一台屏幕比例特殊的机器上就可能出现布局溢出;Android 11 和 Android 14 对权限、后台限制的策略完全不同,代码里如果存在基于旧版本假设的逻辑,很容易出现“开发机正常、用户设备崩溃”的情况。
第三是性能和稳定性问题开始集中暴露。功能代码越堆越多,冷启动时间可能变长,内存占用可能缓慢上涨,部分低端机型在图片列表页面滑动时可能出现明显掉帧。这些问题不一定导致功能不可用,但会直接影响用户评价和留存。
第四是发布前的工程准备。签名文件、版本号、渠道包、隐私政策、合规授权说明、内部灰度机制,每一样都需要留出时间配置和验证。把这些问题放到功能全部完成后再处理,其实已经偏晚,但 90% 阶段仍然来得及系统排期。
2. 需求冻结:把“完成”定义清楚
2.1 明确版本优先级
进入 90% 阶段后,第一件事不是继续做新需求,而是冻结当前迭代的需求范围。如果你不冻结,产品同学看到功能能跑了,很容易继续提“这里再加一个按钮、那里再加一个筛选条件”。单个需求改动量不大,但累积起来会不断打断回归验证的节奏,也让测试范围无限扩大。
我建议在需求冻结时引入一个简单的优先级模型。P0 表示阻塞发布的问题,例如登录失败、核心交易流程错误、数据丢失、严重闪退,这类问题必须在当前版本解决。P1 表示影响体验但不阻塞发布的问题,例如部分文案错误、次要页面样式偏差、低概率偶现的卡顿,这类问题可以排到当前版本,也可以降级到下个版本。P2 表示优化类需求,例如增加转场动画、重新设计空状态插画、提升某页面加载速度,这类需求在当前版本原则上不做。
这个优先级划分要形成书面记录,不能只停留在口头约定。项目进入 90% 阶段后,研发、产品、测试对“什么问题是当前版本必修”如果理解不一致,后续很容易出现互相拉扯。把优先级写进需求池或者项目管理工具里,每次新反馈进来先判级,再决定是否插入当前迭代。
2.2 功能完成清单应该包含哪些内容
一个功能从“代码写完”到“真正完成”,至少需要满足以下几个条件:
- 主流程按产品文档运行通过,并且通过测试用例覆盖。
- 分支流程和异常场景有明确处理逻辑,例如弱网、断网、服务端返回异常、用户重复点击。
- 关键操作有日志输出,出现问题时可以通过日志定位。
- 页面状态完整,包括加载态、空态、错误态,不能只有成功态。
- 涉及用户数据和敏感操作的地方,有权限校验和边界保护。
如果团队里对“完成”的定义不一致,我建议先花半小时整理一份开发完成定义清单。比如“登录功能完成”不能只表示能拿到 token 并跳转到首页,还要包括登录接口超时提示、验证码错误提示、用户主动取消登录、Token 失效后的自动跳转逻辑。把这些内容写清楚,90% 这个数字才真正具备参考价值。
3. 工程配置与依赖治理:防止“开发能跑、换机必炸”
3.1 项目目录结构梳理
功能开发阶段,为了快速上线,项目目录里通常会产生不少临时文件、调试代码、未使用的资源。到了 90% 阶段,这些问题如果不清理,轻则增加包体积,重则导致构建不稳定。
以常见的原生 Android 工程为例,一个相对清晰的结构大概是这样的:
HermesStudioApp/ ├── app/ │ ├── src/ │ │ ├── main/ │ │ │ ├── java/com/hermes/studio/ │ │ │ │ ├── base/ │ │ │ │ ├── data/ │ │ │ │ ├── model/ │ │ │ │ ├── ui/ │ │ │ │ └── utils/ │ │ │ ├── res/ │ │ │ └── AndroidManifest.xml │ │ ├── debug/ │ │ └── release/ │ ├── build.gradle │ └── proguard-rules.pro ├── config/ ├── docs/ ├── gradle/ └── build.gradle如果你使用的是 Flutter、React Native 或者 uni-app,目录结构会有差异,但思路是相通的:业务代码、配置文件、文档、构建脚本要分层存放。收尾阶段查看工程时,如果一个目录里混着测试页面、旧原型、临时备份代码,会严重影响排查效率。
我自己的习惯是,在 90% 阶段抽出一个时间块,专门做代码清理。删除没有被引用的工具类,把 TODO 注释重新过一遍,把调试阶段的测试入口用开关控制起来。清理过程中需要注意,删除代码前先确认没有其他模块引用,最好借助 IDE 的 Find Usage 功能或者项目的静态检查工具,避免误删。
3.2 多环境配置隔离
90% 阶段经常会出现一个非常典型的错误:开发环境配置被不小心打进了发布包。症状表现是,测试人员拿着 release 包,却发现应用请求的是测试服务器地址,数据状态和正式环境完全不一致。
出现这种问题的根源,是环境配置没有和构建流程解耦。比较好的做法是把不同环境的配置独立存放,在构建时通过参数选择。例如在工程中维护一份环境配置文件:
# 文件路径:config/env.properties app.env=prod api.base.url=https://api.hermes-studio.example.com api.timeout=15000 log.level=warn示例中的example.com只是占位域名,真实项目中要替换成你们自己的服务端地址。多环境配置的落地方式有很多种,原生 Android 可以通过 BuildConfig 字段注入,Flutter 可以使用--dart-define,uni-app 则可以在打包时按环境变量区分。关键原则只有一条:环境地址、密钥、渠道标识这类内容不应该手写在业务代码里,也不应该直接硬编码在全局常量文件中。
如果项目当前的配置管理比较混乱,建议在收尾阶段统一做一次重构。把开发、测试、预发布、正式环境的配置全部抽出来,检查 git 仓库里是否误提交了包含密码或密钥的敏感文件,确认没问题后再进行后续打包。
3.3 依赖版本锁定与三方库治理
项目进入后期之后,三方依赖的治理要格外谨慎。开发阶段大家喜欢引入各种库来提升效率,但每个库都意味着额外的包体积和潜在的兼容性问题。到了 90% 阶段,第一原则是“不随意升级大版本,不随意引入新库”。
如果你的项目使用 Gradle 管理依赖,可以通过版本目录或统一变量来锁定版本。这种做法能让所有模块引用同一份依赖版本,避免出现“A 模块用了 OkHttp 4.x,B 模块还在用 3.x”这类问题。若项目已经出现依赖冲突,构建时通常会产生提示,不要通过盲目排除依赖来修复,而应该先确认冲突的具体原因,再选择保留哪个版本。
这个阶段还建议做一次无用依赖清理。找一下是否有仅在调试期使用、正式包根本不需要的库,或者已经被业务代码弃用但没有移除的模块。清理依赖后,最好在干净环境下重新执行一次完整构建,确认没有因为删除依赖而暴露编译问题。
4. 界面交互与边界场景补全
4.1 空状态、弱网与失败重试
90% 阶段最容易在 UI 层面暴露的问题,是页面只有“数据正常返回”这一种形态。真实用户在网络信号差、账号首次登录、列表无数据、接口报错时会看到什么,很多项目其实没有认真设计。
举一个很常见的例子。列表页从服务端拉取数据失败后,页面上如果只显示一片空白,用户并不知道是网络问题、服务端问题,还是自己的操作问题。更好的处理方式是在页面中放置一个明确的错误状态图标和文案,并给出“重新加载”按钮。用户点击重试后,按钮进入 loading 状态,请求成功则刷新列表,请求再次失败则保留错误提示。
弱网场景则要关注超时时间设置。有些应用默认的超时时间很长,用户在网络不佳时会误以为应用卡死;超时时间设置过短,又会在网络抖动时频繁失败。从实践看,常规接口的连接超时可以设置在 10 秒左右,读取超时可以控制在 15 秒左右,文件上传类接口需要单独增加超时时间。具体数值需要结合你们服务端的响应速度来调整。
4.2 权限申请与系统限制
权限是移动应用后期最容易出问题的部分。很多开发者在功能调试时直接授予了所有权限,所以并没有体验到用户拒绝权限后应用的表现。
拿 Android 平台来说,动态权限策略在不同版本上存在差异,Android 6.0 及以上需要运行时申请危险权限,Android 11 对存储权限做了进一步限制,Android 13 甚至把通知也变成了运行时权限。如果应用没有做权限适配,很可能出现在新系统上无法弹出权限框、功能入口点击无反应、甚至直接崩溃的问题。
90% 阶段应该把权限申请流程完整走查一遍。不要默认用户一定会点击“允许”,要预设用户拒绝权限、勾选“不再询问”、中途取消授权等场景。对于不授权就无法使用的核心功能,要给出明确的引导文案并跳转到系统设置页;对于非核心功能,则应该允许用户跳过并降级体验。
4.3 表单与操作确认逻辑
如果 Hermes Studio App 的业务中包含表单填写或状态修改类操作,那么收尾阶段要特别注意重复提交和二次确认的问题。
很多用户在网络卡顿时会习惯性连续点击提交按钮。如果前端不加以拦截,同一个请求可能被发送多次,导致服务端重复下单、重复留言、重复修改数据。处理方案通常是在按钮点击后立即进入 loading 状态并禁用按钮,等请求返回后恢复。对于用户主动填写的重要表单数据,建议在离开页面时增加未保存提醒,避免用户误触返回键导致内容丢失。
涉及删除、覆盖、支付、权限变更风险较高的操作,必须增加二次确认弹窗。不要觉得弹窗影响体验,真实用户体验中最怕的是“不小心点了一下,数据就没了”。
5. 性能优化与稳定性排查
5.1 冷启动与首屏渲染
当功能代码全部开发完成后,性能问题会逐渐浮现。Hermes Studio App 在开发中期曾经出现过一个现象:应用点击图标后接近两秒才看到首页内容。后来排查发现,是启动阶段同时完成了大量初始化任务,导致主线程被占用。
优化思路比较通用,就是把启动任务按优先级拆开。必须在首页绘制前完成的任务,例如读取本地登录态、初始化崩溃监控 SDK,保留在启动流程中;可以延迟执行的任务,例如预加载某个二级页面的数据、初始化非核心统计 SDK,则放到首屏渲染完成后再异步执行。
启动流程调优没有统一的代码模板,因为它和业务结构关系太大。一个比较通用的检查方法是,在启动阶段为每个初始化任务打印耗时日志,再通过日志分析哪几个任务占用了最多时间。不要凭感觉猜测,先量化,再优化。
5.2 网络层的超时与重试策略
网络层是移动应用稳定性的重灾区。以 OkHttp 为例,如果项目使用 Java 或 Kotlin 开发,一个基本的超时配置思路如下:
val client = OkHttpClient.Builder() .connectTimeout(10, TimeUnit.SECONDS) .readTimeout(15, TimeUnit.SECONDS) .writeTimeout(15, TimeUnit.SECONDS) .retryOnConnectionFailure(true) .build()这里需要提醒一下,以上代码是常见的配置示例,具体还要根据项目实际情况调整超时时间。代码片段中涉及 OkHttp 的依赖,需要根据项目当前使用的版本保持一致,不建议在收尾阶段随意升级大版本。
网络请求应该区分“可重试”和“不可重试”两种场景。查询类接口在超时后重试通常没有问题,但提交订单、修改数据、删除文件这类写操作需要谨慎重试。如果服务端没有做幂等处理,客户端盲目重试可能导致数据重复创建。对于写操作的超时,更合理的做法是提示用户“请求已发送,请稍后查看结果”,而不是自动重发请求。
5.3 内存与卡顿检查
低端机型上的性能问题通常在开发阶段很难暴露,因为开发测试机一般配置不差。收尾阶段需要专门做一轮内存和卡顿排查。
Android 项目可以使用 Android Studio 自带的 Profiler 工具观察应用内存变化。一个简单的方法是,在列表页反复上下滑动,进入详情页再返回,重复多次后观察内存是否持续上涨且无法回落。如果内存曲线呈现明显的阶梯式上升,大概率存在内存泄漏,比如 Activity 被静态变量持有、监听器没有反注册、Handler 未移除回调等。
如果项目在低端设备上滑动卡顿,优先检查是否存在主线程中执行网络请求、图片加载没有做缩略图处理、列表 item 布局层级过深等问题。优化时一次只改一个点,改完在灰度设备上复测,避免多个改动叠加后无法定位收益来源。
6. 异常崩溃治理与日志监控
6.1 崩溃优先级评估
进入 90% 阶段后,我会开始每天看崩溃列表。每次发布前,团队需要明确一个原则:必现崩溃必须修复,高频崩溃必须修复,偶现但影响严重的问题要给出临时规避方案并排期修复。
崩溃处理最忌讳的是“看到崩溃就改,改完不知道有没有用”。正确的做法是先复现。如果某个崩溃无法稳定复现,优先检查崩溃日志中的堆栈信息和发生场景,确认是否与特定系统版本、特定机型、特定页面有关。提交代码前,至少要能解释清楚崩溃的触发链路,修复后也需要在相同的场景下反复验证。
对于无法立刻解决的偶现崩溃,可以在异常捕获层面做一层兜底,确保崩溃影响范围被限制在单个模块。例如某个数据解析异常只影响详情页展示时,可以通过捕获异常展示统一错误页,防止整个应用闪退。不过这种兜底只能作为临时手段,不能替代真正的修复。
6.2 日志分级的落地
日志是排查线上问题的唯一线索,但日志打得太随意,同样会带来问题。90% 阶段建议重新梳理一遍日志规范。
| 日志级别 | 使用场景 | 示例 |
|---|---|---|
| Debug | 本地开发调试,发布包中关闭 | 页面参数打印、临时调试信息 |
| Info | 关键业务流程节点 | 用户登录成功、接口请求完成 |
| Warn | 有潜在风险但不影响主流程 | 数据格式不一致、配置缺失、重试触发 |
| Error | 异常和错误信息 | 接口返回错误、崩溃前关键现场 |
这里要特别提醒敏感信息保护的问题。收尾阶段一定要检查现有的日志代码,看是否存在打印 Token、手机号、身份证号、密码等敏感信息的操作。日志应该服务于问题定位,而不是把用户隐私暴露在日志系统中。生产环境建议使用独立的日志开关和脱敏组件,如果项目还没有这类能力,至少要在代码审查阶段拦截明显的敏感日志打印。
6.3 灰度环境下的发布观察
完成了代码层面的崩溃治理后,最后一公里是发布策略。不要等到全量发布后才去看崩溃数据,这样风险太大。更稳妥的做法是先找少量内部用户或核心用户进行灰度发布,观察一段时间,确认崩溃率、关键页面成功率无明显异常后,再逐步放量。
灰度期间需要关注的指标包括:崩溃率是否高于基线、启动失败率、首页加载成功率、主要业务接口错误率,以及用户反馈中是否出现开发阶段未预料到的问题。一旦发现问题,可以快速通过配置开关关闭对应功能,或者回滚到上一个稳定版本。把发布当成一个可观测、可控制的流程,而不是一个“点按钮就完事”的操作,是 90% 阶段走向稳定发布的关键。
7. 自动化测试与上线前回归
7.1 核心链路回归清单
手工回归在项目较大时容易遗漏,所以需要先列出一份核心链路清单。下面是一张可以套用的基础模板,Hermes Studio App 的测试同学可以直接按模块扩展:
| 模块 | 测试项 | 操作路径 | 预期结果 |
|---|---|---|---|
| 登录 | 正常登录 | 输入账号密码点击登录 | 进入首页,展示用户信息 |
| 登录 | 密码错误 | 输入错误密码登录 | 提示错误;允许重新输入 |
| 登录 | 登录态过期 | 模拟 Token 失效后进入需登录页面 | 跳转登录页并提示重新登录 |
| 首页 | 首次加载 | 冷启动进入首页 | 显示加载状态后展示内容 |
| 首页 | 网络断开 | 开启飞行模式后下拉刷新 | 展示网络错误提示与重试按钮 |
| 消息 | 列表为空 | 新用户进入消息页 | 展示空状态引导,不出现白屏 |
这份清单不需要覆盖所有页面,优先保障主流程。在 90% 阶段,任何一次代码提交都可能引入回归问题,因此回归清单要根据最近改动范围动态调整。提交涉及登录模块时,至少要把登录、注册、退出登录相关的用例跑一遍。
7.2 UI 自动化冒烟示例
如果项目资源允许,建议在核心链路上搭建 UI 自动化冒烟用例。Appium 是目前比较常用的开源移动端自动化测试框架,支持 Android 和 iOS。下面是一个使用 Python 客户端的极简连接示例,思路是先建立会话,再执行页面操作:
from appium import webdriver desired_caps = { "platformName": "Android", "deviceName": "emulator-5554", "appPackage": "com.hermes.studio", "appActivity": ".MainActivity", "noReset": True, } driver = webdriver.Remote("http://localhost:4723/wd/hub", desired_caps) # 示例:等待首页元素出现 # driver.find_element(By.ID, "com.hermes.studio:id/main_container") driver.quit()这段代码只是演示框架的基本结构,不同版本的 Appium 对部分 capability 的写法有差异,如果你的本地服务是 Appium 2.x,部分配置项需要使用appium:前缀。重要的是先跑通一个冒烟用例,让每日构建后的自动验证成为可能,而不是一次性追求覆盖全部页面。
自动化测试的价值在于防止基础功能在频繁迭代中悄悄坏掉。团队可以每天定时跑一遍冒烟用例,如果发现登录流程或首页加载失败,就能第一时间收到通知,避免把明显有问题的包交到测试人员手里。
7.3 兼容性测试关注点
移动应用的兼容性很难通过一两台测试机完全覆盖。如果 Hermes Studio App 面向的是大众用户,至少需要覆盖不同屏幕尺寸、不同 Android 版本、不同厂商系统的场景。
没有大量真机资源时,可以先通过云测试平台选择主流机型做一轮核心用例覆盖。重点关注几个方向:Android 8 以下的老设备系统资源是否足够;Android 12 以上的新设备是否存在隐私弹窗、后台限制、通知权限等问题;华为、小米、OPPO、vivo 等厂商系统对后台进程的限制是否影响了推送和定位功能。
兼容性测试发现的问题,建议按照“系统版本 + 机型 + 操作步骤 + 预期结果 + 实际结果”的格式记录,方便开发同学快速定位。这类问题往往不体现在代码逻辑错误上,而是体现在系统和硬件差异上,因此详细的现场信息比什么都重要。
8. 打包、签名与内部分发准备
8.1 签名、版本号与构建产物
正式发布前,签名配置和版本号管理是必须确认的事项。Android 发布包需要使用正式签名文件进行签名,签名文件要妥善保管,不要把密码提交到代码仓库,也不要随意更换签名证书。如果之前使用的是调试签名,那么安装过旧版本的用户在覆盖安装时会因为签名不一致而失败,只能卸载重装。
版本号方面,通常需要维护两个概念:versionCode是给系统识别用的整数,每次发布都要递增;versionName是展示给用户看的版本名称,例如1.0.0、1.1.0。在收尾阶段,建议把版本号管理纳入构建流程,避免发布前手动修改遗漏。
原生 Android 工程中,可以使用以下命令生成 release 包:
./gradlew clean assembleRelease如果是 Flutter 工程,则对应:
flutter build apk --release构建命令会根据技术栈不同有差异。关键是确认每次发布的包都是从这个稳定构建流程中产出的,不要出现“开发本地打了一个包先发出去,但代码没有合入主干”的情况。
8.2 隐私政策与权限合规检查
应用上架前,隐私政策和权限合规是无法回避的环节。很多开发团队到这一步才发现,用了某个统计 SDK 后,需要向用户披露的数据采集项远超预期;或者申请了大量用不上的权限,导致应用商店审核不通过。
收尾阶段应该以应用实际功能为基础,梳理并校准权限申请范围。删除与业务无关的权限,例如一个纯工具类应用如果没有地址相关功能,就不应该申请定位权限。对需要获取用户信息的第三方 SDK,确认其隐私说明和使用目的已经包含在应用隐私政策中。
隐私合规检查最好让产品、研发、测试共同参与,由产品确认文案,研发确认 SDK 实际采集的数据字段,测试在隐私模式和普通模式下各走一遍关键流程,确保用户没有同意隐私政策前,应用不会启动数据上报。
8.3 内部发布与观察方式
上架正式市场之前,通常会先进行一轮内部发布。内部发布可以使用应用市场提供的内部测试通道,也可以借助常见的应用分发工具上传安装包,让参与测试的同事直接通过链接安装。
内部发布阶段要明确告知测试人员:这次的安装包是哪个版本号、代码分支是什么、重点验证哪几个功能、已知问题有哪些。如果只丢一个二维码,没有任何说明,测试效果会大打折扣。每次准备新安装包时,最好顺手整理一份简短的发布说明,哪怕只有几行字,也能让验收过程高效很多。
内部验证通过后,再根据团队发布策略决定是先走少量用户灰度,还是直接全量发布。从工程角度来说,把内部验证、灰度观察、全量发布拆成三个独立步骤,每一步都设定明确的通过标准,出现线上问题后的处理成本会低很多。
9. 常见问题与排查思路
9.1 常见问题速查表
90% 阶段常见的问题通常呈现出固定模式,下面整理成速查表,方便收藏和复用:
| 问题现象 | 常见原因 | 解决思路 |
|---|---|---|
| 开发环境正常,测试包请求了错误的服务器 | 环境配置没有按构建类型区分 | 清理硬编码 BaseUrl,按 BuildConfig 或环境变量隔离 |
| 用户反馈收不到消息推送 | 厂商后台限制或用户关闭通知权限 | 检查各厂商推送通道,完善权限引导 |
| 安装新版提示“应用未安装” | 签名不一致或版本号更低 | 确认正式签名配置,检查 versionCode 是否递增 |
| 页面数据刷新后出现重复内容 | 分页请求没有做去重或页码重置 | 检查 onRefresh 和 onLoadMore 的并发处理 |
| 低端机滑动明显卡顿 | 主线程执行耗时任务或图片过大 | 使用 Profiler 定位主线程耗时,开启缩略图加载 |
| 偶现崩溃找不到原因 | 缺少崩溃现场信息或发生在线程池 | 补充结构化日志,增加用户操作路径记录 |
| Debug 包正常,Release 包逻辑异常 | 代码混淆导致反射或序列化失败 | 调整混淆规则,保留必要的类和方法 |
| 用户点击提交按钮后重复下单 | 按钮没有防重复处理 | 提交后立即禁用按钮并进入 loading 状态 |
9.2 一条完整的排查思路示例
这里以“Android Release 包启动闪退”为例,说明排查路径。这类问题在 90% 阶段很典型,因为它只在正式包出现,调试包无法复现。
首先不要直接翻代码,先拿到 Release 包的崩溃堆栈。如果项目已经接入崩溃监控 SDK,直接去平台上找对应版本号、对应设备的崩溃记录。如果还没有接入,可以先用命令行安装并抓取日志:
adb install app-release.apk adb logcat -c # 启动应用 adb shell monkey -p com.hermes.studio -c android.intent.category.LAUNCHER 1 adb logcat -d > crash.log拿到完整日志后,重点搜索FATAL EXCEPTION、AndroidRuntime、Process: com.hermes.studio这些关键字段。Release 包闪退比较常见的原因是混淆导致的类或方法找不到,例如 Gson 反序列化实体类没有添加 keep 规则,或者通过反射调用的 SDK 方法被混淆器移除了。对照堆栈里的类名,去 proguard-rules.pro 中补充对应的保留规则,再重新打一个 Release 包验证。
如果堆栈信息不明显,可以在 Release 构建中临时关闭混淆,对比是否还会崩溃。通过这种方式可以快速区分是混淆问题还是业务逻辑问题。定位到问题后,不要把“关闭混淆”作为解决方案,毕竟关闭混淆会显著增加包被逆向的风险,正确做法是针对具体问题补充精细的混淆保留规则。
10. 最佳实践与项目收尾建议
10.1 版本收尾期最值得养成的习惯
到了开发进度 90% 的节点,我觉得有几条工程习惯特别重要。
第一条是“小步提交,频繁集成”。不要等所有功能都改完了再一次性提交代码,而是每修完一个明确的问题就提交一次,并写清楚提交说明。这样做的好处是,回归中发现新问题时,可以快速定位是哪一次提交引入的变更。
第二条是“所有变更都从分支合入”。收尾阶段直接在主分支上改 bug 风险很高,尤其是当多个开发人员同时修改时,很容易把未验证的代码混入发布分支。保持一个长期稳定的主干和一个独立的 release 分支,发布包只从 release 分支构建。
第三条是“建立发布前检查清单”。每次准备打包前,逐项确认版本号、签名、环境配置、依赖锁定状态、崩溃监控 SDK 是否初始化、隐私政策链接是否可访问。这些项目比较琐碎,单纯靠人脑记忆很容易遗漏,清单可以成为团队的工具资产。
第四条是“保持文档同步”。项目进入 90% 阶段后,代码中的模块结构、接口调用关系、依赖情况应该整理成简单的文档。不要写大而全的设计文档,重点是让下一个接手的人知道工程如何构建、环境如何配置、已知问题在哪里。这些文档在版本发布后整理效率最高,因为此时对项目的整体理解刚好达到顶峰。
10.2 收尾阶段如何减少发布风险
功能开发到 90% 之后,项目真正需要的是把不确定性降下来。不确定的事项越少,发布风险就越低。每次提交代码前问自己三个问题:这个改动影响哪些模块?有没有对应的回归测试方案?如果线上出现问题,能否通过日志快速定位?能明确回答这三个问题,改动质量通常不会差。
最后一个建议是:不要急着把 90% 的进度改成 100%。这个阶段继续暴露问题是正常的,说明回归和验证在起作用;真正需要警惕的是没有任何反馈、大家都很乐观的状态,那往往意味着很多边界场景根本没有被测试覆盖到。把需求矩阵、核心链路用例、发布检查清单准备好,90% 到 100% 的路就会变得非常清晰。项目收尾过程本身也是一次很好的团队复盘素材,把当前版本踩过的坑记录下来,下一个版本再开会规划时,能省下大量试错成本。