最近开发Android P(9.0), 关闭WITH_DEXPREOPT(不关闭的话,编译framework或者services生成的jar包不能直接push到机器,影响开发效率,不知到有没有方法解决这个问题)后,发现每次开机都要很久。
我们使用log大法分析一下
10:12:30.474 1338 1338 I SystemServer: UpdatePackagesIfNeeded
...
10:12:40.544 1338 1338 I PackageManager.DexOptimizer: Running dexopt (dexoptNeeded=1) on: /system/priv-app/FusedLocation/FusedLocation.apk pkg=com.android.location.fused isa=arm64 dexoptFlags=public targetFilter=verify oatDir=null classLoaderContext=PCL[/system/framework/com.android.location.provider.jar]
10:12:40.546 974 1363 V installed: DexInv: --- BEGIN '/system/priv-app/FusedLocation/FusedLocation.apk' ---
10:12:40.613 1709 1709 I dex2oat: /system/bin/dex2oat --input-vdex-fd=-1 --output-vdex-fd=19 --compiler-filter=verify --classpath-dir=/system/priv-app/FusedLocation --class-loader-context=PCL[/system/framework/com.android.location.provider.jar] --generate-mini-debug-info --compact-dex-level=none --compilation-reason=boot
10:12:40.741 1709 1709 I dex2oat: dex2oat took 131.017ms (122.126ms cpu) (threads: 8) arena alloc=0B (0B) java alloc=132KB (135984B) native alloc=2MB (2499928B) free=1654KB (1694376B)
10:12:40.783 974 1363 V installed: DexInv: --- END '/system/priv-app/FusedLocation/FusedLocation.apk' (success) ---
...
10:12:45.738 1338 1338 D SystemServerTiming: UpdatePackagesIfNeeded took to complete: 15264ms
从开机log看出,用了15s,实际情况是应用越多耗时越久
为什么每次都做这个优化,现有log信息看不出来,那就查代码吧
/frameworks/base/services/java/com/android/server/SystemServer.java
if (!mOnlyCore) {
traceBeginAndSlog("UpdatePackagesIfNeeded");
try {
mPackageManagerService.updatePackagesIfNeeded();
} catch (Throwable e) {
reportWtf("update packages", e);
}
traceEnd();
}
显然mOnlyCore为false,因为log里打印了UpdatePackagesIfNeeded
public void updatePackagesIfNeeded() {
enforceSystemOrRoot("Only the system can request package update");
// We need to re-extract after an OTA.
boolean causeUpgrade = isUpgrade();
// First boot or factory reset.
// Note: we also handle devices that are upgrading to N right now as if it is their
// first boot, as they do not have profile data.
boolean causeFirstBoot = isFirstBoot() || mIsPreNUpgrade;
// We need to re-extract after a pruned cache, as AoT-ed files will be out of date.
boolean causePrunedCache = VMRuntime.didPruneDalvikCache();
if (!causeUpgrade && !causeFirstBoot && !causePrunedCache) {
return;
}
List pkgs;
synchronized (mPackages) {
pkgs = PackageManagerServiceUtils.getPackagesForDexopt(mPackages.values(), this);
}
final long startTime = System.nanoTime();
final int[] stats = performDexOptUpgrade(pkgs, mIsPreNUpgrade /* showDialog */,
causeFirstBoot ? REASON_FIRST_BOOT : REASON_BOOT,
false /* bootComplete */);
...
}
这个函数先收集需要做优化的pkgs,然后将pkgs交给函数performDexOptUpgrade
收集pkg前,有一个判断,
!causeUpgrade && !causeFirstBoot && !causePrunedCache
如果不是升级,并且不是第一个开机,并且没有删减cache,就直接返回
显然cache被删减了,为了证明我的推测,添加了log,push service.jar,重启,
果然
1297 1297 I PackageManager: causeUpgrade=false,causeFirstBoot=false,causePrunedCache=true
为了进一步确定,对比了7.0的Android,这三个变量都是false
VMRuntime.didPruneDalvikCache()
是java库的函数,VMRuntime.java对应的native文件是
art/runtime/native/dalvik_system_VMRuntime.cc
对应的函数是
static jboolean VMRuntime_didPruneDalvikCache(JNIEnv* env ATTRIBUTE_UNUSED,
jclass klass ATTRIBUTE_UNUSED) {
return Runtime::Current()->GetPrunedDalvikCache() ? JNI_TRUE : JNI_FALSE;
}
继续跟踪,art/runtime/runtime.h
bool GetPrunedDalvikCache() const {
return pruned_dalvik_cache_;
}
发现只是读这个变量值,那这个值在哪里设置的呢
搜索art目录,发现,只有一个地方设置了这个值,就是删减cache的时候,cache是DalvikCache
art/runtime/gc/space/image_space_fs.h
static void PruneDalvikCache(InstructionSet isa) {
CHECK_NE(isa, InstructionSet::kNone);
// Prune the base /data/dalvik-cache.
// Note: GetDalvikCache may return the empty string if the directory doesn't
// exist. It is safe to pass "" to DeleteDirectoryContents, so this is okay.
impl::DeleteDirectoryContents(GetDalvikCache("."), false);
// Prune /data/dalvik-cache/.
impl::DeleteDirectoryContents(GetDalvikCache(GetInstructionSetString(isa)), false);
// Be defensive. There should be a runtime created here, but this may be called in a test.
if (Runtime::Current() != nullptr) {
Runtime::Current()->SetPrunedDalvikCache(true);
}
}
这个函数调用的地方有几个,而且都在art/runtime/gc/space/image_space.cc
,并且有log,只需要在文件最前面定义
#define LOG_TAG "art"
就能打开log.
编译make libart -j8
, push, 重启
1006 1006 W art: Failed execv(/system/bin/patchoat --input-image-location=/system/framework/boot.art --output-image-directory=/data/dalvik-cache/arm --instruction-set=arm --verify) because non-0 exit status Preemptively pruning the dalvik cache.
找到了删减cache的代码位置
std::unique_ptr ImageSpace::CreateBootImage(const char* image_location,
const InstructionSet image_isa,
bool secondary_image,
std::string* error_msg) {
...
bool verified = VerifyImage(image_location, dalvik_cache.c_str(), image_isa, &local_error_msg);
// If we prune for space at a secondary image, we may end up in a crash loop with the _exit
// path.
bool check_space = CheckSpace(dalvik_cache, &local_error_msg);
if (!verified || !check_space) {
// Note: it is important to only prune for space on the primary image, or we will hit the
// restart path.
LOG(WARNING) << local_error_msg << " Preemptively pruning the dalvik cache.";
PruneDalvikCache(image_isa);
...
}
再次添加Log, 发现verified失败了。而7.0没有VerifyImage.
VerfifyImage是执行patchoat
VerifyImage: /system/bin/patchoat --input-image-location=/system/framework/boot.art --output-image-directory=/data/dalvik-cache/arm --instruction-set=arm --verify
打开patchoat的log,编译make patchoat -j8
最后发现是打不开文件boot.art.rel
Failed to open image relocation file /system/framework/arm64/boot.art.rel
而关闭DEXPREOPT是没有这个文件的。
最后解决方法:
ifeq ($(TARGET_BUILD_VARIANT), user)
WITH_DEXPREOPT := true
DONT_DEXPREOPT_PREBUILTS := false
else
WITH_DEXPREOPT := false
DONT_DEXPREOPT_PREBUILTS := true
endif
#ifdef ART_TARGET_ANDROID
#include "cutils/properties.h"
#endif
bool verified = VerifyImage(image_location, dalvik_cache.c_str(), image_isa, &local_error_msg);
// 以下为加入的代码,如果是usedebug,认为verify通过
#ifdef ART_TARGET_ANDROID
char build_type_value[PROPERTY_VALUE_MAX];
if (property_get("ro.build.type", build_type_value, "") > 0) {
std::string build_type(build_type_value);
if (build_type.compare("userdebug") == 0) {
verified = true;
LOG(INFO) << "dont't verify secondary images if is userdebug";
}
}
#endif