Android RollBack機制實現原理剖析

thinkinwm發表於2020-12-08

轉載 https://blog.csdn.net/ChaoY1116/article/details/109143954

 

功能介紹:

在Android 10.0中,Google新增加了個功能。
如果使用者對新升級的APP不滿意,可以通過“回到過去”,回滾到舊版。
當然,如果新安裝的apk出現了各種問題無法使用,也可以進行回滾的操作。
這樣的確可以極大的提升使用者的體驗,但是因為這塊的邏輯較複雜,我們以module_crash_rollback_test為例,來看下具體的實現邏輯。


程式碼路徑如下:

./base/services/core/java/com/android/server/rollback
./base/core/java/android/content/rollback

工作原理:

如何驗證這個功能是否邏輯生效,我們可以使用這個方法:

1. adb install -r -d --enable-rollback --staged ***.apk
2. adb reboot
3. adb shell dumpsys rollback
4. adb root
5. adb shell am crash ***  (10 times)
6. adb reboot
7. adb wait-for-devices 1 mins
8. adb shell dumpsys rollback

我們即可從RollBack的狀態,檢查rollback機制是否被啟用以及使用。
dumpsys的code在程式碼中對應如下:

程式碼路徑為:frameworks/base/services/core/java/com/android/server/rollback/Rollback.java

    void dump(IndentingPrintWriter ipw) {
        synchronized (mLock) {
            ipw.println(info.getRollbackId() + ":");
            ipw.increaseIndent();
            ipw.println("-state: " + getStateAsString());
            ipw.println("-timestamp: " + getTimestamp());
            if (getStagedSessionId() != -1) {
                ipw.println("-stagedSessionId: " + getStagedSessionId());
            }
            ipw.println("-packages:");
            ipw.increaseIndent();
            for (PackageRollbackInfo pkg : info.getPackages()) {
                ipw.println(pkg.getPackageName()
                        + " " + pkg.getVersionRolledBackFrom().getLongVersionCode()
                        + " -> " + pkg.getVersionRolledBackTo().getLongVersionCode());
            }
            ipw.decreaseIndent();
            if (isCommitted()) {
                ipw.println("-causePackages:");
                ipw.increaseIndent();
                for (VersionedPackage cPkg : info.getCausePackages()) {
                    ipw.println(cPkg.getPackageName() + " " + cPkg.getLongVersionCode());
                }
                ipw.decreaseIndent();
                ipw.println("-committedSessionId: " + info.getCommittedSessionId());
            }
            if (mExtensionVersions.size() > 0) {
                ipw.println("-extensionVersions:");
                ipw.increaseIndent();
                ipw.println(mExtensionVersions.toString());
                ipw.decreaseIndent();
            }
            ipw.decreaseIndent();
        }
    }

從dumpsys中,我們就可以看到rollback的當前執行狀態。

    String getStateAsString() {
        synchronized (mLock) {
            return rollbackStateToString(mState);
        }
    }

邏輯很簡單,即為將RollBack中的mState變數值置為String並且打出。
變數定義如下:

    /**
     * The current state of the rollback.
     * ENABLING, AVAILABLE, or COMMITTED.
     */
    @GuardedBy("mLock")
    private @RollbackState int mState;

會有四個狀態值,來對應當前的mState.

    @IntDef(prefix = { "ROLLBACK_STATE_" }, value = {
            ROLLBACK_STATE_ENABLING,
            ROLLBACK_STATE_AVAILABLE,
            ROLLBACK_STATE_COMMITTED,
            ROLLBACK_STATE_DELETED
    })

那麼在執行module_crash_rollback_test的時候,我們的邏輯是怎麼生效的呢?
首先是在rollbackmanagerservice中:

/**
 * Service that manages APK level rollbacks. Publishes
 * Context.ROLLBACK_SERVICE.
 *
 * @hide
 */
public final class RollbackManagerService extends SystemService {

    private RollbackManagerServiceImpl mService;

    public RollbackManagerService(Context context) {
        super(context);
    }

    @Override
    public void onStart() {
        mService = new RollbackManagerServiceImpl(getContext());
        publishBinderService(Context.ROLLBACK_SERVICE, mService);
    }

    @Override
    public void onUnlockUser(int user) {
        mService.onUnlockUser(user);
    }

    @Override
    public void onBootPhase(int phase) {
        if (phase == SystemService.PHASE_BOOT_COMPLETED) {
            mService.onBootCompleted();
        }
    }

可以看到的是,在PHASE_BOOT_COMPLETED時,將會呼叫onBootCompleted的函式。
如果看過之前的文章的同學,可能也明白了這個函式是在系統啟動完成後,針對全域性發出的通知。

    @AnyThread
    void onBootCompleted() {
        DeviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_ROLLBACK_BOOT,
                mExecutor, properties -> updateRollbackLifetimeDurationInMillis());

        getHandler().post(() -> {
            updateRollbackLifetimeDurationInMillis();
            runExpiration();

            // Check to see if any rollback-enabled staged sessions or staged
            // rollback sessions been applied.
            List<Rollback> enabling = new ArrayList<>();
            List<Rollback> restoreInProgress = new ArrayList<>();
            Set<String> apexPackageNames = new HashSet<>();
            synchronized (mLock) {
                Iterator<Rollback> iter = mRollbacks.iterator();
                while (iter.hasNext()) {
                    Rollback rollback = iter.next();
                    if (!rollback.isStaged()) {
                        // We only care about staged rollbacks here
                        continue;
                    }

                    PackageInstaller.SessionInfo session = mContext.getPackageManager()
                            .getPackageInstaller().getSessionInfo(rollback.getStagedSessionId());
                    if (session == null || session.isStagedSessionFailed()) {
                        iter.remove();
                        rollback.delete(mAppDataRollbackHelper);
                        continue;
                    }

                    if (session.isStagedSessionApplied()) {
                        if (rollback.isEnabling()) {
                            enabling.add(rollback);
                        } else if (rollback.isRestoreUserDataInProgress()) {
                            restoreInProgress.add(rollback);
                        }
                    }
                    apexPackageNames.addAll(rollback.getApexPackageNames());
                }
            }

            for (Rollback rollback : enabling) {
                makeRollbackAvailable(rollback);
            }

            for (Rollback rollback : restoreInProgress) {
                rollback.setRestoreUserDataInProgress(false);
            }

            for (String apexPackageName : apexPackageNames) {
                // We will not recieve notifications when an apex is updated,
                // so check now in case any rollbacks ought to be expired. The
                // onPackagedReplace function is safe to call if the package
                // hasn't actually been updated.
                onPackageReplaced(apexPackageName);
            }

            synchronized (mLock) {
                mOrphanedApkSessionIds.clear();
            }

            mPackageHealthObserver.onBootCompletedAsync();
        });
    }

這段主要說的是在系統啟動過程中,我們將會對rollback的功能開啟,各個session的狀態,以及實際的packageName進行replaced,restore userdata的操作。這邊分析一下onPackageReplaced函式:

    /**
     * Called when a package has been replaced with a different version.
     * Removes all backups for the package not matching the currently
     * installed package version.
     */
    @WorkerThread
    private void onPackageReplaced(String packageName) {
        // TODO: Could this end up incorrectly deleting a rollback for a
        // package that is about to be installed?
        long installedVersion = getInstalledPackageVersion(packageName);

        synchronized (mLock) {
            Iterator<Rollback> iter = mRollbacks.iterator();
            while (iter.hasNext()) {
                Rollback rollback = iter.next();
                // TODO: Should we remove rollbacks in the ENABLING state here?
                if ((rollback.isEnabling() || rollback.isAvailable())
                        && rollback.includesPackageWithDifferentVersion(packageName,
                        installedVersion)) {
                    iter.remove();
                    rollback.delete(mAppDataRollbackHelper);
                }
            }
        }
    }

當包被其他版本替換時呼叫時,我們會通過installedVersion來儲存APK的版本號,
並且在下面將會刪除與當前安裝的包版本不匹配的包的所有備份。
在操作完,將會執行onBootCompletedAsync函式,而這邊是進行的通知。

    /** Verifies the rollback state after a reboot and schedules polling for sometime after reboot
     * to check for native crashes and mitigate them if needed.
     */
    public void onBootCompletedAsync() {
        mHandler.post(()->onBootCompleted());
    }

那麼這個onBootCompleted在做什麼工作呢?

    private void onBootCompleted() {
        RollbackManager rollbackManager = mContext.getSystemService(RollbackManager.class);
        if (!rollbackManager.getAvailableRollbacks().isEmpty()) {
            // TODO(gavincorkery): Call into Package Watchdog from outside the observer
            PackageWatchdog.getInstance(mContext).scheduleCheckAndMitigateNativeCrashes();
        }

        SparseArray<String> rollbackIds = popLastStagedRollbackIds();
        for (int i = 0; i < rollbackIds.size(); i++) {
            WatchdogRollbackLogger.logRollbackStatusOnBoot(mContext,
                    rollbackIds.keyAt(i), rollbackIds.valueAt(i),
                    rollbackManager.getRecentlyCommittedRollbacks());
        }
    }

這邊的重頭戲來了,scheduleCheckAndMitigateNativeCrashes看上去和我們要驗證的module_crash_rollback_test非常的相似。

    /**
     * Since this method can eventually trigger a rollback, it should be called
     * only once boot has completed {@code onBootCompleted} and not earlier, because the install
     * session must be entirely completed before we try to rollback.
     */
    public void scheduleCheckAndMitigateNativeCrashes() {
        Slog.i(TAG, "Scheduling " + mNumberOfNativeCrashPollsRemaining + " polls to check "
                + "and mitigate native crashes");
        mShortTaskHandler.post(()->checkAndMitigateNativeCrashes());
    }

只是列印了log,就來執行check的操作。

    /**
     * This method should be only called on mShortTaskHandler, since it modifies
     * {@link #mNumberOfNativeCrashPollsRemaining}.
     */
    private void checkAndMitigateNativeCrashes() {
        mNumberOfNativeCrashPollsRemaining--;
        // Check if native watchdog reported a crash
        if ("1".equals(SystemProperties.get("sys.init.updatable_crashing"))) {
            // We rollback everything available when crash is unattributable
            onPackageFailure(Collections.EMPTY_LIST, FAILURE_REASON_NATIVE_CRASH);
            // we stop polling after an attempt to execute rollback, regardless of whether the
            // attempt succeeds or not
        } else {
            if (mNumberOfNativeCrashPollsRemaining > 0) {
                mShortTaskHandler.postDelayed(() -> checkAndMitigateNativeCrashes(),
                        NATIVE_CRASH_POLLING_INTERVAL_MILLIS);
            }
        }
    }

這邊就非常奇怪了,為什麼會有sys.init.updatable_crashing這個systemproperties呢?
這個properties是定義在什麼地方?
程式碼路徑: system/core/init/service.cpp

    // If we crash > 4 times in 4 minutes or before boot_completed,
    // reboot into bootloader or set crashing property
    boot_clock::time_point now = boot_clock::now();
    if (((flags_ & SVC_CRITICAL) || is_process_updatable) && !(flags_ & SVC_RESTART)) {
        bool boot_completed = android::base::GetBoolProperty("sys.boot_completed", false);
        if (now < time_crashed_ + 4min || !boot_completed) {
            if (++crash_count_ > 4) {
                if (flags_ & SVC_CRITICAL) {
                    // Aborts into bootloader
                    LOG(FATAL) << "critical process '" << name_ << "' exited 4 times "
                               << (boot_completed ? "in 4 minutes" : "before boot completed");
                } else {
                    LOG(ERROR) << "updatable process '" << name_ << "' exited 4 times "
                               << (boot_completed ? "in 4 minutes" : "before boot completed");
                    // Notifies update_verifier and apexd
                    SetProperty("sys.init.updatable_crashing_process_name", name_);
                    SetProperty("sys.init.updatable_crashing", "1");
                }
            }
        } else {
            time_crashed_ = now;
            crash_count_ = 1;
        }
    }

這裡其實是對Crash的一個檢查,如果在開機以後,規定時間內有四次以上的crash,然後就會觸發這個properties的定義。
同時會記錄當前程式的名字:sys.init.updatable_crashing_process_name。
但是在正常的過程中,這個應該不會出現。
但是在我們之前測試步驟中,當我們連續crash apk多次,那麼重啟後是否就會啟用rollback呢?
應該是的,我們繼續看看狀態的改變過程。

    /**
     * Called when a process fails due to a crash, ANR or explicit health check.
     *
     * <p>For each package contained in the process, one registered observer with the least user
     * impact will be notified for mitigation.
     *
     * <p>This method could be called frequently if there is a severe problem on the device.
     */
    public void onPackageFailure(List<VersionedPackage> packages,
            @FailureReasons int failureReason) {
        if (packages == null) {
            Slog.w(TAG, "Could not resolve a list of failing packages");
            return;
        }
        mLongTaskHandler.post(() -> {
            synchronized (mLock) {
                if (mAllObservers.isEmpty()) {
                    return;
                }
                boolean requiresImmediateAction = (failureReason == FAILURE_REASON_NATIVE_CRASH
                        || failureReason == FAILURE_REASON_EXPLICIT_HEALTH_CHECK);
                if (requiresImmediateAction) {
                    handleFailureImmediately(packages, failureReason);
                } else {
                    for (int pIndex = 0; pIndex < packages.size(); pIndex++) {
                        VersionedPackage versionedPackage = packages.get(pIndex);
                        // Observer that will receive failure for versionedPackage
                        PackageHealthObserver currentObserverToNotify = null;
                        int currentObserverImpact = Integer.MAX_VALUE;

                        // Find observer with least user impact
                        for (int oIndex = 0; oIndex < mAllObservers.size(); oIndex++) {
                            ObserverInternal observer = mAllObservers.valueAt(oIndex);
                            PackageHealthObserver registeredObserver = observer.registeredObserver;
                            if (registeredObserver != null
                                    && observer.onPackageFailureLocked(
                                    versionedPackage.getPackageName())) {
                                int impact = registeredObserver.onHealthCheckFailed(
                                        versionedPackage, failureReason);
                                if (impact != PackageHealthObserverImpact.USER_IMPACT_NONE
                                        && impact < currentObserverImpact) {
                                    currentObserverToNotify = registeredObserver;
                                    currentObserverImpact = impact;
                                }
                            }
                        }

                        // Execute action with least user impact
                        if (currentObserverToNotify != null) {
                            currentObserverToNotify.execute(versionedPackage, failureReason);
                        }
                    }
                }
            }
        });
    }

當package failureReason 的原因為Native_Crash和FAILURE_REASON_EXPLICIT_HEALTH_CHECK時,將會立刻對問題進行處理。
使用函式為:handleFailureImmediately。

    /**
     * For native crashes or explicit health check failures, call directly into each observer to
     * mitigate the error without going through failure threshold logic.
     */
    private void handleFailureImmediately(List<VersionedPackage> packages,
            @FailureReasons int failureReason) {
        VersionedPackage failingPackage = packages.size() > 0 ? packages.get(0) : null;
        PackageHealthObserver currentObserverToNotify = null;
        int currentObserverImpact = Integer.MAX_VALUE;
        for (ObserverInternal observer: mAllObservers.values()) {
            PackageHealthObserver registeredObserver = observer.registeredObserver;
            if (registeredObserver != null) {
                int impact = registeredObserver.onHealthCheckFailed(
                        failingPackage, failureReason);
                if (impact != PackageHealthObserverImpact.USER_IMPACT_NONE
                        && impact < currentObserverImpact) {
                    currentObserverToNotify = registeredObserver;
                    currentObserverImpact = impact;
                }
            }
        }
        if (currentObserverToNotify != null) {
            currentObserverToNotify.execute(failingPackage,  failureReason);
        }
    }

在使用後,會執行execute的函式:

    @Override
    public boolean execute(@Nullable VersionedPackage failedPackage,
            @FailureReasons int rollbackReason) {
        if (rollbackReason == PackageWatchdog.FAILURE_REASON_NATIVE_CRASH) {
            rollbackAll();
            return true;
        }

        RollbackInfo rollback = getAvailableRollback(failedPackage);
        if (rollback == null) {
            Slog.w(TAG, "Expected rollback but no valid rollback found for " + failedPackage);
            return false;
        }
        rollbackPackage(rollback, failedPackage, rollbackReason);
        // Assume rollback executed successfully
        return true;
    }

這裡面我們主要關注的是NATIVE_CRASH的實現,所以將會去看rollbackAll的具體實現。

    private void rollbackAll() {
        Slog.i(TAG, "Rolling back all available rollbacks");
        RollbackManager rollbackManager = mContext.getSystemService(RollbackManager.class);
        List<RollbackInfo> rollbacks = rollbackManager.getAvailableRollbacks();

        // Add all rollback ids to mPendingStagedRollbackIds, so that we do not reboot before all
        // pending staged rollbacks are handled.
        synchronized (mPendingStagedRollbackIds) {
            for (RollbackInfo rollback : rollbacks) {
                if (rollback.isStaged()) {
                    mPendingStagedRollbackIds.add(rollback.getRollbackId());
                }
            }
        }

        for (RollbackInfo rollback : rollbacks) {
            VersionedPackage sample = rollback.getPackages().get(0).getVersionRolledBackFrom();
            rollbackPackage(rollback, sample, PackageWatchdog.FAILURE_REASON_NATIVE_CRASH);
        }
    }

RollBackPackage具體的實現邏輯如下:

    /**
     * Rolls back the session that owns {@code failedPackage}
     *
     * @param rollback {@code rollbackInfo} of the {@code failedPackage}
     * @param failedPackage the package that needs to be rolled back
     */
    private void rollbackPackage(RollbackInfo rollback, VersionedPackage failedPackage,
            @FailureReasons int rollbackReason) {
        final RollbackManager rollbackManager = mContext.getSystemService(RollbackManager.class);
        int reasonToLog = WatchdogRollbackLogger.mapFailureReasonToMetric(rollbackReason);
        final String failedPackageToLog;
        if (rollbackReason == PackageWatchdog.FAILURE_REASON_NATIVE_CRASH) {
            failedPackageToLog = SystemProperties.get(
                    "sys.init.updatable_crashing_process_name", "");
        } else {
            failedPackageToLog = failedPackage.getPackageName();
        }
        VersionedPackage logPackageTemp = null;
        if (isModule(failedPackage.getPackageName())) {
            logPackageTemp = WatchdogRollbackLogger.getLogPackage(mContext, failedPackage);
        }

        final VersionedPackage logPackage = logPackageTemp;
        WatchdogRollbackLogger.logEvent(logPackage,
                FrameworkStatsLog.WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_TYPE__ROLLBACK_INITIATE,
                reasonToLog, failedPackageToLog);
        final LocalIntentReceiver rollbackReceiver = new LocalIntentReceiver((Intent result) -> {
            int status = result.getIntExtra(RollbackManager.EXTRA_STATUS,
                    RollbackManager.STATUS_FAILURE);
            if (status == RollbackManager.STATUS_SUCCESS) {
                if (rollback.isStaged()) {
                    int rollbackId = rollback.getRollbackId();
                    synchronized (mPendingStagedRollbackIds) {
                        mPendingStagedRollbackIds.add(rollbackId);
                    }
                    BroadcastReceiver listener =
                            listenForStagedSessionReady(rollbackManager, rollbackId,
                                    logPackage);
                    handleStagedSessionChange(rollbackManager, rollbackId, listener,
                            logPackage);
                } else {
                    WatchdogRollbackLogger.logEvent(logPackage,
                            FrameworkStatsLog
                                    .WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_TYPE__ROLLBACK_SUCCESS,
                            reasonToLog, failedPackageToLog);
                }
            } else {
                if (rollback.isStaged()) {
                    markStagedSessionHandled(rollback.getRollbackId());
                }
                WatchdogRollbackLogger.logEvent(logPackage,
                        FrameworkStatsLog
                                .WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_TYPE__ROLLBACK_FAILURE,
                        reasonToLog, failedPackageToLog);
            }
        });

        mHandler.post(() ->
                rollbackManager.commitRollback(rollback.getRollbackId(),
                        Collections.singletonList(failedPackage),
                        rollbackReceiver.getIntentSender()));
    }

這裡面我們不去具體的分析某個session,而是回到前文中,提到的具體的狀態,這裡就會看到最後的這麼一個邏輯。

        mHandler.post(() ->
                rollbackManager.commitRollback(rollback.getRollbackId(),
                        Collections.singletonList(failedPackage),
                        rollbackReceiver.getIntentSender()));

這裡面是呼叫了rollbackManager的commitRollback方法:

    @Override
    public void commitRollback(int rollbackId, ParceledListSlice causePackages,
            String callerPackageName, IntentSender statusReceiver) {
        enforceManageRollbacks("commitRollback");

        final int callingUid = Binder.getCallingUid();
        AppOpsManager appOps = mContext.getSystemService(AppOpsManager.class);
        appOps.checkPackage(callingUid, callerPackageName);

        getHandler().post(() ->
                commitRollbackInternal(rollbackId, causePackages.getList(),
                    callerPackageName, statusReceiver));
    }

其實也就是獲取package,然後去通過commitRollbackInternal處理。

    /**
     * Performs the actual work to commit a rollback.
     * The work is done on the current thread. This may be a long running
     * operation.
     */
    @WorkerThread
    private void commitRollbackInternal(int rollbackId, List<VersionedPackage> causePackages,
            String callerPackageName, IntentSender statusReceiver) {
        Slog.i(TAG, "commitRollback id=" + rollbackId + " caller=" + callerPackageName);

        Rollback rollback = getRollbackForId(rollbackId);
        if (rollback == null) {
            sendFailure(
                    mContext, statusReceiver, RollbackManager.STATUS_FAILURE_ROLLBACK_UNAVAILABLE,
                    "Rollback unavailable");
            return;
        }
        rollback.commit(mContext, causePackages, callerPackageName, statusReceiver);
    }

rollback的commit將會去具體的更改某個rollback的狀態:

    /**
     * Commits the rollback.
     */
    void commit(final Context context, List<VersionedPackage> causePackages,
            String callerPackageName, IntentSender statusReceiver) {
        synchronized (mLock) {
            if (!isAvailable()) {
                sendFailure(context, statusReceiver,
                        RollbackManager.STATUS_FAILURE_ROLLBACK_UNAVAILABLE,
                        "Rollback unavailable");
                return;
            }

            if (containsApex() && wasCreatedAtLowerExtensionVersion()) {
                PackageManagerInternal pmi = LocalServices.getService(PackageManagerInternal.class);
                if (extensionVersionReductionWouldViolateConstraint(mExtensionVersions, pmi)) {
                    sendFailure(context, statusReceiver, RollbackManager.STATUS_FAILURE,
                            "Rollback may violate a minExtensionVersion constraint");
                    return;
                }
            }

            // Get a context to use to install the downgraded version of the package.
            Context pkgContext;
            try {
                pkgContext = context.createPackageContextAsUser(callerPackageName, 0,
                        UserHandle.of(mUserId));
            } catch (PackageManager.NameNotFoundException e) {
                sendFailure(context, statusReceiver, RollbackManager.STATUS_FAILURE,
                        "Invalid callerPackageName");
                return;
            }

            PackageManager pm = pkgContext.getPackageManager();
            try {
                PackageInstaller packageInstaller = pm.getPackageInstaller();
                PackageInstaller.SessionParams parentParams = new PackageInstaller.SessionParams(
                        PackageInstaller.SessionParams.MODE_FULL_INSTALL);
                parentParams.setRequestDowngrade(true);
                parentParams.setMultiPackage();
                if (isStaged()) {
                    parentParams.setStaged();
                }
                parentParams.setInstallReason(PackageManager.INSTALL_REASON_ROLLBACK);

                int parentSessionId = packageInstaller.createSession(parentParams);
                PackageInstaller.Session parentSession = packageInstaller.openSession(
                        parentSessionId);

                for (PackageRollbackInfo pkgRollbackInfo : info.getPackages()) {
                    if (pkgRollbackInfo.isApkInApex()) {
                        // No need to issue a downgrade install request for apk-in-apex. It will
                        // be rolled back when its parent apex is downgraded.
                        continue;
                    }
                    PackageInstaller.SessionParams params = new PackageInstaller.SessionParams(
                            PackageInstaller.SessionParams.MODE_FULL_INSTALL);
                    String installerPackageName = mInstallerPackageName;
                    if (TextUtils.isEmpty(mInstallerPackageName)) {
                        installerPackageName = pm.getInstallerPackageName(
                                pkgRollbackInfo.getPackageName());
                    }
                    if (installerPackageName != null) {
                        params.setInstallerPackageName(installerPackageName);
                    }
                    params.setRequestDowngrade(true);
                    params.setRequiredInstalledVersionCode(
                            pkgRollbackInfo.getVersionRolledBackFrom().getLongVersionCode());
                    if (isStaged()) {
                        params.setStaged();
                    }
                    if (pkgRollbackInfo.isApex()) {
                        params.setInstallAsApex();
                    }
                    int sessionId = packageInstaller.createSession(params);
                    PackageInstaller.Session session = packageInstaller.openSession(sessionId);
                    File[] packageCodePaths = RollbackStore.getPackageCodePaths(
                            this, pkgRollbackInfo.getPackageName());
                    if (packageCodePaths == null) {
                        sendFailure(context, statusReceiver, RollbackManager.STATUS_FAILURE,
                                "Backup copy of package: "
                                        + pkgRollbackInfo.getPackageName() + " is inaccessible");
                        return;
                    }

                    for (File packageCodePath : packageCodePaths) {
                        try (ParcelFileDescriptor fd = ParcelFileDescriptor.open(packageCodePath,
                                ParcelFileDescriptor.MODE_READ_ONLY)) {
                            final long token = Binder.clearCallingIdentity();
                            try {
                                session.write(packageCodePath.getName(), 0,
                                        packageCodePath.length(),
                                        fd);
                            } finally {
                                Binder.restoreCallingIdentity(token);
                            }
                        }
                    }
                    parentSession.addChildSessionId(sessionId);
                }

                final LocalIntentReceiver receiver = new LocalIntentReceiver(
                        (Intent result) -> {
                            int status = result.getIntExtra(PackageInstaller.EXTRA_STATUS,
                                    PackageInstaller.STATUS_FAILURE);
                            if (status != PackageInstaller.STATUS_SUCCESS) {
                                // Committing the rollback failed, but we still have all the info we
                                // need to try rolling back again, so restore the rollback state to
                                // how it was before we tried committing.
                                // TODO: Should we just kill this rollback if commit failed?
                                // Why would we expect commit not to fail again?
                                // TODO: Could this cause a rollback to be resurrected
                                // if it should otherwise have expired by now?
                                synchronized (mLock) {
                                    mState = ROLLBACK_STATE_AVAILABLE;
                                    mRestoreUserDataInProgress = false;
                                    info.setCommittedSessionId(-1);
                                }
                                sendFailure(context, statusReceiver,
                                        RollbackManager.STATUS_FAILURE_INSTALL,
                                        "Rollback downgrade install failed: "
                                                + result.getStringExtra(
                                                PackageInstaller.EXTRA_STATUS_MESSAGE));
                                return;
                            }

                            synchronized (mLock) {
                                if (!isStaged()) {
                                    // All calls to restoreUserData should have
                                    // completed by now for a non-staged install.
                                    mRestoreUserDataInProgress = false;
                                }

                                info.getCausePackages().addAll(causePackages);
                                RollbackStore.deletePackageCodePaths(this);
                                RollbackStore.saveRollback(this);
                            }

                            // Send success.
                            try {
                                final Intent fillIn = new Intent();
                                fillIn.putExtra(
                                        RollbackManager.EXTRA_STATUS,
                                        RollbackManager.STATUS_SUCCESS);
                                statusReceiver.sendIntent(context, 0, fillIn, null, null);
                            } catch (IntentSender.SendIntentException e) {
                                // Nowhere to send the result back to, so don't bother.
                            }

                            Intent broadcast = new Intent(Intent.ACTION_ROLLBACK_COMMITTED);

                            for (UserInfo userInfo : UserManager.get(context).getUsers(true)) {
                                context.sendBroadcastAsUser(broadcast,
                                        userInfo.getUserHandle(),
                                        Manifest.permission.MANAGE_ROLLBACKS);
                            }
                        }
                );

                mState = ROLLBACK_STATE_COMMITTED;
                info.setCommittedSessionId(parentSessionId);
                mRestoreUserDataInProgress = true;
                parentSession.commit(receiver.getIntentSender());
            } catch (IOException e) {
                Slog.e(TAG, "Rollback failed", e);
                sendFailure(context, statusReceiver, RollbackManager.STATUS_FAILURE,
                        "IOException: " + e.toString());
            }
        }
    }

在執行完後,會將mState置為ROLLBACK_STATE_COMMITTED;

mState = ROLLBACK_STATE_COMMITTED;

所以,當我們檢查初始化狀態為

ROLLBACK_STATE_ENABLING,

功能介紹:

在Android 10.0中,Google新增加了個功能。
如果使用者對新升級的APP不滿意,可以通過“回到過去”,回滾到舊版。
當然,如果新安裝的apk出現了各種問題無法使用,也可以進行回滾的操作。
這樣的確可以極大的提升使用者的體驗,但是因為這塊的邏輯較複雜,我們以module_crash_rollback_test為例,來看下具體的實現邏輯。


程式碼路徑如下:

./base/services/core/java/com/android/server/rollback
./base/core/java/android/content/rollback

工作原理:

如何驗證這個功能是否邏輯生效,我們可以使用這個方法:

1. adb install -r -d --enable-rollback --staged ***.apk
2. adb reboot
3. adb shell dumpsys rollback
4. adb root
5. adb shell am crash ***  (10 times)
6. adb reboot
7. adb wait-for-devices 1 mins
8. adb shell dumpsys rollback

我們即可從RollBack的狀態,檢查rollback機制是否被啟用以及使用。
dumpsys的code在程式碼中對應如下:

程式碼路徑為:frameworks/base/services/core/java/com/android/server/rollback/Rollback.java

    void dump(IndentingPrintWriter ipw) {
        synchronized (mLock) {
            ipw.println(info.getRollbackId() + ":");
            ipw.increaseIndent();
            ipw.println("-state: " + getStateAsString());
            ipw.println("-timestamp: " + getTimestamp());
            if (getStagedSessionId() != -1) {
                ipw.println("-stagedSessionId: " + getStagedSessionId());
            }
            ipw.println("-packages:");
            ipw.increaseIndent();
            for (PackageRollbackInfo pkg : info.getPackages()) {
                ipw.println(pkg.getPackageName()
                        + " " + pkg.getVersionRolledBackFrom().getLongVersionCode()
                        + " -> " + pkg.getVersionRolledBackTo().getLongVersionCode());
            }
            ipw.decreaseIndent();
            if (isCommitted()) {
                ipw.println("-causePackages:");
                ipw.increaseIndent();
                for (VersionedPackage cPkg : info.getCausePackages()) {
                    ipw.println(cPkg.getPackageName() + " " + cPkg.getLongVersionCode());
                }
                ipw.decreaseIndent();
                ipw.println("-committedSessionId: " + info.getCommittedSessionId());
            }
            if (mExtensionVersions.size() > 0) {
                ipw.println("-extensionVersions:");
                ipw.increaseIndent();
                ipw.println(mExtensionVersions.toString());
                ipw.decreaseIndent();
            }
            ipw.decreaseIndent();
        }
    }

從dumpsys中,我們就可以看到rollback的當前執行狀態。

    String getStateAsString() {
        synchronized (mLock) {
            return rollbackStateToString(mState);
        }
    }

邏輯很簡單,即為將RollBack中的mState變數值置為String並且打出。
變數定義如下:

    /**
     * The current state of the rollback.
     * ENABLING, AVAILABLE, or COMMITTED.
     */
    @GuardedBy("mLock")
    private @RollbackState int mState;

會有四個狀態值,來對應當前的mState.

    @IntDef(prefix = { "ROLLBACK_STATE_" }, value = {
            ROLLBACK_STATE_ENABLING,
            ROLLBACK_STATE_AVAILABLE,
            ROLLBACK_STATE_COMMITTED,
            ROLLBACK_STATE_DELETED
    })

那麼在執行module_crash_rollback_test的時候,我們的邏輯是怎麼生效的呢?
首先是在rollbackmanagerservice中:

/**
 * Service that manages APK level rollbacks. Publishes
 * Context.ROLLBACK_SERVICE.
 *
 * @hide
 */
public final class RollbackManagerService extends SystemService {

    private RollbackManagerServiceImpl mService;

    public RollbackManagerService(Context context) {
        super(context);
    }

    @Override
    public void onStart() {
        mService = new RollbackManagerServiceImpl(getContext());
        publishBinderService(Context.ROLLBACK_SERVICE, mService);
    }

    @Override
    public void onUnlockUser(int user) {
        mService.onUnlockUser(user);
    }

    @Override
    public void onBootPhase(int phase) {
        if (phase == SystemService.PHASE_BOOT_COMPLETED) {
            mService.onBootCompleted();
        }
    }

可以看到的是,在PHASE_BOOT_COMPLETED時,將會呼叫onBootCompleted的函式。
如果看過之前的文章的同學,可能也明白了這個函式是在系統啟動完成後,針對全域性發出的通知。

    @AnyThread
    void onBootCompleted() {
        DeviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_ROLLBACK_BOOT,
                mExecutor, properties -> updateRollbackLifetimeDurationInMillis());

        getHandler().post(() -> {
            updateRollbackLifetimeDurationInMillis();
            runExpiration();

            // Check to see if any rollback-enabled staged sessions or staged
            // rollback sessions been applied.
            List<Rollback> enabling = new ArrayList<>();
            List<Rollback> restoreInProgress = new ArrayList<>();
            Set<String> apexPackageNames = new HashSet<>();
            synchronized (mLock) {
                Iterator<Rollback> iter = mRollbacks.iterator();
                while (iter.hasNext()) {
                    Rollback rollback = iter.next();
                    if (!rollback.isStaged()) {
                        // We only care about staged rollbacks here
                        continue;
                    }

                    PackageInstaller.SessionInfo session = mContext.getPackageManager()
                            .getPackageInstaller().getSessionInfo(rollback.getStagedSessionId());
                    if (session == null || session.isStagedSessionFailed()) {
                        iter.remove();
                        rollback.delete(mAppDataRollbackHelper);
                        continue;
                    }

                    if (session.isStagedSessionApplied()) {
                        if (rollback.isEnabling()) {
                            enabling.add(rollback);
                        } else if (rollback.isRestoreUserDataInProgress()) {
                            restoreInProgress.add(rollback);
                        }
                    }
                    apexPackageNames.addAll(rollback.getApexPackageNames());
                }
            }

            for (Rollback rollback : enabling) {
                makeRollbackAvailable(rollback);
            }

            for (Rollback rollback : restoreInProgress) {
                rollback.setRestoreUserDataInProgress(false);
            }

            for (String apexPackageName : apexPackageNames) {
                // We will not recieve notifications when an apex is updated,
                // so check now in case any rollbacks ought to be expired. The
                // onPackagedReplace function is safe to call if the package
                // hasn't actually been updated.
                onPackageReplaced(apexPackageName);
            }

            synchronized (mLock) {
                mOrphanedApkSessionIds.clear();
            }

            mPackageHealthObserver.onBootCompletedAsync();
        });
    }

這段主要說的是在系統啟動過程中,我們將會對rollback的功能開啟,各個session的狀態,以及實際的packageName進行replaced,restore userdata的操作。這邊分析一下onPackageReplaced函式:

    /**
     * Called when a package has been replaced with a different version.
     * Removes all backups for the package not matching the currently
     * installed package version.
     */
    @WorkerThread
    private void onPackageReplaced(String packageName) {
        // TODO: Could this end up incorrectly deleting a rollback for a
        // package that is about to be installed?
        long installedVersion = getInstalledPackageVersion(packageName);

        synchronized (mLock) {
            Iterator<Rollback> iter = mRollbacks.iterator();
            while (iter.hasNext()) {
                Rollback rollback = iter.next();
                // TODO: Should we remove rollbacks in the ENABLING state here?
                if ((rollback.isEnabling() || rollback.isAvailable())
                        && rollback.includesPackageWithDifferentVersion(packageName,
                        installedVersion)) {
                    iter.remove();
                    rollback.delete(mAppDataRollbackHelper);
                }
            }
        }
    }

當包被其他版本替換時呼叫時,我們會通過installedVersion來儲存APK的版本號,
並且在下面將會刪除與當前安裝的包版本不匹配的包的所有備份。
在操作完,將會執行onBootCompletedAsync函式,而這邊是進行的通知。

    /** Verifies the rollback state after a reboot and schedules polling for sometime after reboot
     * to check for native crashes and mitigate them if needed.
     */
    public void onBootCompletedAsync() {
        mHandler.post(()->onBootCompleted());
    }

那麼這個onBootCompleted在做什麼工作呢?

    private void onBootCompleted() {
        RollbackManager rollbackManager = mContext.getSystemService(RollbackManager.class);
        if (!rollbackManager.getAvailableRollbacks().isEmpty()) {
            // TODO(gavincorkery): Call into Package Watchdog from outside the observer
            PackageWatchdog.getInstance(mContext).scheduleCheckAndMitigateNativeCrashes();
        }

        SparseArray<String> rollbackIds = popLastStagedRollbackIds();
        for (int i = 0; i < rollbackIds.size(); i++) {
            WatchdogRollbackLogger.logRollbackStatusOnBoot(mContext,
                    rollbackIds.keyAt(i), rollbackIds.valueAt(i),
                    rollbackManager.getRecentlyCommittedRollbacks());
        }
    }

這邊的重頭戲來了,scheduleCheckAndMitigateNativeCrashes看上去和我們要驗證的module_crash_rollback_test非常的相似。

    /**
     * Since this method can eventually trigger a rollback, it should be called
     * only once boot has completed {@code onBootCompleted} and not earlier, because the install
     * session must be entirely completed before we try to rollback.
     */
    public void scheduleCheckAndMitigateNativeCrashes() {
        Slog.i(TAG, "Scheduling " + mNumberOfNativeCrashPollsRemaining + " polls to check "
                + "and mitigate native crashes");
        mShortTaskHandler.post(()->checkAndMitigateNativeCrashes());
    }

只是列印了log,就來執行check的操作。

    /**
     * This method should be only called on mShortTaskHandler, since it modifies
     * {@link #mNumberOfNativeCrashPollsRemaining}.
     */
    private void checkAndMitigateNativeCrashes() {
        mNumberOfNativeCrashPollsRemaining--;
        // Check if native watchdog reported a crash
        if ("1".equals(SystemProperties.get("sys.init.updatable_crashing"))) {
            // We rollback everything available when crash is unattributable
            onPackageFailure(Collections.EMPTY_LIST, FAILURE_REASON_NATIVE_CRASH);
            // we stop polling after an attempt to execute rollback, regardless of whether the
            // attempt succeeds or not
        } else {
            if (mNumberOfNativeCrashPollsRemaining > 0) {
                mShortTaskHandler.postDelayed(() -> checkAndMitigateNativeCrashes(),
                        NATIVE_CRASH_POLLING_INTERVAL_MILLIS);
            }
        }
    }

這邊就非常奇怪了,為什麼會有sys.init.updatable_crashing這個systemproperties呢?
這個properties是定義在什麼地方?
程式碼路徑: system/core/init/service.cpp

    // If we crash > 4 times in 4 minutes or before boot_completed,
    // reboot into bootloader or set crashing property
    boot_clock::time_point now = boot_clock::now();
    if (((flags_ & SVC_CRITICAL) || is_process_updatable) && !(flags_ & SVC_RESTART)) {
        bool boot_completed = android::base::GetBoolProperty("sys.boot_completed", false);
        if (now < time_crashed_ + 4min || !boot_completed) {
            if (++crash_count_ > 4) {
                if (flags_ & SVC_CRITICAL) {
                    // Aborts into bootloader
                    LOG(FATAL) << "critical process '" << name_ << "' exited 4 times "
                               << (boot_completed ? "in 4 minutes" : "before boot completed");
                } else {
                    LOG(ERROR) << "updatable process '" << name_ << "' exited 4 times "
                               << (boot_completed ? "in 4 minutes" : "before boot completed");
                    // Notifies update_verifier and apexd
                    SetProperty("sys.init.updatable_crashing_process_name", name_);
                    SetProperty("sys.init.updatable_crashing", "1");
                }
            }
        } else {
            time_crashed_ = now;
            crash_count_ = 1;
        }
    }

這裡其實是對Crash的一個檢查,如果在開機以後,規定時間內有四次以上的crash,然後就會觸發這個properties的定義。
同時會記錄當前程式的名字:sys.init.updatable_crashing_process_name。
但是在正常的過程中,這個應該不會出現。
但是在我們之前測試步驟中,當我們連續crash apk多次,那麼重啟後是否就會啟用rollback呢?
應該是的,我們繼續看看狀態的改變過程。

    /**
     * Called when a process fails due to a crash, ANR or explicit health check.
     *
     * <p>For each package contained in the process, one registered observer with the least user
     * impact will be notified for mitigation.
     *
     * <p>This method could be called frequently if there is a severe problem on the device.
     */
    public void onPackageFailure(List<VersionedPackage> packages,
            @FailureReasons int failureReason) {
        if (packages == null) {
            Slog.w(TAG, "Could not resolve a list of failing packages");
            return;
        }
        mLongTaskHandler.post(() -> {
            synchronized (mLock) {
                if (mAllObservers.isEmpty()) {
                    return;
                }
                boolean requiresImmediateAction = (failureReason == FAILURE_REASON_NATIVE_CRASH
                        || failureReason == FAILURE_REASON_EXPLICIT_HEALTH_CHECK);
                if (requiresImmediateAction) {
                    handleFailureImmediately(packages, failureReason);
                } else {
                    for (int pIndex = 0; pIndex < packages.size(); pIndex++) {
                        VersionedPackage versionedPackage = packages.get(pIndex);
                        // Observer that will receive failure for versionedPackage
                        PackageHealthObserver currentObserverToNotify = null;
                        int currentObserverImpact = Integer.MAX_VALUE;

                        // Find observer with least user impact
                        for (int oIndex = 0; oIndex < mAllObservers.size(); oIndex++) {
                            ObserverInternal observer = mAllObservers.valueAt(oIndex);
                            PackageHealthObserver registeredObserver = observer.registeredObserver;
                            if (registeredObserver != null
                                    && observer.onPackageFailureLocked(
                                    versionedPackage.getPackageName())) {
                                int impact = registeredObserver.onHealthCheckFailed(
                                        versionedPackage, failureReason);
                                if (impact != PackageHealthObserverImpact.USER_IMPACT_NONE
                                        && impact < currentObserverImpact) {
                                    currentObserverToNotify = registeredObserver;
                                    currentObserverImpact = impact;
                                }
                            }
                        }

                        // Execute action with least user impact
                        if (currentObserverToNotify != null) {
                            currentObserverToNotify.execute(versionedPackage, failureReason);
                        }
                    }
                }
            }
        });
    }

當package failureReason 的原因為Native_Crash和FAILURE_REASON_EXPLICIT_HEALTH_CHECK時,將會立刻對問題進行處理。
使用函式為:handleFailureImmediately。

    /**
     * For native crashes or explicit health check failures, call directly into each observer to
     * mitigate the error without going through failure threshold logic.
     */
    private void handleFailureImmediately(List<VersionedPackage> packages,
            @FailureReasons int failureReason) {
        VersionedPackage failingPackage = packages.size() > 0 ? packages.get(0) : null;
        PackageHealthObserver currentObserverToNotify = null;
        int currentObserverImpact = Integer.MAX_VALUE;
        for (ObserverInternal observer: mAllObservers.values()) {
            PackageHealthObserver registeredObserver = observer.registeredObserver;
            if (registeredObserver != null) {
                int impact = registeredObserver.onHealthCheckFailed(
                        failingPackage, failureReason);
                if (impact != PackageHealthObserverImpact.USER_IMPACT_NONE
                        && impact < currentObserverImpact) {
                    currentObserverToNotify = registeredObserver;
                    currentObserverImpact = impact;
                }
            }
        }
        if (currentObserverToNotify != null) {
            currentObserverToNotify.execute(failingPackage,  failureReason);
        }
    }

在使用後,會執行execute的函式:

    @Override
    public boolean execute(@Nullable VersionedPackage failedPackage,
            @FailureReasons int rollbackReason) {
        if (rollbackReason == PackageWatchdog.FAILURE_REASON_NATIVE_CRASH) {
            rollbackAll();
            return true;
        }

        RollbackInfo rollback = getAvailableRollback(failedPackage);
        if (rollback == null) {
            Slog.w(TAG, "Expected rollback but no valid rollback found for " + failedPackage);
            return false;
        }
        rollbackPackage(rollback, failedPackage, rollbackReason);
        // Assume rollback executed successfully
        return true;
    }

這裡面我們主要關注的是NATIVE_CRASH的實現,所以將會去看rollbackAll的具體實現。

    private void rollbackAll() {
        Slog.i(TAG, "Rolling back all available rollbacks");
        RollbackManager rollbackManager = mContext.getSystemService(RollbackManager.class);
        List<RollbackInfo> rollbacks = rollbackManager.getAvailableRollbacks();

        // Add all rollback ids to mPendingStagedRollbackIds, so that we do not reboot before all
        // pending staged rollbacks are handled.
        synchronized (mPendingStagedRollbackIds) {
            for (RollbackInfo rollback : rollbacks) {
                if (rollback.isStaged()) {
                    mPendingStagedRollbackIds.add(rollback.getRollbackId());
                }
            }
        }

        for (RollbackInfo rollback : rollbacks) {
            VersionedPackage sample = rollback.getPackages().get(0).getVersionRolledBackFrom();
            rollbackPackage(rollback, sample, PackageWatchdog.FAILURE_REASON_NATIVE_CRASH);
        }
    }

RollBackPackage具體的實現邏輯如下:

    /**
     * Rolls back the session that owns {@code failedPackage}
     *
     * @param rollback {@code rollbackInfo} of the {@code failedPackage}
     * @param failedPackage the package that needs to be rolled back
     */
    private void rollbackPackage(RollbackInfo rollback, VersionedPackage failedPackage,
            @FailureReasons int rollbackReason) {
        final RollbackManager rollbackManager = mContext.getSystemService(RollbackManager.class);
        int reasonToLog = WatchdogRollbackLogger.mapFailureReasonToMetric(rollbackReason);
        final String failedPackageToLog;
        if (rollbackReason == PackageWatchdog.FAILURE_REASON_NATIVE_CRASH) {
            failedPackageToLog = SystemProperties.get(
                    "sys.init.updatable_crashing_process_name", "");
        } else {
            failedPackageToLog = failedPackage.getPackageName();
        }
        VersionedPackage logPackageTemp = null;
        if (isModule(failedPackage.getPackageName())) {
            logPackageTemp = WatchdogRollbackLogger.getLogPackage(mContext, failedPackage);
        }

        final VersionedPackage logPackage = logPackageTemp;
        WatchdogRollbackLogger.logEvent(logPackage,
                FrameworkStatsLog.WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_TYPE__ROLLBACK_INITIATE,
                reasonToLog, failedPackageToLog);
        final LocalIntentReceiver rollbackReceiver = new LocalIntentReceiver((Intent result) -> {
            int status = result.getIntExtra(RollbackManager.EXTRA_STATUS,
                    RollbackManager.STATUS_FAILURE);
            if (status == RollbackManager.STATUS_SUCCESS) {
                if (rollback.isStaged()) {
                    int rollbackId = rollback.getRollbackId();
                    synchronized (mPendingStagedRollbackIds) {
                        mPendingStagedRollbackIds.add(rollbackId);
                    }
                    BroadcastReceiver listener =
                            listenForStagedSessionReady(rollbackManager, rollbackId,
                                    logPackage);
                    handleStagedSessionChange(rollbackManager, rollbackId, listener,
                            logPackage);
                } else {
                    WatchdogRollbackLogger.logEvent(logPackage,
                            FrameworkStatsLog
                                    .WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_TYPE__ROLLBACK_SUCCESS,
                            reasonToLog, failedPackageToLog);
                }
            } else {
                if (rollback.isStaged()) {
                    markStagedSessionHandled(rollback.getRollbackId());
                }
                WatchdogRollbackLogger.logEvent(logPackage,
                        FrameworkStatsLog
                                .WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_TYPE__ROLLBACK_FAILURE,
                        reasonToLog, failedPackageToLog);
            }
        });

        mHandler.post(() ->
                rollbackManager.commitRollback(rollback.getRollbackId(),
                        Collections.singletonList(failedPackage),
                        rollbackReceiver.getIntentSender()));
    }

這裡面我們不去具體的分析某個session,而是回到前文中,提到的具體的狀態,這裡就會看到最後的這麼一個邏輯。

        mHandler.post(() ->
                rollbackManager.commitRollback(rollback.getRollbackId(),
                        Collections.singletonList(failedPackage),
                        rollbackReceiver.getIntentSender()));

這裡面是呼叫了rollbackManager的commitRollback方法:

    @Override
    public void commitRollback(int rollbackId, ParceledListSlice causePackages,
            String callerPackageName, IntentSender statusReceiver) {
        enforceManageRollbacks("commitRollback");

        final int callingUid = Binder.getCallingUid();
        AppOpsManager appOps = mContext.getSystemService(AppOpsManager.class);
        appOps.checkPackage(callingUid, callerPackageName);

        getHandler().post(() ->
                commitRollbackInternal(rollbackId, causePackages.getList(),
                    callerPackageName, statusReceiver));
    }

其實也就是獲取package,然後去通過commitRollbackInternal處理。

    /**
     * Performs the actual work to commit a rollback.
     * The work is done on the current thread. This may be a long running
     * operation.
     */
    @WorkerThread
    private void commitRollbackInternal(int rollbackId, List<VersionedPackage> causePackages,
            String callerPackageName, IntentSender statusReceiver) {
        Slog.i(TAG, "commitRollback id=" + rollbackId + " caller=" + callerPackageName);

        Rollback rollback = getRollbackForId(rollbackId);
        if (rollback == null) {
            sendFailure(
                    mContext, statusReceiver, RollbackManager.STATUS_FAILURE_ROLLBACK_UNAVAILABLE,
                    "Rollback unavailable");
            return;
        }
        rollback.commit(mContext, causePackages, callerPackageName, statusReceiver);
    }

rollback的commit將會去具體的更改某個rollback的狀態:

    /**
     * Commits the rollback.
     */
    void commit(final Context context, List<VersionedPackage> causePackages,
            String callerPackageName, IntentSender statusReceiver) {
        synchronized (mLock) {
            if (!isAvailable()) {
                sendFailure(context, statusReceiver,
                        RollbackManager.STATUS_FAILURE_ROLLBACK_UNAVAILABLE,
                        "Rollback unavailable");
                return;
            }

            if (containsApex() && wasCreatedAtLowerExtensionVersion()) {
                PackageManagerInternal pmi = LocalServices.getService(PackageManagerInternal.class);
                if (extensionVersionReductionWouldViolateConstraint(mExtensionVersions, pmi)) {
                    sendFailure(context, statusReceiver, RollbackManager.STATUS_FAILURE,
                            "Rollback may violate a minExtensionVersion constraint");
                    return;
                }
            }

            // Get a context to use to install the downgraded version of the package.
            Context pkgContext;
            try {
                pkgContext = context.createPackageContextAsUser(callerPackageName, 0,
                        UserHandle.of(mUserId));
            } catch (PackageManager.NameNotFoundException e) {
                sendFailure(context, statusReceiver, RollbackManager.STATUS_FAILURE,
                        "Invalid callerPackageName");
                return;
            }

            PackageManager pm = pkgContext.getPackageManager();
            try {
                PackageInstaller packageInstaller = pm.getPackageInstaller();
                PackageInstaller.SessionParams parentParams = new PackageInstaller.SessionParams(
                        PackageInstaller.SessionParams.MODE_FULL_INSTALL);
                parentParams.setRequestDowngrade(true);
                parentParams.setMultiPackage();
                if (isStaged()) {
                    parentParams.setStaged();
                }
                parentParams.setInstallReason(PackageManager.INSTALL_REASON_ROLLBACK);

                int parentSessionId = packageInstaller.createSession(parentParams);
                PackageInstaller.Session parentSession = packageInstaller.openSession(
                        parentSessionId);

                for (PackageRollbackInfo pkgRollbackInfo : info.getPackages()) {
                    if (pkgRollbackInfo.isApkInApex()) {
                        // No need to issue a downgrade install request for apk-in-apex. It will
                        // be rolled back when its parent apex is downgraded.
                        continue;
                    }
                    PackageInstaller.SessionParams params = new PackageInstaller.SessionParams(
                            PackageInstaller.SessionParams.MODE_FULL_INSTALL);
                    String installerPackageName = mInstallerPackageName;
                    if (TextUtils.isEmpty(mInstallerPackageName)) {
                        installerPackageName = pm.getInstallerPackageName(
                                pkgRollbackInfo.getPackageName());
                    }
                    if (installerPackageName != null) {
                        params.setInstallerPackageName(installerPackageName);
                    }
                    params.setRequestDowngrade(true);
                    params.setRequiredInstalledVersionCode(
                            pkgRollbackInfo.getVersionRolledBackFrom().getLongVersionCode());
                    if (isStaged()) {
                        params.setStaged();
                    }
                    if (pkgRollbackInfo.isApex()) {
                        params.setInstallAsApex();
                    }
                    int sessionId = packageInstaller.createSession(params);
                    PackageInstaller.Session session = packageInstaller.openSession(sessionId);
                    File[] packageCodePaths = RollbackStore.getPackageCodePaths(
                            this, pkgRollbackInfo.getPackageName());
                    if (packageCodePaths == null) {
                        sendFailure(context, statusReceiver, RollbackManager.STATUS_FAILURE,
                                "Backup copy of package: "
                                        + pkgRollbackInfo.getPackageName() + " is inaccessible");
                        return;
                    }

                    for (File packageCodePath : packageCodePaths) {
                        try (ParcelFileDescriptor fd = ParcelFileDescriptor.open(packageCodePath,
                                ParcelFileDescriptor.MODE_READ_ONLY)) {
                            final long token = Binder.clearCallingIdentity();
                            try {
                                session.write(packageCodePath.getName(), 0,
                                        packageCodePath.length(),
                                        fd);
                            } finally {
                                Binder.restoreCallingIdentity(token);
                            }
                        }
                    }
                    parentSession.addChildSessionId(sessionId);
                }

                final LocalIntentReceiver receiver = new LocalIntentReceiver(
                        (Intent result) -> {
                            int status = result.getIntExtra(PackageInstaller.EXTRA_STATUS,
                                    PackageInstaller.STATUS_FAILURE);
                            if (status != PackageInstaller.STATUS_SUCCESS) {
                                // Committing the rollback failed, but we still have all the info we
                                // need to try rolling back again, so restore the rollback state to
                                // how it was before we tried committing.
                                // TODO: Should we just kill this rollback if commit failed?
                                // Why would we expect commit not to fail again?
                                // TODO: Could this cause a rollback to be resurrected
                                // if it should otherwise have expired by now?
                                synchronized (mLock) {
                                    mState = ROLLBACK_STATE_AVAILABLE;
                                    mRestoreUserDataInProgress = false;
                                    info.setCommittedSessionId(-1);
                                }
                                sendFailure(context, statusReceiver,
                                        RollbackManager.STATUS_FAILURE_INSTALL,
                                        "Rollback downgrade install failed: "
                                                + result.getStringExtra(
                                                PackageInstaller.EXTRA_STATUS_MESSAGE));
                                return;
                            }

                            synchronized (mLock) {
                                if (!isStaged()) {
                                    // All calls to restoreUserData should have
                                    // completed by now for a non-staged install.
                                    mRestoreUserDataInProgress = false;
                                }

                                info.getCausePackages().addAll(causePackages);
                                RollbackStore.deletePackageCodePaths(this);
                                RollbackStore.saveRollback(this);
                            }

                            // Send success.
                            try {
                                final Intent fillIn = new Intent();
                                fillIn.putExtra(
                                        RollbackManager.EXTRA_STATUS,
                                        RollbackManager.STATUS_SUCCESS);
                                statusReceiver.sendIntent(context, 0, fillIn, null, null);
                            } catch (IntentSender.SendIntentException e) {
                                // Nowhere to send the result back to, so don't bother.
                            }

                            Intent broadcast = new Intent(Intent.ACTION_ROLLBACK_COMMITTED);

                            for (UserInfo userInfo : UserManager.get(context).getUsers(true)) {
                                context.sendBroadcastAsUser(broadcast,
                                        userInfo.getUserHandle(),
                                        Manifest.permission.MANAGE_ROLLBACKS);
                            }
                        }
                );

                mState = ROLLBACK_STATE_COMMITTED;
                info.setCommittedSessionId(parentSessionId);
                mRestoreUserDataInProgress = true;
                parentSession.commit(receiver.getIntentSender());
            } catch (IOException e) {
                Slog.e(TAG, "Rollback failed", e);
                sendFailure(context, statusReceiver, RollbackManager.STATUS_FAILURE,
                        "IOException: " + e.toString());
            }
        }
    }

在執行完後,會將mState置為ROLLBACK_STATE_COMMITTED;

mState = ROLLBACK_STATE_COMMITTED;

所以,當我們檢查初始化狀態為

ROLLBACK_STATE_ENABLING,

開機後的狀態為

ROLLBACK_STATE_AVAILABLE,

執行完crash,rollback後的狀態為

ROLLBACK_STATE_COMMITTED

這個功能的實現和驗證就成功了,實現應用回滾的具體操作。

開機後的狀態為

ROLLBACK_STATE_AVAILABLE,

執行完crash,rollback後的狀態為

ROLLBACK_STATE_COMMITTED

這個功能的實現和驗證就成功了,實現應用回滾的具體操作。

相關文章