Android “original-package” 机制解析
原文:https://blog.csdn.net/hwy584624785/article/details/51774580
问题
之前项目上遇到一个问题:手机系统从版本 A 通过 FOTA 升级到版本 B 后,系统源码中的输入法(LatinIME.apk,下面称 LatinIME)不见了!也就是设置中语言和输入法选项没有 Android Keyboard 这一项,并且设置中的应用选项中也找不到。
问题情况
先了解了一下两个版本的情况:版本 A 是只有 LatinIME 的;版本 B 中除了 LatinIME 还新预制了 GMS 中的输入法(LatinImeGoogle.apk, 下面称 LatinImeGoogle)。升级后版本 B 的 LatinIME 输入法不见了,只剩下 LatinImeGoogle 输入法。
初步分析
分析的大概步骤如下:
首先确定升级后版本 LatinIME 虽然是看不见了,但 apk 包依然存在并且此情况是必现的,所以不可能是 FOTA 升级的某些意外导致的。
同时已经确定的情况:
直接 download B 版本,两个 App 是同时存在的。
不管是 A 版本还是 B 版本通过 adb install 或 adb push 安装两个输入法应用,两个 App 都是同时存在的。
通过 pm list package 命令查看
版本 A 信息如下
1 | package:/system/app/LatinImeGoogle.apk=com.google.android.inputmethod.latin |
版本 B 的信息如下
1 | package:/system/app/LatinImeGoogle.apk=com.android.inputmethod.latin |
版本 B 的输入法包名是变了的,而且是变成了 LatinIME 的包名了!
反编译应用,在 LatinImeGoogle 的 manifest 文件中发现有 orignial-package 属性:
1 | <original-package android:name="com.android.inputmethod.latin" />1 |
看,与步骤 3 已经联系上了。所以这个属性应该就是问题所在。
接下来分析 original-package 属性。
源码中搜 original-package, PackageParser.java 中的 parsePackage() 方法对属性的处理:
1 | } else if (tagName.equals("original-package")) { |
查 AndroidManifestOriginalPackage 的定义,在 attrs_manifest.xml 文件中:
1 | <!-- Private tag to declare the original package name that this package is |
官方的解释很清楚了:之前安装的应用是系统应用,并且包名不同,之前应用的数据就会以新安装应用的名字保留下来。
这已经可以解释 LatinIME 消失的原因了。但是仅仅通过这个说明是不能完全解释步骤2中的现象的.为什么 adb push 和 adb install LatinImeGoogle 后的现象是两个应用并存呢?
深入分析 original-package 机制
继续看源码。
上面 parsePackage() 方法中的 mOriginalPackages,查源码可知对 mOriginalPackages 的其余处理只在 PackageManagerServerice 中了。mOriginalPackages 在 PackageManagerService 中出现的次数并不多,但要搞清流程,简单有效的方法就是打 log。 打 log 这里有个小技巧。当时因为是 FOTA 升级上来的,总不可能添加一些 log 就去做一次升级抓 log 吧,很麻烦并且不能保证有用的 log 没有被冲刷掉。所以当时直接拿版本 B 的手机做 dumpsys 操作,查找有没有相关 package 信息。果然是有的:
Package warning messages:
1 | 1/1/14 12:08 AM: No settings file; creating initial state |
对应源码 scanPackageLI(pkg,…) 的代码:
1 | // File a report about this. |
reportSettingsProblem() 方法是既可以在 dumpsys 又可以在 logcat 中打印出来的,用此方法添加 log 发现 FOTA 升级上来走的流程是 if (pkg.mOriginalPackages.contains(renamed)) ,而 adb install 和 adb push 走的流程是它的 else 流程,是因为 renamed 为空,也就是 mSettings.mRenamedPackages 不包含 com.google.android.inputmethod.latin 。
mRenamedPackages 的值添加是在 Settings.java (/frameworks/base/services/java/com/android/server/pm/Settings.java) 中,最终处理(具体调用过程不写了)在两个地方:上面提到的 if 语句下面调用的 getPackageLPw() 方法,这个显然不是原因。另一个是在 PackageManagerService 的构造函数调用 readLPw() 方法处理。
我们知道 PackageManagerService 是 SystemServer 调用启动,SystemServer 又是 Zygote 启动,这就是原因:采用adb push 和 adb install时在PKMS的构造函数中会先清除已经不存在的应用,然后再扫描该应用 ,所以这样安装应用 “original-package” 机制是不起作用的,FOTA 当然是会走构造函数的,所以“original-package” 机制是有效的。到此问题已经解决,更详细的内容可以查看 PackageManagerService 源码。
总结
Android 的 “original-package” 运行机制:
之前已安装的应用是系统应用,并且新安装的应用与之前包名不同,那么定义有 original-package 属性的新应用就可以在走 PackageManagerService 构造函数的流程下(例如 FOTA 升级)实现应用替换,保持原有应用数据不变,但是以新安装应用的形式展现给用户。查看源码 /packages/apps/ 下的应用,会发现很多都有此属性,就是为了以后升级时应用数据共享。这一属性从 Android 的 Froyo 版本就有,可以看出主要是针对 Google 自己和 ROM 厂商而设置的,一般的开发者使用不到。
Android O PKMS中关于origin-package中的处理:
scanPackageDirtyLI
:
1 | // Check if we are renaming from an original package name. |