使用配置文件的apk安裝編譯分析

  衆所周知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);
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章