衆所周知JVM執行的是.class字節碼,而Android的Dalvik或ART是不能直接運行.class文件,運行的是編譯生成的dex文件,並且不同版本的Android在運行時對dex的編譯策略也存在着變化。在Android4.4之前Android虛擬機是Dalvik,在Android2.2到Android4.4時期,其執行的時候採用的是JIT編譯,當App運行時,每當遇到一個新類,JIT編譯器就會對這個類進行即時編譯,經過編譯後的代碼,會被優化成相當精簡的原生型指令碼,這樣在下次執行到相同邏輯的時候,速度就會更快;在Android4.4的時候Google推出了新的虛擬機運行時環境ART,在Android7.0之前,其採用的是AOT編譯,應用在安裝的時候會啓動 dex2oat,把 dex預編譯成 ELF 文件,每次運行程序的時候不用重新編譯。以上兩種方式存在着缺陷很明顯:JIT每次程序啓動後都要重新編譯,在運行時也會額外對性能消耗;AOT在apk安裝時要消耗大量時間,且編譯優化後的文件又會額外佔用磁盤空間。所以在Android7.0時採用AOT/JIT 混合編譯的策略:apk安裝時dex文件不會被編譯,app運行時dex文件會解釋執行,熱點函數被JIT編譯後緩存並生成profile文件,在手機空閒或者充電時進行AOT編譯。
本文將基於Android10的源碼分析Apk安裝過程中,對dex的編譯處理;並給出一種基於熱代碼配置文件的安裝方案。
dex2oat
dex編譯優化成爲運行時執行的oat的核心是dex2oat,所以首先介紹一下dex2oat。dex2oat是ART中把dex文件編譯成oat的工具,位置在手機的/system/bin/dex2oat,源碼在art/dex2oat/Dex2oat.cc。執行dex2oat編譯時的編譯選項中有compile-filter,從 Android O 開始,有四個官方支持的過濾器:
verify:只運行 DEX 代碼驗證。
quicken:運行 DEX 代碼驗證,並優化一些 DEX 指令,以獲得更好的解譯器性能。
speed:運行 DEX 代碼驗證,並對所有方法進行 AOT 編譯。
speed-profile:運行 DEX 代碼驗證,並對配置文件中列出的方法進行 AOT 編譯。
apk安裝
接下來我們分析Android安裝apk時的流程,apk安裝可以分爲四步:將APK的信息通過IO流的形式寫入到PackageInstaller.Session中;調用PackageInstaller.Session的commit方法,將APK的信息交由PKMS處理;拷貝Apk文件到/data/app等目錄;執行安裝。
我們跳過前面的步驟,直接分析真正安裝的代碼。複製完Apk後調用HandlerParams,執行PackageManagerService中的安裝方法。
//PackageManagerService.java
private void processPendingInstall(final InstallArgs args, final int currentStatus) {
if (args.mMultiPackageInstallParams != null) {
args.mMultiPackageInstallParams.tryProcessInstallRequest(args, currentStatus);
} else {
//普通安裝
PackageInstalledInfo res = createPackageInstalledInfo(currentStatus);
processInstallRequestsAsync(res.returnCode == PackageManager.INSTALL_SUCCEEDED, Collections.singletonList(new InstallRequest(args, res)));
}
}
//processInstallRequestsAsync中會調用到installPackagesLI
private void installPackagesLI(List<InstallRequest> requests) {
//分析安裝包並對其進行初始驗證
final PrepareResult prepareResult = preparePackageLI(request.args, request.installResult);
final List<ScanResult> scanResults = scanPackageTracedLI(
prepareResult.packageToScan, prepareResult.parseFlags,
prepareResult.scanFlags, System.currentTimeMillis(),
request.args.user);
ReconcileRequest reconcileRequest = new ReconcileRequest(preparedScans, installArgs,
installResults,
prepareResults,
mSharedLibraries,
Collections.unmodifiableMap(mPackages), versionInfos,
lastStaticSharedLibSettings);
commitRequest = new CommitRequest(reconciledPackages, sUserManager.getUserIds());
commitPackagesLocked(commitRequest);
//執行完成Apk安裝
executePostCommitSteps(commitRequest);
}
private void executePostCommitSteps(CommitRequest commitRequest) {
for (ReconciledPackage reconciledPkg : commitRequest.reconciledPackages.values()) {
//根據解析的包準備完數據進行安裝
prepareAppDataAfterInstallLIF(pkg);
//如果需要替換安裝,則需要清楚原有的APP數據
if (reconciledPkg.prepareResult.clearCodeCache) {
clearAppDataLIF(pkg, UserHandle.USER_ALL, FLAG_STORAGE_DE | FLAG_STORAGE_CE | FLAG_STORAGE_EXTERNAL | Installer.FLAG_CLEAR_CODE_CACHE_ONLY);
}
//如果需要更新,通知dex有更新
if (reconciledPkg.prepareResult.replace) {
mDexManager.notifyPackageUpdated(pkg.packageName, pkg.baseCodePath, pkg.splitCodePaths);
}
//爲新的代碼路徑準備應用程序配置文件
mArtManagerService.prepareAppProfiles(pkg, resolveUserIds(reconciledPkg.installArgs.user.getIdentifier()),
final boolean performDexopt =
(!instantApp || Global.getInt(mContext.getContentResolver(),
Global.INSTANT_APP_DEXOPT_ENABLED, 0) != 0)
&& ((pkg.applicationInfo.flags & ApplicationInfo.FLAG_DEBUGGABLE) == 0);
if (performDexopt) {
DexoptOptions dexoptOptions = new DexoptOptions(packageName,
REASON_INSTALL,
DexoptOptions.DEXOPT_BOOT_COMPLETE
| DexoptOptions.DEXOPT_INSTALL_WITH_DEX_METADATA_FILE);
//執行dex優化
mPackageDexOptimizer.performDexOpt(pkg, null /* instructionSets */, getOrCreateCompilerPackageStats(pkg), mDexManager.getPackageUseInfoOrDefault(packageName),
dexoptOptions);
}
BackgroundDexOptService.notifyPackageChanged(packageName);
}
}
}
使用profile安裝apk
Android 7.0 引入了 Profile Guided Optimization(PGO),允許 Android 運行時收集熱門代碼的配置,並集中編譯提升應用性能,可以減少完全編譯的應用耗時和磁盤空間佔用,但這種模式需要app運行一段時間後才能感受到性能的優化。在Improving app performance with ART optimizing profiles in the cloud 文章中,Google通過收集衆多用戶的熱門代碼,構建核心聚合代碼配置文件,Google Play就會將代碼配置和Apk一同安裝,在安裝時實現有效的配置來引導優化,從而改善冷啓動時間以及穩定性能狀態。
Google Play中的配置文件.dm的內容和熱帶碼的primary.prof中的內容是一致的。使用profman --profile-file=/data/misc/profiles/cur/0/package name/primary.prof --dump-only
可以查看prof中的內容:
也可以使用cmd package dump-profiles package name
,dump爲一個txt文件到/data/misc/profman。
Google Play安裝Apk時實際上使用的是pm install命令,我們可以用命令模擬一遍這種帶.dm的安裝過程:
1.app安裝運行一段時間之後,得到/data/misc/profiles/cur/0/package name/primary.prof,將primary.prof壓縮爲zip改爲dm格式;
2.執行安裝命令:
pm install-create
pm install-write SESSION_ID AppName.apk /data/local/tmp/AppName.apk
//比正常安裝多執行下面一行
pm install-write SESSION_ID AppName.dm /data/local/tmp/AppName.dm
pm install-commit SESSION_ID
下面通過源碼分析安裝Apk時profile的處理,上一節中已經分析到了PackageMangerService的executePostCommitSteps方法中爲新的代碼路徑準備應用程序配置文件,下面進一步分析準備配置文件的代碼。
//PackageManagerService.java
// Prepare the application profiles for the new code paths.
// This needs to be done before invoking dexopt so that any install-time profile
// can be used for optimizations.
mArtManagerService.prepareAppProfiles(pkg,resolveUserIds(reconciledPkg.installArgs.user.getIdentifier()),
/* updateReferenceProfileContent= */ true);
//ArtManagerService.jav
//給app準備profile,如果有dex metadata拷貝到reference profile
public void prepareAppProfiles(PackageParser.Package pkg, @UserIdInt int user,
boolean updateReferenceProfileContent) {
ArrayMap<String, String> codePathsProfileNames = getPackageProfileNames(pkg);
for (int i = codePathsProfileNames.size() - 1; i >= 0; i--) {
String codePath = codePathsProfileNames.keyAt(i);
String profileName = codePathsProfileNames.valueAt(i);
String dexMetadataPath = null;
if (updateReferenceProfileContent) {
File dexMetadata = DexMetadataHelper.findDexMetadataForFile(new File(codePath));
dexMetadataPath = dexMetadata == null ? null : dexMetadata.getAbsolutePath();
}
synchronized (mInstaller) {
boolean result = mInstaller.prepareAppProfile(pkg.packageName, user, appId,
profileName, codePath, dexMetadataPath);
}
}
//Installer.java
public boolean prepareAppProfile(String pkg, @UserIdInt int userId, @AppIdInt int appId,
String profileName, String codePath, String dexMetadataPath) throws InstallerException {
return mInstalld.prepareAppProfile(pkg, userId, appId, profileName, codePath,dexMetadataPath);
}
//dexopt.cpp
bool prepare_app_profile(const std::string& package_name,
userid_t user_id,
appid_t app_id,
const std::string& profile_name,
const std::string& code_path,
const std::unique_ptr<std::string>& dex_metadata) {
// 將dex metdata合併到reference profile.
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)));
if (apk_fd < 0) {
PLOG(ERROR) << "Could not open code path " << code_path;
return false;
}
RunProfman args;
args.SetupCopyAndUpdate(std::move(dex_metadata_fd),
std::move(ref_profile_fd),
std::move(apk_fd),
code_path);
// The copy and update takes ownership over the fds.
args.Exec();
}
return true;
}
分析完配置文件準備的執行過程,回到PackageManagerService中的安裝流程,接下來PackageDexOptimizer調用performDexOpt執行編譯優化dex。
//PackageDexOptimizer.java
private int performDexOptLI(PackageParser.Package pkg, String[] targetInstructionSets, CompilerStats.PackageStats packageStats,
PackageDexUsage.PackageUseInfo packageUseInfo, DexoptOptions options) {
String dexMetadataPath = null;
if (options.isDexoptInstallWithDexMetadata()) {
//尋找是否有dex matedata文件
File dexMetadataFile = DexMetadataHelper.findDexMetadataForFile(new File(path));
dexMetadataPath = dexMetadataFile == null ? null : dexMetadataFile.getAbsolutePath();
}
//dexOptPath方法中調用Installer的dexopt
int newResult = dexOptPath(pkg, path, dexCodeIsa, compilerFilter,
profileUpdated, classLoaderContexts[i], dexoptFlags, sharedGid,
packageStats, options.isDowngrade(), profileName, dexMetadataPath,
options.getCompilationReason());
if ((result != DEX_OPT_FAILED) && (newResult != DEX_OPT_SKIPPED)) {
result = newResult;
}
}
private int dexOptPath(PackageParser.Package pkg, String path, String isa,
String compilerFilter, boolean profileUpdated, String classLoaderContext,
int dexoptFlags, int uid, CompilerStats.PackageStats packageStats, boolean downgrade,
String profileName, String dexMetadataPath, int compilationReason) {
//調用DexFile.getDexOptNeeded判斷是否需要dex2oat編譯,沒有oat、oat文件不能匹配boot image、oat文件不能匹配compiler filter等情況
int dexoptNeeded = getDexoptNeeded(path, isa, compilerFilter, classLoaderContext, profileUpdated, downgrade);
//Installer中dexopt調用dexopt.cpp的dexopt
mInstaller.dexopt(path, uid, pkg.packageName, isa, dexoptNeeded, oatDir, dexoptFlags,
compilerFilter, pkg.volumeUuid, classLoaderContext, pkg.applicationInfo.seInfo,
false /* downgrade*/, pkg.applicationInfo.targetSdkVersion,
profileName, dexMetadataPath, getAugmentedReasonName(compilationReason, dexMetadataPath != null));
}
//dexopt.cpp
int dexopt(const char* dex_path, uid_t uid, const char* pkgname, const char* instruction_set,
int dexopt_needed, const char* oat_dir, int dexopt_flags, const char* compiler_filter,
const char* volume_uuid, const char* class_loader_context, const char* se_info,
bool downgrade, int target_sdk_version, const char* profile_name,
const char* dex_metadata_path, const char* compilation_reason, std::string* error_msg) {
//打開dex輸入文件
unique_fd input_fd(open(dex_path, O_RDONLY, 0));
std::vector<unique_fd> context_input_fds;
if (open_dex_paths(context_dex_paths, &context_input_fds, error_msg) != 0) {
LOG(ERROR) << *error_msg;
return -1;
}
//創建oat輸出文件
char out_oat_path[PKG_PATH_MAX];
//打開reference profile
Dex2oatFileWrapper reference_profile_fd = maybe_open_reference_profile(
pkgname, dex_path, profile_name, profile_guided, is_public, uid, is_secondary_dex);
//打開dex matedata文件
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)));
}
//拼裝命令參數執行dex2oat
RunDex2Oat runner(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,
join_fds(context_input_fds),
target_sdk_version,
enable_hidden_api_checks,
generate_compact_dex,
dex_metadata_fd.get(),
compilation_reason);
runner.Exec(DexoptReturnCodes::kDex2oatExec);
}