只需说话,就能让应用变成你想要的样子
安卓修改大师智改工坊 · 把改包这件事说清楚
应用更新与版本号
装不上、退不回,是发版里最常见的两种卡住

改一个包,最让人措手不及的不是界面改了没生效,而是这一版根本装不上去。双击安装包,系统弹一句「应用未安装」,没有错误码,没有原因,甚至连是不是签名的问题都不告诉你。你换一台手机再试,还是这句。

另一头也一样:新版本发出去,用户反馈某个功能坏了,你想让他先退回上一版,于是把旧安装包发过去。结果旧包也装不上——系统说过的话和刚才那句一模一样。两件事听起来完全相反,原因却是同一个数字。

这一篇讲版本号。它不像图标和名字那样看得见,但每一次安装、每一次更新、每一次回退,都是系统拿它在做判断。写错了不一定报错,报错的时候也不会告诉你错在哪。官网是 www.apkeditor.cn。

一、两个数字,一个给人看,一个给系统看

每个安装包里都有两个版本数字,它们的名字很像,作用完全不同。混着说,后面全乱。

第一个是版本名,通常写成 1.2.3 这种三段式。它是给人和给商店看的:应用详情页上显示它,用户投诉的时候会报它,运营写更新日志的时候用它。系统从来不按它判断新旧,你写 9.9.9 也没用。

第二个是版本代码,是一个整数。它在包里只有一处,没有小数点,没有字母。系统装包、升包、判断能不能覆盖的时候,比的只有这一个数。它大就新,它小就旧,跟版本名写什么毫无关系。

这两个数字最常出问题的地方,是有人以为改了版本名就等于发了新版。版本名从 1.2.3 改成 1.2.4,版本代码还停在上一次的值,系统一看两个包一样新,直接拒绝覆盖。这就是那句「应用未安装」最常见的来由之一。

反过来也有:版本代码加了,版本名忘了改。这种包能装上,但商店页面上显示的版本号没变,用户以为没更新,客服也解释不清。两个数字必须一起动,而且要动的方向一致。

还有一个容易忽略的点:版本代码是整数,所以它只有大小,没有含义。有人喜欢把它编成 20240501 这样的日期,看上去很聪明,但等到一天里发两个包的时候就卡住了。这个问题后面单独讲。

二、装不上的时候,系统到底在比什么

把「应用未安装」拆开看,系统在装包之前其实做了三道比对。三道都过才允许装,任何一道不过就这一句提示,不区分是哪道。

第一道,包名。系统认应用的身份,只认包名。包名不一样,系统就当它是另一个应用,可以并存安装,走的是全新安装的流程。这道不过的情况很少,因为改包的人一般不会动包名,除非有意要做并存。

第二道,签名。新包的签名必须和已装的同包名应用一致。不一致,系统就认为这不是同一个来源发的包,拒绝覆盖。这是三道里最容易踩的一道,也是最不容易自查的一道——因为签名在包里的位置很偏,改完包重新签名时用的证书如果换了,或者根本没用同一份,装的时候就必然失败。

第三道,版本代码。新包的版本代码不能低于已装的那一个。相等不行,更低更不行。相等被拒是因为系统分不清这两个包谁新谁旧,干脆不换;更低被拒是因为系统默认不允许降级,防止用户被诱导装回有漏洞的旧版本。

三道比对里,只有第二道和第三道会经常在改包场景里出问题。第一道出问题通常是有意的,后两道出问题几乎都是无意的。

还有一个细节值得记住:这三道比对全部发生在安装之前,所以它跟应用里面的任何代码都没有关系。你在应用里写多少提示都没用,包根本没进去。这也是为什么这类问题查起来特别费劲——所有能看日志的地方,都还没有开始记录。

把这三道记成一句话:同一个包名、同一份签名、不低于的版本代码,三样齐了才谈得上覆盖安装。任何一次「装不上」,先按这三样逐个排除,比反复重打包快得多。

三、版本号在包里不止一处,改漏一处就白改

版本号看着只有两个数字,但它在整个交付链路里会出现好几个地方。改包的时候只改一处,后面就会对不上。

第一处,包本身的属性。这是系统唯一认的那一处,也就是上面说的版本代码。它在打包的时候写进去,装完之后在系统的应用信息里能看到。这一处不对,前面三道比对就过不去。

第二处,构建脚本里的声明。现在很少有项目手写这个数字,通常写在构建配置里,打包时自动填进去。问题在于有些项目里有两套配置,一套给调试用,一套给正式发版用,改的时候只改了一套,出来的包版本号就是旧的。

第三处,代码里读出来的地方。关于页、设置页底部、帮助与反馈,经常会显示当前版本。这些地方大多是从包里读的,改了版本号会自动跟着变;但也有不少是写死的字符串,于是就出现了「关于页显示 1.2.3,实际装的是 1.2.4」这种让人怀疑人生的场面。

第四处,更新检查的逻辑。应用启动时问服务端「有没有新版」,服务端返回一个数字,客户端拿本地数字去比。这里比的应该是版本代码,不是版本名。拿版本名做字符串比较,会出现 1.10 被判定为比 1.9 旧的情况,因为逐位比的时候 1 小于 9。这个坑每年都有人踩。

第五处,商店后台填的那一栏。上传安装包时,后台会读包里的版本代码,很多平台还会要求你手填一个版本说明。包和后台对不上,审核会被打回,理由通常是「版本信息不一致」,但不会告诉你是哪一项不一致。

第六处,文件名和交付记录。这一处不影响安装,但影响人。发出去的包叫「巡检端_v2_最终版_改.apk」,三个月后没人说得清它到底是哪一版。交付包里最该规范的就是文件名,写清应用、版本名、版本代码、日期,比任何口头交接都可靠。

六处里,前两处决定能不能装,第三第四处决定用户看到的和系统认为的是不是一回事,第五第六处决定事后能不能查清。改版本号这件事,一处不对就是全链路不对。

四、降级安装:为什么几乎一定失败

把新版本装回旧版本,这件事在很多团队里被当成常规操作:出了事先退回去。但在手机上,退回去比装上去难得多。

原因就是第二道和第三道比对的组合。版本代码更低,第三道直接不过;即使你把版本代码改高,签名如果换了,第二道也不过。两道卡住任何一道,结果都是同一句「应用未安装」,没有任何进一步说明。

更麻烦的是,降级不只是装不进去,装进去了也未必能用。这一点经常被忽略。应用在运行过程中会往本地写数据:数据库有版本号,配置有版本号,缓存有格式。新版本把数据库升到第 5 版、加了两列,旧版本只认识第 4 版,读到不认识的版本,常见的结果是直接打不开,或者悄悄把不认识的部分丢掉。装是装上了,数据没了。

所以「回退一版」这件事,实际要做的是三件事,而不是一件:把包弄进去、把数据兼容住、把用户拉回来。只做第一件,往往是白忙。

有没有能装进去的例外?有,但都需要提前准备。一是版本代码仍保持递增:回退的包不是旧包,而是用旧代码重新打一个新版本代码的包,本质上是「往前发一版旧功能」,系统看来仍是升级。二是包名不同:把它做成另一个应用并存安装,代价是数据从零开始。三是卸载重装:这条路一定通,代价是本机数据全部清空。

三条路里,第一条最干净,也最需要纪律:每次发版都保留可重新打包的源码和同一份签名。没有这两样,第一条路走不通,只能选第三条。而第三条对用户来说,等于让他把应用重新装一遍,之前填的东西全没了。大多数「回退一下」的要求,最后卡住的都不是技术,是这一步的代价没人愿意承担。

五、什么时候真的需要降级

先说清楚:不是所有「新版本有问题」都要降级。把不需要降级的场景误判成需要,会付出很大代价。

真正需要降级的场景,通常有这几个特征:影响面大、原因不明、短期修不了。比如新版本在某批机型上必崩,日志里看不出原因;比如某个关键接口在新版里调错了,后端暂时不能改;比如某个客户的定制需求只存在于旧版里,新版把它覆盖了。共同点是:新版没法用,旧版还能用,且短时间内没法把新版修好。

不需要降级的场景也很典型。只是某个功能不好用、只是界面上有个文案错了、只是某一台设备上有问题——这些应该的做法是修一个更高的版本发出去,而不是往回退。往回退的成本比往前修高一到两个数量级,这一点很多人没算过。

这里有个判断标准很好用:如果你能在一天内修好并发出一个更高的版本,就不要考虑降级。因为降级要处理包、数据、用户三件事,而修一版只要处理一件。只有在「一天内修不好」的时候,降级才进入选项。

还有一种情况经常被误认为需要降级:客户环境里装的是我们自己发的旧版本,现在要更新到新版本,装不上。这不是降级,这是升级失败,九成是前面那道签名或版本代码没过。把这两种情况混在一起讨论,会得出完全错误的方案。

分清之后再看一件事:降级是一个流程问题,不是一次操作。它需要有人在发版前就准备好源码、签名、旧版数据库的兼容方案,还要准备一句能对客户说清楚的话。等到出事那天再想,基本只能选卸载重装。

我们内部现在的做法是:每个正式发布的版本,源码打一个标签归档,签名文件按应用分别保管,发版记录里写清版本名、版本代码、日期、改了什么。这三样东西准备起来不花时间,但决定了出事那天有没有退路。

六、回退的三条路,各自要付出什么

三条路都能把旧功能送回用户手上,但代价完全不同。选哪条,取决于你能接受多大的代价。

第一条,重新打一个版本代码更高的包,里面是旧功能。这条路对用户最友好:装的时候是正常升级,数据不动,习惯不变。代价在内部:需要有旧代码、需要能重新打包、需要同一份签名。它的本质不是降级,是「往前发一版旧东西」,所以系统完全不会拦。

这条路有一个必须注意的地方:版本名要写清楚。如果你把它写成旧版本号,用户会以为装到了旧版,以后再也说不清「哪个是最新的」。正确写法是版本名继续往前走,比如加一个后缀说明这是回退版本,版本代码继续递增。版本代码只往前,永远不回头,这是唯一不能破的规矩。

第二条,改包名,做成另一个应用并存安装。这条路技术上一定通,因为系统认为它是新应用。代价是数据不通、权限要重新授权、桌面多一个图标、两个应用可能抢同一个后台服务。它适合的场景很窄:比如要在一台设备上同时跑新旧两套逻辑做对比,或者客户坚持要保留旧版但也不想丢新版。

第三条,卸载再装。这条路最直接,也最伤。卸载会清掉应用的全部本地数据:登录状态、草稿、缓存、本地数据库、下载的文件。如果应用里有没同步上去的数据,卸载就等于删除。所以走这条路之前,必须先确认一件事:用户手上的东西,云端有没有。

三条路的排序很清楚:能重打包就不改包名,能改包名就不卸载。因为代价是递增的:重打包只是内部麻烦,改包名会让用户体验断裂,卸载则会真的丢数据。

还有一个细节经常被忘掉:不管走哪条路,都要先解决「为什么会出问题」。重打包回去只是把症状压住,如果根因还在,下一个版本发出去照样出事。我们在内部的要求是:回退单和原因分析单同时开,回退可以当天做,原因分析必须在一周内给出结论。只回退不分析,等于把同一个坑挖深一点。

七、第一个实例:灰度发了五个客户,四个装不上

我们自己维护的现场巡检端,去年做过一次灰度。选了五个客户先升级,想着有问题影响面小。结果五个里有四个回复「装不上」。

第一反应是包坏了,重新打包发了一遍,还是装不上。第二反应是客户手机的问题,让客户换一台,还是装不上。到这一步才想起来去看版本代码。

一看就明白了:这次灰度的包,版本代码和线上那个包是一样的。原因很普通——上次发版之后,版本代码是在一个公共的构建配置里改的,这次改包是另一条流水线出的包,那条流水线读的是另一份配置,里面还停在上一个值。两份配置,两个数字,谁也没错,但它们对不上。

而唯一装上的那个客户,正好是从来没装过这个应用的——全新安装不经过那三道比对,所以能装上。这个细节当时让判断更混乱:有人说「他能装,说明包没问题」,实际上恰恰说明包有问题,只是这一台走的是另一条路径。

修起来很快:把版本代码改成比线上更高,重新出包,五个客户一次全过,前后不到两小时。但排查花了半天,半天里做的事全是猜。

这件事之后我们定了三条规矩,到现在还在用。第一,版本代码只在一处定义,所有流水线都读这一处,不允许各写一份。第二,出包之后先看包里的版本代码,不看构建日志,直接看成品包,因为成品才是发出去的东西。第三,灰度名单里必须有一个从没装过的新设备,用来区分「覆盖安装」和「全新安装」两条路径。

第三条最容易被忽略,也最有用。如果灰度名单全是老客户,你永远测不到全新安装的路径;如果全是新设备,你永远测不到覆盖安装的路径。两条路径的失败方式不一样,提示却一模一样。

八、第二个实例:退回去了,数据打不开

另一个实例是门店经营管理平台。有一次新版本上线,某个报表在部分门店算错了,影响对账,必须马上处理。修一版至少要一天,所以决定先回退。

这次回退走的是第一条路:用上一版的代码,打一个版本代码更高的包。装的过程很顺,所有门店都升级成功了。然后问题来了:有六家门店打开应用之后,进不了主界面,卡在加载页。

查下来是数据库。新版本在升级时把本地数据库的版本升了一级,加了两张表。回退的包只认识旧的数据库版本,打开的时候发现版本比自己高,它的处理方式是放弃,而不是降级读取。于是就卡住了。

这个问题的麻烦之处在于:它只发生在「装过新版本」的设备上。没升级过的门店一切正常,所以一开始怀疑的方向全错了——有人怀疑是回退包的签名,有人怀疑是某几家门店的网络,还有人怀疑是账号权限。真实原因藏在本地数据里,而本地数据不在任何人的视线里。

最后处理的方案不复杂:给回退包加一段兼容逻辑,遇到更高的数据库版本时,把新增的表按空表处理,不让它直接放弃。这段逻辑写起来不到半天,但它必须写在回退包里,也就是必须重新打一次包、再升一次版本代码、再发一轮。一轮下来,比直接修原问题还慢。

这件事给我们的教训有两条。第一,回退方案必须在发版之前就验证过,而不是出事那天第一次跑。现在的做法是:每个版本上线前,用上一版包在装过新版的测试机上装一次,看能不能正常打开、数据还在不在。第二,凡是改过本地数据结构的版本,回退的难度都要单独评估,因为它的风险不在安装环节,在打开环节。

回头看,那六家门店之所以中招,还有个偶然因素:他们是升级最积极的一批。越早升级的门店,越可能在回退时踩到这个坑。这提醒我们一件事:回退方案要按「已经升级的用户」来设计,而不是按「还没升级的用户」。前者才是真正需要回退的人。

九、更新提示:服务端和客户端比的是哪个数

应用里的「发现新版本」这个功能,看起来简单,实际上是一个很容易出错的地方。错的地方通常在比较方式上。

正确的做法是固定的:服务端返回最新版本的版本代码,客户端拿本地版本代码跟它比,大就提示更新,不大就不提示。两个整数比大小,没有歧义。

错的做法有很多种,最常见的是拿版本名做字符串比较。字符串比较是逐位比字符的:1.10 和 1.9 比,第一位都是 1,第二位 1 小于 9,于是判断 1.10 比 1.9 小。这个错误在版本号从个位数跨到两位数的那一天才会暴露,之前一直是对的,所以特别容易被忽略。

第二种错法是服务端和客户端约定不一致。服务端返回的是版本名,客户端拿它跟版本代码比,或者反过来。这种错不会报错,只会一直提示更新或者一直不提示。症状是「用户说有新版,我这里没有」,然后就变成一场扯皮。

第三种错法是缓存放错了位置。检查更新的结果被缓存了一天,用户手动点「检查更新」也读缓存,于是新版本发布了,用户怎么点都看不到。手动检查应该是强制刷新,这是常识,但实现的时候经常忘。

第四种错法最隐蔽:渠道包共用一份更新配置。同一份代码出了三个渠道的包,版本代码不一样,但服务端只维护了一个最新版本号,于是某个渠道的用户永远收到「有新版本」,点进去装不上。这种问题会持续很久,因为从后台看,一切正常。

这四种错法的共同点是:都不报错。没有崩溃、没有日志、没有告警,只有用户的感觉不对。所以检查更新这件事,验收时要专门做两件事:把本地版本代码改低一位,看提示是否正确出现;把本地改成和服务端一样,看提示是否会消失。两件都过,才算这个功能是对的。

十、版本代码怎么编:两种常见方案和各自的坑

版本代码是一个整数,理论上你想写多大都行。正因为自由,团队里就出现了各种自创的编码方式,各有各的坑。

第一种,纯递增。第一次发版写 1,第二次写 2,一路加下去。它最大的好处是永远不会算错:下一次是多少一目了然,谁都编不出歧义。缺点是它不携带任何信息:看到 137,你不知道这是哪一天发的、对应哪个版本名,得去翻记录。

第二种,日期式。把年月日拼成一个数,比如 20240501。它的好处是把时间和版本绑在一起,看数字就知道哪天发的。坑在一天发两个包。同一天出了两个包,日期是一样的,第二个包如果也用这个数字,就装不上;如果顺手加一位,变成 202405011,那第二天就又乱了。日期式还有一个隐患:它假设日期是单调的,跨时区或者时间被改过,排出来的顺序就会错。

第三种,混合式。主版本号、次版本号各占固定几位,后面留几位给构建序号。比如三段各占两位,加上两位构建号,拼出来是 1020301 这样的数。这种方案信息量最全,但必须写清楚规则并贴在墙上,否则第二年接手的人一定算错位数。

不管你选哪种,有三条底线不能破。第一,只增不减。任何情况下新包的版本代码都不能小于或等于线上包的,包括回退包、修复包、紧急包。第二,同一份代码出的包共用同一个数。否则用户从 A 渠道换到 B 渠道会装不上。第三,写进发版记录。每次发版记一行:日期、版本名、版本代码、改了什么。这一行记录将来能省掉几小时的排查。

还有一个很实际的建议:把版本代码留出余量。如果你用递增式,别从 1 开始,从 100 开始,前面留给测试包和内部包;如果你用日期式,后面留两位给当天的构建序号。余量的意义不是好看,是在紧急情况下还有空间插一个包进去。

最后一句提醒:版本代码是给机器看的,不要试图让它同时承担给人看的功能。想表达「这是第几个大版本」「这是哪天的包」,用版本名和发版记录去表达。让它只做一件事——比大小,它就不会骗你。

版本号在包与交付链路中的位置
同一个数字,在六个地方各有一份,改漏一处就对不上

十一、渠道包与白牌交付:同一个应用,好几个版本

同一份代码,往往会出好几个包:正式渠道、内部测试、某个客户的定制版、白牌客户的换皮版。这些包的版本号怎么协调,是一个很容易被忽略的问题。

最省事的想法是「都写一样的版本代码」。听起来合理,实际会出问题:用户从定制版换回正式版时,因为版本代码相同,装不上;两个渠道的包同时推到同一台设备上,也装不上。而这两个包的功能可能确实有差别,用户根本不知道装的是哪个。

比较稳妥的做法是给每个渠道分配固定的号段。比如正式渠道用 1 开头,内部测试用 8 开头,某个大客户的定制版用 3 开头,白牌客户按签约顺序分配。这样任何一个包的版本代码都能反推出它属于哪个渠道,而且不同渠道之间天然不会撞号。

号段方案有一个前提:渠道之间不能互相覆盖安装。这通常是符合预期的,定制版和正式版本来就不该混装。如果确实需要覆盖,那就说明它们应该是同一个渠道,不该分号段。号段不是限制,它是在用数字表达「这两个包不是一回事」。

白牌交付的场景更麻烦一点。白牌的特点是同一个应用给多个客户,各自换名字、换图标、换配色。如果只改这些表面东西,包名不变,那所有白牌客户的包就是同一个应用的不同版本,谁后装谁覆盖,这在交付现场几乎必然出问题。

所以白牌交付一般会连包名一起改,让每个客户的包成为独立的应用。这样带来三个后果,都要提前想清楚:一是版本号要各自独立递增,不能共用一个数字;二是签名可以共用一份,但每个客户的包必须能追溯到同一份签名,否则以后升级会失败;三是更新通道要分开,不能让 A 客户收到 B 客户的更新。

我们内部做过一次白牌交付,五个客户,最初图省事共用了一套版本号和一份更新配置。结果第二个客户的包发出去之后,第一个客户的用户收到了更新提示,点进去装的是别人的品牌。那次之后,白牌的规矩改成:一个号段、一份签名、一套更新配置,三样一一对应,不许共用。

十二、七个问题,都是发版时真被问过的

下面这七个问题,基本覆盖了版本号相关的绝大部分困惑。答案都不长,但每一条都能省掉一次返工。

问题一:为什么改了版本名还是装不上?因为系统不看版本名。它只比版本代码,两个包一样大就拒绝覆盖。改版本名的同时必须把版本代码加一。

问题二:为什么同一个包装在这台能装、那台不能?大概率是那台已经装了一个版本代码相同或更高的同包名应用。也可能那台上装的是另一个签名的版本,比如从应用商店装的正版,和你自己签的测试包。不同签名之间永远不能互相覆盖。

问题三:卸载重装为什么一定行?因为卸载把应用和它的数据一起清掉了,安装时就没有「已装的同包名应用」可比对,走的是全新安装。代价是本机数据全清,包括登录状态和未同步的内容。

问题四:用户的版本比我还新,怎么办?说明他装过更高的版本代码。这种情况只能让他卸载,或者你发一个更高的版本代码。没有第三条路,因为系统不允许降级。

问题五:能不能出一个「降级包」?能,但它的版本代码必须比现行版本更高,本质是「用旧功能打一个新版本」。如果坚持用原来的旧版本代码,那就装不上,不管你叫它什么。

问题六:签名文件丢了怎么办?这个最麻烦。签名不一致的包无法覆盖安装,只能改包名做成新应用,或者让所有用户卸载重装。所以签名文件的备份,是和代码同等重要的事,而且必须多人多处保管,不能只在一个人的电脑上。

问题七:内部测试包怎么发才不干扰正式版?两条路:要么用不同的包名,测试包和正式包并存;要么用同一包名但版本代码始终高于正式版,测完再发正式版时把代码继续往上加。前者的代价是数据不通,后者要求测试包的版本代码管得住。两条路都有人用,但千万别混着用。

十三、验收清单:七项走完才算发完一版

下面这张表是我们内部发版前会过的七项。看起来都是小事,但每一项都对应过一次真实的事故。

序号检查项怎么查不过会怎样
1版本代码比线上高只看成品包,不看构建配置相等就是装不上,最常见
2版本名和版本代码同步两个数字一起动只动一个,商店页面会对不上
3包里的签名是同一份和上一版比对换了签名,覆盖安装必然失败
4关于页显示的是新版本号装完打开看一眼写死的字符串会留在旧值
5覆盖安装成功在装过旧版的设备上装这条路径和全新安装不一样
6全新安装成功在从没装过的设备上装灰度名单里必须有这样一台
7回退方案已跑过用上一版包在装过新版的机器上装一次不跑就等于没有回退方案

这七项里,第五和第六经常被合成一项来做,这是最常见的一个疏漏。覆盖安装和全新安装走的是两条不同的判断路径,失败的表现却完全一样,都是那句没有原因的「应用未安装」。只测一条,就等于只测了一半。

第七项是这七项里最容易被跳过的一项,因为它看起来「用不上」。但回退方案的价值,恰恰在于它只在出事那天被用到一次,而那一次必须成功。前面第二个实例里那六家门店,就是这一项没做直接导致的。

还有一个操作层面的建议:把这张表做成发版单的一部分,每次发版逐项打勾,签上名字。不是为了追责,是为了让「这次有没有查第七条」变成一个能看见的事实,而不是一句「应该查过了」。

安装失败提示
三道比对任何一道不过,提示都是同一句,不告诉你是哪一道

十四、一次返工的账:两小时改完,半天在猜

把前面那个灰度的事故算一遍,会看到一个很典型的比例。

实际修复:两小时。包括找出两份构建配置、统一成一处、重新出包、发给五个客户、等他们确认装上了。排查:半天。包括重打两次包、让客户换设备、怀疑网络、怀疑存储空间、拉群讨论。比例是 1 比 2,而且那半天里没有产生任何有效信息,所有动作都是猜。

真正让排查变慢的,是那句提示本身。「应用未安装」这句话把三道比对的结果合并成了一个,不告诉你卡在哪一道。而三道比对里,签名和版本代码的排查方式完全不同:前者要去比对证书指纹,后者只要看一眼数字。合并之后,你没法从提示反推,只能逐个试。

所以后来我们做了一件很小的事:在发版流程里加一步「看一眼成品包的版本代码」。这一步大概十秒钟,用的是现成的工具,不需要写代码。加进去之后,同类问题的排查时间从半天降到几分钟。因为它把「三道比对」里最常坏的那一道,变成了发版前就能看见的事实。

这件事还有一个副作用值得说:它改变了讨论的方式。以前出问题,讨论的是「是不是包坏了」「是不是手机的问题」;现在讨论的是「版本代码是多少」「签名是哪一份」。前者是猜,后者是查。一个团队把猜换成查,靠的往往不是能力提升,而是把关键信息挪到了能被一眼看到的位置。

顺便说一句关于回退的账。回退的成本,几乎全部花在「没想到」上。没想到数据会被升版本、没想到旧包在新数据上打不开、没想到重打一次包又要走一轮验证。这些「没想到」在事后看都很明显,但在事前,它们不在任何人的清单上。所以那份七项验收表的价值,不在于它多完整,而在于它把「没想到」变成了「要打勾」。

发版前的验收核对
发版前多看一眼成品包,比事后排查半天更省时间

十五、把「发一版」写成一句能执行的话

和前面几篇一样,这件事最后要落到一句能派下去的话上。反例是「发个新版本,注意别装不上」,正例要把关键信息写全。

一句能执行的话长这样:把巡检端的版本名从 1.2.3 提到 1.3.0,版本代码从 42 提到 43;只改公共构建配置里的那一处;用同一份签名出包;发出去之前在装过 1.2.3 的设备上做一次覆盖安装,在一台新设备上做一次全新安装;两个都装上之后,打开关于页确认显示 1.3.0;最后按发版单七项打勾。

这句话里,有四个地方是必须有的。第一,两个数字的原值和新值都要写出来,不能只说「升一下版本号」——升到多少,是最容易出分歧的地方。第二,写清改哪一处,因为包里和构建配置里各有一份,两份不一致就是事故的源头。第三,写清签名从哪儿来,不能默认「用原来那个」。第四,写清验收的两个场景,覆盖安装和全新安装各一次。

还有两条建议值得加进去。一是把「回退方案」写进发版单:这一版如果出问题,退回到哪个版本代码,用哪份源码,谁有权决定回退。这句话平时没用,出事那天能省掉一场会。二是把发版记录同步给所有会接触客户的人:客服、交付、销售,因为他们是最早听到「装不上」的人,手里有版本代码,才能第一时间判断是哪种情况。

我们内部现在的发版单,从最早的三行变成了现在的十四行。多出来的十一行,每一行都对应一次事故。发版单变长不是坏事,它意味着那些曾经靠运气躲过的问题,现在靠流程挡住了。

十六、三句话,把这件事收住

聊到这里,能带走的东西可以压成三句。

第一句:给人看的是版本名,给系统看的是版本代码,两个都要动。只改一个,要么装不上,要么商店页面对不上。两个数字一起改,是最省事的习惯。

第二句:装不上只有三种原因,包名、签名、版本代码。任何一次「应用未安装」,都在这三样里,逐个排除比反复重打包快得多。而且这三样都在装包之前生效,跟应用里面的代码一点关系都没有。

第三句:回退不是把旧包发过去,是往前发一版旧功能。版本代码只能往上走,这是唯一不能破的规矩。想让用户回到旧功能,只能用更高的版本代码重新打包,同时确认本地数据能被旧版本读懂。

三句合起来,其实是一件事:版本号是发版这条链上唯一的硬约束,其他环节都可以商量,它不行。所以它值得在动手之前就被写清楚,而不是在装不上的时候被想起来。

十七、附:一页速查

下面这张表把全文压成八行,发版或排查装不上的时候可以对着看。

要点内容注意
两个数字版本名给人看 / 版本代码给系统看只改版本名,必然装不上
三道比对包名同、签名同、版本代码不低于任何一道不过,提示都是同一句
比对时机全部发生在装包之前应用里的代码和日志此时还没开始跑
降级系统默认不允许想退回旧功能,只能发更高的版本代码
回退三条路重打包 / 改包名 / 卸载重装代价依次递增,能重打包就别卸载
数据的坑数据库版本升高后旧版读不了装上了打不开,比装不上更难查
更新提示比版本代码,不比版本名字符串比较会让 1.10 小于 1.9
验收七项两个安装场景 + 一次回退演练只测一条路径等于只测一半

最后补一句:上面这些都能在同一次改包里一起处理掉。版本名、版本代码、签名、回编,本来就是一次发版要动的全部东西,把它们放在一个流程里,比四处分头改要稳。安卓修改大师智改工坊把这几个环节放在一次操作里,改完可以真机预览确认,产品介绍页在 这里。

十八、用过的人怎么说

灰度五个客户四个装不上,最后发现是两份构建配置里的版本代码不一样。现在只留一处,改完直接看成品包。

—— 老宋 · 现场系统负责人

回退过一次,装是装上了,六家门店打不开。数据库版本比旧版高,旧版直接放弃。这个坑我记一辈子。

—— 阿凯 · 门店端开发

1.10 比 1.9 小这个错我们犯过,用户一直在提示更新,点进去又没有。改成比版本代码之后就好了。

—— 方工 · 移动端测试

最有用的是「覆盖安装和全新安装是两条路」。以前只在自己手机上试,永远试的是覆盖安装。

—— 陶然 · 交付工程师

白牌五个客户共用一套更新配置,结果 A 客户收到了 B 客户的包。现在一个客户一个号段。

—— 林工 · 白牌交付

签名文件只存在一个人电脑上,那个人离职之后我们才发现。现在三处备份,交接清单里必查。

—— 陈默 · 企业应用维护

这一篇里的两个实例,都是我们自己维护的应用,一套现场巡检端和一个门店经营管理平台。版本号这件事的特点是:它对的时候你完全感觉不到它,错的时候它只说一句话,而且不说原因。所以文中的三道比对、六处位置、七项验收,都是从这类一句话里长出来的。

合规提醒:本文所有实例均针对自有版权或已获授权的应用,用于企业内部维护、白牌交付与合法定制。请勿通过改动版本号、签名或安装包属性绕过系统安全校验、冒用他人应用身份,或为未授权应用制作分发版本。改包前请确认你对目标应用拥有相应权利,并妥善保管签名材料。

版本号是发版这件事里最不像「设计」的一部分,却决定了你的改动能不能到用户手上。它不负责好看,只负责让系统认得出这是新的一版。

安卓修改大师智改工坊能做的,是把改包之后那一串连锁动作一次落到位:版本名和版本代码一起改、回编、对齐同一份签名,改完可以真机预览再确认。说清楚,就够了。

只需说话,就能让应用变成你想要的样子

安卓修改大师智改工坊 · 版本号、签名、回编,一句话就改

自动回编 · 对齐签名 · 真机预览 · 每一处改动可追溯

立即下载智改工坊(AI 版)

Windows 7 SP1 及以上 · 产品介绍页 https://www.apkeditor.cn/ai-version.aspx