Android重启后已升级的内置应用被还原
背景原因
前两天客户报过来一个问题:
机器预置了客户自己开发的 APP,他们在内测的时候发现,APP 在线升级后,只要重启机器,APP 就被还原成一开始预置的版本了……
因没有对我们开放测试渠道,故无法第一时间复现和分析问题;也不清楚客户那边的专业技能水平如何,操作方式是否正确。而我们同 base 的项目从来没有出现过类似问题,我们也没有修改过 APP 升级代码,于是在开始排查代码前,先和客户确认了如下问题:
-
在线安装改为本地手动安装是否还有问题?
原因:APP 升级时采用的是在线静默安装,怀疑可能静默安装有问题。
反馈:APK 放入机器,手动安装同样有问题。 -
Settings 里面是否显示为新版本?
原因:排除 APP 读取版本信息不准确的可能,确认系统真的有处理了升级。
反馈:重启前,APP 自身、Settings 均显示已升级到新版本;重启后,均显示版本被还原了。 -
包名类名是否变化了?
原因:APP 是Launcher
,而且是默认 Home
,若包名类名变化,在重设 Home 前,重启后自动启动原来的 Home ,好像也说得过去。
反馈:包名类名没有变化。 -
开放测试渠道,了解客户APP检测更新,处理升级的流程。
反馈:客户服务器上有一个路径,APP每次启动都会去看这个路径是否有文件,如果有,就判定为有新版本,然后就下载,静默安装更新。
应用更新规则
做 APP 开发的应该都知道两个 Manifest 属性:versionCode
、versionName
。
早期使用 Eclipse+ADT
开发时,这两个属性是直接定义在 AndroidManifest.xml
中的,而 Manifest 的改动频率也比较高,因此开发人员基本都能注意到。
后面 Google 推了 AS 之后,这两个属性的配置就转到 gradle
脚本了,然后在编译的时候,由脚本自动添加到 AndroidManifest.xml
,这时候直接AS上手的可能就容易忽视了,尤其一些没有上架 APP Store 计划的项目。
再回过来说这两个属性,官方API是这么描述的,拿笔记住:
versionCode: Integer, the version code
versionName: String, the version name
versionCode
才是版本号
(类是于ID),作为系统判断应用是否能升级的依据,每次版本变化的时候都要随着变化,规则是数字越大版本越新
。
versionName
只是版本名
,是给人看的,方便理解的一串字符串,可以随便定义随便改,怎么写看你自己,就是玩出花来都没人管你。但是,一般都习惯写成 Vx.x.x
的形式,有的也在上面加上时间,总之都是为了增加可读性。
扯了这么多,反复强调了这么多,相信根据背景原因中的升级策略,也能猜出来是什么问题了。没错,这 APP 前后两版的 versionCode 是一样的,导致重启后,系统在扫描加载应用的时候,直接使用了内置的……
解决方法
对于这个问题,解决方法有两种:一种是修改 APP
,按设计标准来;一种是修改 framework
,改变APP扫描加载时的判断逻辑。
1、修改 APP
这种方法是标准的处理方式,也是能被普遍接受的方法。方法很简单,每次更新版本时,修改下 versionCode
,而不是根据 versionName
判断,更不是像这里一开始说的判断服务器是否有这个文件。
2、修改 framework (代码来源:MTK N0)
此方法不具通用性,仅适用自有 Rom 的情况,其它 Rom 不适用。而且因为修改了被普遍接受的标准的系统逻辑,不排除有引起其它问题的可能。
扫描加载 APP 的代码在 PackageManagerService
中:
\frameworks\base\services\core\java\com\android\server\pm\PackageManagerService.java
在这个文件中有这么一个方法:
/**
* Scans a package and returns the newly parsed package.
* @throws PackageManagerException on a parse error.
*/
private PackageParser.Package scanPackageLI(PackageParser.Package pkg, File scanFile,
final int policyFlags, int scanFlags, long currentTime, UserHandle user)
throws PackageManagerException {
在扫描内置应用时,它调用了下面的方法,也就是在这里判断了 versionCode
,代码如下:
/**
* Scans a package and returns the newly parsed package.
* @throws PackageManagerException on a parse error.
*/
private PackageParser.Package scanPackageInternalLI(PackageParser.Package pkg, File scanFile,
int policyFlags, int scanFlags, long currentTime, UserHandle user)
throws PackageManagerException {
......
/** M: [Operator] Allow vendor package downgrade. @{ */
/// Always install the updated one on data partition
if (pkg.mVersionCode < ps.versionCode
|| ((policyFlags & PackageParser.PARSE_IS_OPERATOR) != 0)) {
/** @} */
......
}
因此要在 framework 层解决这个问题,只需要将上面的 pkg.mVersionCode < ps.versionCode
修改为 pkg.mVersionCode <= ps.versionCode
。
小结
-
Android APP升级有标准的,被普遍接受的判断方法,就是以 versionCode 作为判断依据。versionName只是用来增强可读性,让用户能看懂版本变了,不能作为升级判断的依据。
-
系统 APP 升级个人这么理解,不一定正确,仅供参考:
内置的应用在 system 分区,用户安装(升级)的应用在 data 分区。
当内置应用升级后,system 和 data 各自持有一个版本。
系统重启,扫描加载APP,以“Always install the updated one on data partition”为原则,判断 system 和 data 两个版本的 versionCode 大小。
如果 system >= data 则将 system 的拷贝到 data 覆盖掉之前的版本。
如果 system < data,则继续加载data的版本。