Previous Article:

APK Installation process: PackageInstaller

In our last article, we did a simple process analysis of PacakgeInstaller. At the end, we saw that PacakgeInstaller calls PMS’s installStage method to delegate the installation of APK to PMS. In this article, we continue to analyze what PMS does during APK installation from the perspective of the PMS installStage method.

PackageManagerService.installStage

void installStage(ActiveInstallSession activeInstallSession) {
    final Message msg = mHandler.obtainMessage(INIT_COPY);
    final InstallParams params = new InstallParams(activeInstallSession);
    
    msg.obj = params;
    mHandler.sendMessage(msg);
}
Copy the code

The code simply sends a Message with what as INIT_COPY to the Handler in the PMS.

Let’s continue with the Handler:

HandlerParams params = (HandlerParams) msg.obj; if (params ! = null) { params.startCopy(); }Copy the code

It calls the startCopy method in the HandlerPararms class.


HandlerParams.startCopy

final void startCopy() {
    handleStartCopy();
    handleReturnCode();
}
Copy the code

HandlerParams is an abstract class, and handleStartCopy and handleReturnCode are also two abstract methods. This is implemented by the InstallParams class throughout the package installation process.


InstallParams.handleStartCopy

public void handleStartCopy() {
    int ret = PackageManager.INSTALL_SUCCEEDED;

    // If we're already staged, we've firmly committed to an install location
    if (origin.staged) {
        if (origin.file != null) {
            installFlags |= PackageManager.INSTALL_INTERNAL;
        } else {
            throw new IllegalStateException("Invalid stage location");
        }
    }

    //是否安装在内部存储中
    final boolean onInt = (installFlags & PackageManager.INSTALL_INTERNAL) != 0;
    //是否安装文件为Instant App
    final boolean ephemeral = (installFlags & PackageManager.INSTALL_INSTANT_APP) != 0;
    PackageInfoLite pkgLite = null;

    //解析给定的包并返回最小的细节
    //在这个方法里,将会解析AndroidManifest.xml文件,并获得targetSdkVersion、
    //minSdkVersion、versionCode、debuggable等信息
    //与此同时,它还会去计算安装时需要的空间大小
    pkgLite = PackageManagerServiceUtils.getMinimalPackageInfo(mContext,
            origin.resolvedPath, installFlags, packageAbiOverride);

    //如果磁盘可用空间太少,先尝试释放缓存。
    if (!origin.staged && pkgLite.recommendedInstallLocation
            == PackageHelper.RECOMMEND_FAILED_INSUFFICIENT_STORAGE) {
        // TODO: focus freeing disk space on the target device
        final StorageManager storage = StorageManager.from(mContext);
        final long lowThreshold = storage.getStorageLowBytes(
                Environment.getDataDirectory());

        final long sizeBytes = PackageManagerServiceUtils.calculateInstalledSize(
                origin.resolvedPath, packageAbiOverride);
        if (sizeBytes >= 0) {
            try {
                mInstaller.freeCache(null, sizeBytes + lowThreshold, 0, 0);
                pkgLite = PackageManagerServiceUtils.getMinimalPackageInfo(mContext,
                        origin.resolvedPath, installFlags, packageAbiOverride);
            } catch (InstallerException e) {
                Slog.w(TAG, "Failed to free cache", e);
            }
        }

        if (pkgLite.recommendedInstallLocation
                == PackageHelper.RECOMMEND_FAILED_INVALID_URI) {
            pkgLite.recommendedInstallLocation
                    = PackageHelper.RECOMMEND_FAILED_INSUFFICIENT_STORAGE;
        }
    }


    if (ret == PackageManager.INSTALL_SUCCEEDED) {
        int loc = pkgLite.recommendedInstallLocation;
        if (loc == PackageHelper.RECOMMEND_FAILED_INVALID_LOCATION) {
            //无法在指定的安装位置安装新包
            ret = PackageManager.INSTALL_FAILED_INVALID_INSTALL_LOCATION;
        } else if (loc == PackageHelper.RECOMMEND_FAILED_ALREADY_EXISTS) {
            //已安装相同的包
            ret = PackageManager.INSTALL_FAILED_ALREADY_EXISTS;
        } else if (loc == PackageHelper.RECOMMEND_FAILED_INSUFFICIENT_STORAGE) {
            //设备剩余磁盘空间不足
            ret = PackageManager.INSTALL_FAILED_INSUFFICIENT_STORAGE;
        } else if (loc == PackageHelper.RECOMMEND_FAILED_INVALID_APK) {
           //无效的安装文件
            ret = PackageManager.INSTALL_FAILED_INVALID_APK;
        } else if (loc == PackageHelper.RECOMMEND_FAILED_INVALID_URI) {
             //安装文件的URI无效
            ret = PackageManager.INSTALL_FAILED_INVALID_URI;
        } else if (loc == PackageHelper.RECOMMEND_MEDIA_UNAVAILABLE) {
            //由于介质不可用而无法在指定的安装位置安装新软件包
            ret = PackageManager.INSTALL_FAILED_MEDIA_UNAVAILABLE;
        } else {
            //检查安装状态,并根据安装的flag重新确定安装位置
            loc = installLocationPolicy(pkgLite);
            if (loc == PackageHelper.RECOMMEND_FAILED_VERSION_DOWNGRADE) {
                //安装不可降级
                ret = PackageManager.INSTALL_FAILED_VERSION_DOWNGRADE;
            } else if (loc == PackageHelper.RECOMMEND_FAILED_WRONG_INSTALLED_VERSION) {
                //要求的版本号和当前安装的apk的版本号不匹配
                ret = PackageManager.INSTALL_FAILED_WRONG_INSTALLED_VERSION;
            } else if (!onInt) {
                // Override install location with flags
                if (loc == PackageHelper.RECOMMEND_INSTALL_EXTERNAL) {
                    // Set the flag to install on external media.
                    installFlags &= ~PackageManager.INSTALL_INTERNAL;
                } else if (loc == PackageHelper.RECOMMEND_INSTALL_EPHEMERAL) {
                    if (DEBUG_INSTANT) {
                        Slog.v(TAG, "...setting INSTALL_EPHEMERAL install flag");
                    }
                    installFlags |= PackageManager.INSTALL_INSTANT_APP;
                    installFlags &= ~PackageManager.INSTALL_INTERNAL;
                } else {
                    // Make sure the flag for installing on external
                    // media is unset
                    installFlags |= PackageManager.INSTALL_INTERNAL;
                }
            }
        }
    }

    final InstallArgs args = createInstallArgs(this);
    mVerificationCompleted = true;
    mIntegrityVerificationCompleted = true;
    mEnableRollbackCompleted = true;
    mArgs = args;

    if (ret == PackageManager.INSTALL_SUCCEEDED) {
        final int verificationId = mPendingVerificationToken++;

        // 校验APK
        if (!origin.existing) {
            PackageVerificationState verificationState =
                    new PackageVerificationState(this);
            mPendingVerification.append(verificationId, verificationState);

            sendIntegrityVerificationRequest(verificationId, pkgLite, verificationState);
            ret = sendPackageVerificationRequest(
                    verificationId, pkgLite, verificationState);

            // If both verifications are skipped, we should remove the state.
            if (verificationState.areAllVerificationsComplete()) {
                mPendingVerification.remove(verificationId);
            }
        }

          //APK 回滚相关的逻辑
        if ((installFlags & PackageManager.INSTALL_ENABLE_ROLLBACK) != 0) {
            // TODO(ruhler) b/112431924: Don't do this in case of 'move'?
            final int enableRollbackToken = mPendingEnableRollbackToken++;
            Trace.asyncTraceBegin(
                    TRACE_TAG_PACKAGE_MANAGER, "enable_rollback", enableRollbackToken);
            mPendingEnableRollback.append(enableRollbackToken, this);

            Intent enableRollbackIntent = new Intent(Intent.ACTION_PACKAGE_ENABLE_ROLLBACK);
            enableRollbackIntent.putExtra(
                    PackageManagerInternal.EXTRA_ENABLE_ROLLBACK_TOKEN,
                    enableRollbackToken);
            enableRollbackIntent.putExtra(
                    PackageManagerInternal.EXTRA_ENABLE_ROLLBACK_SESSION_ID,
                    mSessionId);
            enableRollbackIntent.setType(PACKAGE_MIME_TYPE);
            enableRollbackIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);

            // Allow the broadcast to be sent before boot complete.
            // This is needed when committing the apk part of a staged
            // session in early boot. The rollback manager registers
            // its receiver early enough during the boot process that
            // it will not miss the broadcast.
            enableRollbackIntent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);

            mContext.sendOrderedBroadcastAsUser(enableRollbackIntent, UserHandle.SYSTEM,
                    android.Manifest.permission.PACKAGE_ROLLBACK_AGENT,
                    new BroadcastReceiver() {
                        @Override
                        public void onReceive(Context context, Intent intent) {
                            // the duration to wait for rollback to be enabled, in millis
                            long rollbackTimeout = DeviceConfig.getLong(
                                    DeviceConfig.NAMESPACE_ROLLBACK,
                                    PROPERTY_ENABLE_ROLLBACK_TIMEOUT_MILLIS,
                                    DEFAULT_ENABLE_ROLLBACK_TIMEOUT_MILLIS);
                            if (rollbackTimeout < 0) {
                                rollbackTimeout = DEFAULT_ENABLE_ROLLBACK_TIMEOUT_MILLIS;
                            }
                            final Message msg = mHandler.obtainMessage(
                                    ENABLE_ROLLBACK_TIMEOUT);
                            msg.arg1 = enableRollbackToken;
                            msg.arg2 = mSessionId;
                            mHandler.sendMessageDelayed(msg, rollbackTimeout);
                        }
                    }, null, 0, null, null);

            mEnableRollbackCompleted = false;
        }
    }

    mRet = ret;
}
Copy the code

HandleStartCopy does the following:

  • APK brief information is parsed to meet the initialization requirements and information verification requirements of the installation
  • Further determine where APK is installed and whether the disk has sufficient installation space, if not, try to free the cache
  • Verify the APK installation information

HandlerParams.handleReturnCode

void handleReturnCode() { if (mVerificationCompleted && mEnableRollbackCompleted) { if ((installFlags & PackageManager.INSTALL_DRY_RUN) ! = 0) { String packageName = ""; try { PackageLite packageInfo = new PackageParser().parsePackageLite(origin.file, 0); packageName = packageInfo.packageName; } catch (PackageParserException e) { Slog.e(TAG, "Can't parse package at " + origin.file.getAbsolutePath(), e); } try { observer.onPackageInstalled(packageName, mRet, "Dry run", new Bundle()); } catch (RemoteException e) { Slog.i(TAG, "Observer no longer exists."); } return; } if (mRet == packagemanager.install_succeeded) {// The replication operation is not actually performed here, Just point codeFile and //resourceFile in InstallArgs to the file object in InstallParam mRet = margs.copyapk (); } processPendingInstall(mArgs, mRet); }}Copy the code

processPendingInstall

private void processPendingInstall(final InstallArgs args, final int currentStatus) { if (args.mMultiPackageInstallParams ! = null) { args.mMultiPackageInstallParams.tryProcessInstallRequest(args, currentStatus); } else {PackageInstalledInfo is used to save installation results, success or failure, What is failure reason PackageInstalledInfo res = createPackageInstalledInfo (currentStatus); processInstallRequestsAsync( res.returnCode == PackageManager.INSTALL_SUCCEEDED, Collections.singletonList(new InstallRequest(args, res))); }}Copy the code

processInstallRequestsAsync

Private void processInstallRequestsAsync (Boolean success, List < InstallRequest > installRequests) {/ / the Handler is executed thread installation operation, Mhandler.post (() -> {if (success) {for (InstallRequest request: // Check if returnCode is INSTALL_SUCCEEDED, Not remove replication APK and delete the related directories and files request. The args. DoPreInstall (request. InstallResult. ReturnCode); } synchronized (mInstallLock) {// installPackagesTracedLI(installRequests); } for (InstallRequest request : InstallRequests) {/ / as request and doPreInstall method. The args. DoPostInstall (request. InstallResult. ReturnCode, request.installResult.uid); } } for (InstallRequest request : // installRequests) {// installRequests, Such as apk backup, roll back, send installation related radio restoreAndPostInstall (request. The args. User. GetIdentifier (), request. InstallResult, new PostInstallData(request.args, request.installResult, null)); }}); }Copy the code

The installPackagesTracedLI method simply calls installPackagesLI. We’ll focus on the installPackagesLI method, which is responsible for parsing the content of Apk and is the core method in the installation process.


installPackagesLI

private void installPackagesLI(List<InstallRequest> requests) { final Map<String, ScanResult> preparedScans = new ArrayMap<>(requests.size()); final Map<String, InstallArgs> installArgs = new ArrayMap<>(requests.size()); final Map<String, PackageInstalledInfo> installResults = new ArrayMap<>(requests.size()); final Map<String, PrepareResult> prepareResults = new ArrayMap<>(requests.size()); final Map<String, VersionInfo> versionInfos = new ArrayMap<>(requests.size()); final Map<String, PackageSetting> lastStaticSharedLibSettings = new ArrayMap<>(requests.size()); final Map<String, Boolean> createdAppId = new ArrayMap<>(requests.size()); boolean success = false; try { for (InstallRequest request : requests) { final PrepareResult prepareResult; // Androidmanifest.xml (apk, androidmanifest.xml); // Androidmanifest.xml (apk, androidmanifest.xml); For example, all // activities, services, permissions and other information in APK, // Verify the validity of dex file through dM-verity. // Parse the APK signature information. // Parse the Android resource index table resources.arsc file prepareResult = preparePackageLI(request.args, request.installResult); } catch (PrepareFailure prepareFailure) { request.installResult.setError(prepareFailure.error, prepareFailure.getMessage()); request.installResult.origPackage = prepareFailure.conflictingPackage; request.installResult.origPermission = prepareFailure.conflictingPermission; return; } finally { Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER); } request.installResult.setReturnCode(PackageManager.INSTALL_SUCCEEDED); request.installResult.installerPackageName = request.args.installerPackageName; final String packageName = prepareResult.packageToScan.packageName; // Save the APK prepareResults. Put (packageName, prepareResult); // Save installresults.put (packageName, request.installResult); // Save the request parameter installArgs. Put (packageName, request.args); Final List<ScanResult> scanResults = scanPackageTracedLI( prepareResult.packageToScan, prepareResult.parseFlags, prepareResult.scanFlags, System.currentTimeMillis(), request.args.user); For (ScanResult result: scanResults) {// save APK ScanResult if (null! = preparedScans.put(result.pkgSetting.pkg.packageName, result)) { request.installResult.setError( PackageManager.INSTALL_FAILED_DUPLICATE_PACKAGE, "Duplicate package " + result.pkgSetting.pkg.packageName + " in multi-package install request."); return; } / / to the system to register a appId createdAppId. Put (packageName, optimisticallyRegisterAppId (result); / / app version information management versionInfos. Put (result. PkgSetting. PKG. PackageName, getSettingsVersionForPackage (result. PkgSetting. PKG)); if (result.staticSharedLibraryInfo ! = null) { final PackageSetting sharedLibLatestVersionSetting = getSharedLibLatestVersionSetting(result); if (sharedLibLatestVersionSetting ! = null) {/ / lib lastStaticSharedLibSettings library information management. The put (result. PkgSetting. PKG. PackageName, sharedLibLatestVersionSetting); } } } } catch (PackageManagerException e) { request.installResult.setError("Scanning Failed.", e); return; }} // Check whether the APK signature is inconsistent with the installed APK signature. And whether it is necessary to remove the ReconcileRequest ReconcileRequest = New ReconcileRequest(preparedScans, installArgs, installResults, prepareResults, mSharedLibraries, Collections.unmodifiableMap(mPackages), versionInfos, lastStaticSharedLibSettings); CommitRequest commitRequest = null; synchronized (mPackages) { Map<String, ReconciledPackage> reconciledPackages; Can try {/ / check apk installation reconciledPackages = reconcilePackagesLocked (reconcileRequest, mSettings. MKeySetManagerService); } catch (ReconcileFailure e) { for (InstallRequest request : requests) { request.installResult.setError("Reconciliation failed..." , e); } return; } finally { Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER); } try { Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "commitPackages"); commitRequest = new CommitRequest(reconciledPackages, sUserManager.getUserIds()); // If you need to use it for an override installation, delete the installed app first and update the PackageSetting message commitPackagesLocked(commitRequest); success = true; } finally { Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER); Create an App Data directory for the installed app. If the directory already exists, verify the directory. Installd is responsible for this operation. Preparing the application configuration file for the new code path needs to be done before calling dexopt //3. Check whether the dex file needs to be optimized and execute executePostCommitSteps(commitRequest) if necessary. } finally { if (! success) { for (ScanResult result : preparedScans.values()) { if (createdAppId.getOrDefault(result.request.pkg.packageName, false)) { cleanUpAppIdCreation(result); } } // TODO(patb): create a more descriptive reason than unknown in future release // mark all non-failure installs as UNKNOWN so we do not  treat them as success for (InstallRequest request : requests) { if (request.installResult.returnCode == PackageManager.INSTALL_SUCCEEDED) { request.installResult.returnCode  = PackageManager.INSTALL_UNKNOWN; } } } for (PrepareResult result : prepareResults.values()) { if (result.freezer ! = null) { result.freezer.close(); } } Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER); }}Copy the code

InstallPackagesLI Installs one or more packages atomically. There are four stages to this operation:

  • Preparation phase: Analyze the current installation information, parse the package’s Androidmanifest.xml file and perform initial validation.
  • Scanning phase: The contents of the package are scanned and further parsed based on the context gathered during the preparation phase.
  • Verification phase: Verifies software package information, such as signatures, to ensure successful installation.
  • Commit phase: Submit the information of all scanned packages and update the system status. This is the only place in the installation process where the system state can be modified, and all predictable errors must be checked prior to this stage.

Some readers will ask, the above process is to analyze the information in the APK file, where is the installation process?

The android installation mainly generates the following files:

  1. The doWriteInternal method of PackageInstallerSession copies the APK file to /data/app/ VMDL ${sessionId}.tmp/base.apk.

  2. ExtractNativeLibraries is called in the PackageInstallerSession commitNonStagedLocked methods, Unzip the Native Lib file in the APK file to /data/app/ VMDL ${sessionId}. TMP/Lib.

  3. PreparePackageLI will rename the installation directory from VMDL ${sessionId}. TMP to ${packageName}-${base64. encodeToString(random 16-byte array)}.

  4. The commit executePostCommitSteps method calls the performDexOpt method in PackageDexOptimizer, In/data/app / ${packageName} – ${Base64. EncodeToString (random 16 byte array)} / oat directory to generate the base art, base. Odex, base. Vdex three files.


Finally, we use a flow chart to summarize the Apk installation process: