Google Play現在除了APK文件之外,還會交付一套基於雲端的ART Profile配置文件Dex Metadata(.dm)。
這個dm文件是從大數據用戶那裏蒐集整理的APK對應的"熱代碼"文件,在通過GP安裝apk時,會跟據dm文件提前進行優化編譯,而不必等到用戶使用一段時間生成熱代碼後再編譯,可以顯著提升首次啓動速度。
Android P或更高版本系統的設備目前已提供相關支持。
1. 查看.prof、.dm文件
dex metadata(.dm)內容跟.prof一致,其實是一個jar(zip)壓縮包,裏面包含一個primary.prof。
安裝apk時傳.prof不識別,只識別.dm格式。
手動編譯和清除一個APK:profile模式
cmd package compile -m speed-profile -f com.whatsapp
cmd package compile --reset com.whatsapp手動編譯和清除所有APK:profile模式
cmd package compile -m speed-profile -f -a
cmd package compile --reset -a-
查看profile、dex metadata文件
profman --profile-file=xxx.prof --dump-only
profman --profile-file=xxx.dm --dump-only=== profile === ProfileInfo: base.apk [index=0] hot methods: startup methods: 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, post startup methods: 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, classes: 13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,
2. 對比使用.dm安裝前後差異
以whatsapp爲例,我們先運行一會兒whatsapp生成primary.prof文件,然後壓縮成zip改名爲WhatsApp.dm,我們就自己製作了dex metdata。
如下實驗可以看到加上.dm後生成的.art、.odex文件明顯變大了,安裝時就進行了oat編譯提高啓動速度。
before: 279K 2019-10-31 10:18 base.odex
after: 1.7M 2019-10-31 10:14 base.odex
默認安裝
pm install WhatsApp.apk
/data/app/com.whatsapp/oat/x86
-rw-r--r-- 1 system all_a71 40K 2019-10-31 10:18 base.art
-rw-r--r-- 1 system all_a71 279K 2019-10-31 10:18 base.odex
-rw-r--r-- 1 system all_a71 11M 2019-10-31 10:18 base.vdex
/data/misc/profiles/cur/0/com.whatsapp/primary.prof 大小爲0
/data/misc/profiles/ref/com.whatsapp 目錄爲空
帶有.dm安裝
pm install-create : created install session [1918895757]
pm install-write 1918895757 WhatsApp.apk /data/local/tmp/WhatsApp.apk
pm install-write 1918895757 WhatsApp.dm /data/local/tmp/WhatsApp.dm //傳入dm參數
pm install-commit 1918895757
/data/app/com.whatsapp
base.apk base.dm lib oat
/data/app/com.whatsapp/oat/x86
-rw-r--r-- 1 system all_a70 304K 2019-10-31 10:14 base.art
-rw-r--r-- 1 system all_a70 1.7M 2019-10-31 10:14 base.odex
-rw-r--r-- 1 system all_a70 11M 2019-10-31 10:14 base.vdex
/data/misc/profiles/cur/0/com.whatsapp/primary.prof 大小爲0
/data/misc/profiles/ref/com.whatsapp/primary.prof 內容與.dm一致
3. 代碼中如何使用profile文件
-
3.1 prepareAppProfiles()生成primary.prof
1. PMS:installPackageLI(): mArtManagerService.prepareAppProfiles(pkg, resolveUserIds(args.user.getIdentifier())); 2. ArtManagerService:prepareAppProfiles(): String codePath = codePathsProfileNames.keyAt(i);///data/app/com.whatsapp-SA9Yu-bYC8NLh1_b2iKULg==/base.apk String profileName = codePathsProfileNames.valueAt(i);//primary.prof File dexMetadata = DexMetadataHelper.findDexMetadataForFile(new File(codePath));//null or base.dm String dexMetadataPath = dexMetadata == null ? null : dexMetadata.getAbsolutePath();//null or base.dm synchronized (mInstaller) { boolean result = mInstaller.prepareAppProfile(pkg.packageName, user, appId, profileName, codePath, dexMetadataPath); } 3. dexopt.cpp:prepare_app_profile(): //code_path= /data/app/com.whatsapp-l1rTMW3FR7a81Q7SkZoAZQ==/base.apk //cur_profile= /data/misc/profiles/cur/0/com.whatsapp/primary.prof //ref_profile= /data/misc/profiles/ref/com.whatsapp/primary.prof //1.創建primary.prof文件 std::string cur_profile = create_current_profile_path(user_id, package_name, profile_name, /*is_secondary_dex*/ false); if (fs_prepare_file_strict(cur_profile.c_str(), 0600, uid, uid) != 0) { return false; } // 2. 沒有dex metadata不再往下走 // Check if we need to install the profile from the dex metadata. if (dex_metadata == nullptr) { return true; } // 3. 將dex metadata和cur primary.prof合併到ref primary.prof // 第一次安裝apk時,dex metadata==ref profile // 啓動whatsapp自動生成熱文件,執行cmd package compile -m speed-profile後,cur下primary.prof變爲0,轉移到了ref unique_fd ref_profile_fd = open_reference_profile(uid, package_name, profile_name, /*read_write*/ true, /*is_secondary_dex*/ false); unique_fd dex_metadata_fd(TEMP_FAILURE_RETRY( open(dex_metadata->c_str(), O_RDONLY | O_NOFOLLOW))); unique_fd apk_fd(TEMP_FAILURE_RETRY(open(code_path.c_str(), O_RDONLY | O_NOFOLLOW))); pid_t pid = fork(); if (pid == 0) { /* child -- drop privileges before continuing */ gid_t app_shared_gid = multiuser_get_shared_gid(user_id, app_id); drop_capabilities(app_shared_gid); // The copy and update takes ownership over the fds. run_profman_copy_and_update(std::move(dex_metadata_fd), std::move(ref_profile_fd), std::move(apk_fd), code_path); }
-
3.2 dex2oat調用.prof編譯
cur_profile和ref_profile只能傳入一個,兩個都傳會報錯。
第一次安裝apk時,如果有dex metadata文件,那麼ref_profile不爲空,dex2oat就會以ref_profile爲熱代碼文件進行編譯。1. PackageDexOptimizer.java:dexOptPath(): mInstaller.dexopt(path, uid, pkg.packageName, isa, dexoptNeeded, oatDir, dexoptFlags, compilerFilter, pkg.volumeUuid, classLoaderContext, pkg.applicationInfo.seInfo, false /* downgrade*/, pkg.applicationInfo.targetSdkVersion, profileName, dexMetadataPath, getReasonName(compilationReason)); 2. installd/dexopt.cpp:dexopt(): // 打開ref profile Dex2oatFileWrapper reference_profile_fd = maybe_open_reference_profile( pkgname, dex_path, profile_name, profile_guided, is_public, uid, is_secondary_dex); //打開dex_metadata文件 unique_fd dex_metadata_fd; if (dex_metadata_path != nullptr) { dex_metadata_fd.reset(TEMP_FAILURE_RETRY(open(dex_metadata_path, O_RDONLY | O_NOFOLLOW))); if (dex_metadata_fd.get() < 0) { PLOG(ERROR) << "Failed to open dex metadata file " << dex_metadata_path; } } //執行dex2oat編譯 run_dex2oat(input_fd.get(), out_oat_fd.get(), in_vdex_fd.get(), out_vdex_fd.get(), image_fd.get(), dex_path, out_oat_path, swap_fd.get(), instruction_set, compiler_filter, debuggable, boot_complete, background_job_compile, reference_profile_fd.get(), class_loader_context, target_sdk_version, enable_hidden_api_checks, generate_compact_dex, dex_metadata_fd.get(), compilation_reason); 3. dex2oat/dex2oat.cc:Dex2oat(): //加載profile文件 if (dex2oat->UseProfile()) { if (!dex2oat->LoadProfile()) { return dex2oat::ReturnCode::kOther; } } 4. dex2oat.cc:LoadProfile(): //profile_file_fd_指ref profile,第一次安裝dex metadata==ref profile if (profile_file_fd_ != -1) { profile_file = LockedFile::DupOf(profile_file_fd_, "profile", true /* read_only_mode */, &error); } else if (profile_file_ != "") { profile_file = LockedFile::Open(profile_file_.c_str(), O_RDONLY, true, &error); }