在一次客户提完新需求,对应用功能模块做完新增后,原本打算当前发布版本就结束下班的,没想到,在测试OTA时,发现应用新增的模块入口界面排版异常,新增模块对应入口点击后直接应用崩溃,这一下把我干沉没了。
老规矩,adb logcat看crash信息,提示信息是 AndroidManifest.xml 内没有配置新增Activity;
通过adb pull下来apk,解包看里边的 AndroidManifest.xml,配置正常
新增文件正常,但系统运行异常,那就只能往系统缓存排查,由于之前没有遇到该问题,只能通过正常开机日志与异常开机日志对比了,推测问题点是PackageManager相关的信息,所以问题日志主找package相关的,通过mtklog抓取正常升级的ota日志与该次异常升级的开机日志,查找里边package对比两边package相关的解析信息
还真找到了一个异常信息提示
通过对比正常升级与该次异常升级,在异常升级的流程中,出现了不同流程log
正常升级
PackageManager: Destroying unknown cache 77feeab01bc36155135abef0849fcf90be953d08
异常升级
PackageManager: Keeping known cache fb0639540709b7cea68ea0d057dff21c5caebbaf
大胆推测,就是该cache未更新导致apk运行异常
查找出处如下:frameworks/base/services/core/java/com/android/server/pm/PackageManagerService.java#preparePackageParserCache
private static @Nullable File preparePackageParserCache() {
if (!FORCE_PACKAGE_PARSED_CACHE_ENABLED) {
if (!DEFAULT_PACKAGE_PARSER_CACHE_ENABLED) {
return null;
}
// Disable package parsing on eng builds to allow for faster incremental development.
if (Build.IS_ENG) {
return null;
}
if (SystemProperties.getBoolean("pm.boot.disable_package_cache", false)) {
Slog.i(TAG, "Disabling package parser cache due to system property.");
return null;
}
}
// The base directory for the package parser cache lives under /data/system/.
final File cacheBaseDir = Environment.getPackageCacheDirectory();
if (!FileUtils.createDir(cacheBaseDir)) {
return null;
}
// There are several items that need to be combined together to safely
// identify cached items. In particular, changing the value of certain
// feature flags should cause us to invalidate any caches.
final String cacheName = FORCE_PACKAGE_PARSED_CACHE_ENABLED ? "debug"
: SystemProperties.digestOf(
"ro.build.fingerprint",
StorageManager.PROP_ISOLATED_STORAGE,
StorageManager.PROP_ISOLATED_STORAGE_SNAPSHOT
);
// Reconcile cache directories, keeping only what we'd actually use.
for (File cacheDir : FileUtils.listFilesOrEmpty(cacheBaseDir)) {
if (Objects.equals(cacheName, cacheDir.getName())) {
Slog.d(TAG, "Keeping known cache " + cacheDir.getName());
} else {
Slog.d(TAG, "Destroying unknown cache " + cacheDir.getName());
FileUtils.deleteContentsAndDir(cacheDir);
}
}
// Return the versioned package cache directory.
File cacheDir = FileUtils.createDir(cacheBaseDir, cacheName);
if (cacheDir == null) {
// Something went wrong. Attempt to delete everything and return.
Slog.wtf(TAG, "Cache directory cannot be created - wiping base dir " + cacheBaseDir);
FileUtils.deleteContentsAndDir(cacheBaseDir);
return null;
}
// The following is a workaround to aid development on non-numbered userdebug
// builds or cases where "adb sync" is used on userdebug builds. If we detect that
// the system partition is newer.
//
// NOTE: When no BUILD_NUMBER is set by the build system, it defaults to a build
// that starts with "eng." to signify that this is an engineering build and not
// destined for release.
if (Build.IS_USERDEBUG && Build.VERSION.INCREMENTAL.startsWith("eng.")) {
Slog.w(TAG, "Wiping cache directory because the system partition changed.");
// Heuristic: If the /system directory has been modified recently due to an "adb sync"
// or a regular make, then blow away the cache. Note that mtimes are *NOT* reliable
// in general and should not be used for production changes. In this specific case,
// we know that they will work.
File frameworkDir = new File(Environment.getRootDirectory(), "framework");
if (cacheDir.lastModified() < frameworkDir.lastModified()) {
FileUtils.deleteContents(cacheBaseDir);
cacheDir = FileUtils.createDir(cacheBaseDir, cacheName);
}
}
return cacheDir;
}
由于还未确认是哪个数据导致系统认为没有更新,为了快速搞定,临时采取的措施是在判断区域内增加系统版本号的匹配条件,通过如果版本号不一致,则强制要求删除缓存,重新解析包数据,改动如下:
版本检测代码:
private static final String LAST_VERSION_FILE = "/data/system/last_ota_version";
private String mCurrentVersion;
private static boolean hasOtaUpdate = false;
private String readNowVersion() {
Slog.e(TAG, "fanhanxi-pkg readNowVersion");
return SystemProperties.get("ro.build.display.id");
}
private boolean checkOtaUpgrade() {
String lastVersion = readLastVersion();
mCurrentVersion = readNowVersion();
Slog.e(TAG, "fanhanxi-pkg checkOtaUpgrade lastVersion = " + lastVersion);
Slog.e(TAG, "fanhanxi-pkg checkOtaUpgrade mCurrentVersion = " + mCurrentVersion);
if (mCurrentVersion != null && lastVersion != null){
boolean hasUpdate = !(mCurrentVersion.equals(lastVersion));
saveCurrentVersion();
return hasUpdate;
}else{
return false;
}
}
private String readLastVersion() {
Slog.e(TAG, "fanhanxi-pkg readLastVersion");
try (FileInputStream fis = new FileInputStream(LAST_VERSION_FILE)) {
Properties props = new Properties();
props.load(fis);
return props.getProperty("version", "");
} catch (IOException e) {
return "";
}
}
private void saveCurrentVersion() {
Slog.e(TAG, "fanhanxi-pkg saveCurrentVersion");
try (FileOutputStream fos = new FileOutputStream(LAST_VERSION_FILE)) {
Properties props = new Properties();
props.setProperty("version", mCurrentVersion);
props.store(fos, "Last OTA Version");
} catch (IOException e) {
Slog.e(TAG, "Failed to save version", e);
}
}
//
public PackageManagerService(Injector injector, boolean onlyCore, boolean factoryTest) {
...
hasOtaUpdate = checkOtaUpgrade();
....
mCacheDir = preparePackageParserCache();
...
}
//
private static @Nullable File preparePackageParserCache() {
.....
// Reconcile cache directories, keeping only what we'd actually use.
for (File cacheDir : FileUtils.listFilesOrEmpty(cacheBaseDir)) {
if (!hasOtaUpdate && Objects.equals(cacheName, cacheDir.getName())) {
Slog.d(TAG, "Keeping known cache " + cacheDir.getName());
} else {
Slog.d(TAG, "Destroying unknown cache " + cacheDir.getName());
FileUtils.deleteContentsAndDir(cacheDir);
}
}
.....
}
在PackageManager初始化的时候,检查当前系统版本是否存在变动,有存在变动的话,则在preparePackageParserCache中强制删除cache
再编译软件升级,运行正常