1. 程式人生 > >apk解除安裝分析

apk解除安裝分析

1, apk解除安裝

和安裝APK過程相對,解除安裝apk過程如下,

1,從PMS的內部結構上刪除acitivity、service、provider等資訊

2,更新Settings中的package資訊

3.刪除code、resource等資訊

4.刪除dex檔案

Apk安裝時,一般都會走PackageManagerService 中的 installPackage 方法。

相反,解除安裝apk的時候一般會呼叫PMS中的deletePackage方法。

2, apk解除安裝流程

流程圖如下,


PMS的deletePackage方法會另開一個子執行緒執行解除安裝apk。

mHandler.post(new Runnable() {
            public void run() {
                mHandler.removeCallbacks(this);
                final int returnCode = deletePackageX(packageName, userId, flags);
                if (observer != null) {
                    try {
                        observer.onPackageDeleted(packageName, returnCode, null);
                    } catch (RemoteException e) {
                        Log.i(TAG, "Observer no longer exists.");
                    } //end catch
                } //end if
            } //end run
        });

解除安裝完成之後,如果註冊了觀察者,就會回撥onPackageDeleted方法。
synchronized (mInstallLock) {
        if (DEBUG_REMOVE) Slog.d(TAG, "deletePackageX: pkg=" + packageName + " user=" + userId);
            res = deletePackageLI(packageName, removeForUser,
                    true, allUsers, perUserInstalled,
                    flags | REMOVE_CHATTY, info, true);
            systemUpdate = info.isRemovedPackageSystemUpdate;
            if (res && !systemUpdate && mPackages.get(packageName) == null) {
                removedForAllUsers = true;
            }
            if (DEBUG_REMOVE) Slog.d(TAG, "delete res: systemUpdate=" + systemUpdate
                    + " removedForAllUsers=" + removedForAllUsers);
        }

deletePackageX直接呼叫deletePackageLI方法, deletePackageLI主要方法如下,

boolean ret = false;
        if (isSystemApp(ps)) {
            if (DEBUG_REMOVE) Slog.d(TAG, "Removing system package:" + ps.name);
            // When an updated system application is deleted we delete the existing resources as well and
            // fall back to existing code in system partition
            ret = deleteSystemPackageLI(ps, allUserHandles, perUserInstalled,
                    flags, outInfo, writeSettings);
        } else {
            if (DEBUG_REMOVE) Slog.d(TAG, "Removing non-system package:" + ps.name);
            // Kill application pre-emptively especially for apps on sd.
            killApplication(packageName, ps.appId, "uninstall pkg");
            ret = deleteInstalledPackageLI(ps, deleteCodeAndResources, flags,
                    allUserHandles, perUserInstalled,
                    outInfo, writeSettings);
        }

如果是系統apk, 則呼叫deleteSystemPackageLI完成解除安裝(該方法其實也是呼叫deleteInstalledPackageLI

方法完成解除安裝);如果不是系統apk, 則呼叫deleteInstalledPackageLI完成解除安裝。

deleteInstalledPackageLI方法如下,

private boolean deleteInstalledPackageLI(PackageSetting ps,
            boolean deleteCodeAndResources, int flags,
            int[] allUserHandles, boolean[] perUserInstalled,
            PackageRemovedInfo outInfo, boolean writeSettings) {
        if (outInfo != null) {
            outInfo.uid = ps.appId;
        }

        // Delete package data from internal structures and also remove data if flag is set
        removePackageDataLI(ps, allUserHandles, perUserInstalled, outInfo, flags, writeSettings);

        // Delete application code and resources
        if (deleteCodeAndResources && (outInfo != null)) {
            outInfo.args = createInstallArgsForExisting(packageFlagsToInstallFlags(ps),
                    ps.codePathString, ps.resourcePathString, getAppDexInstructionSets(ps));
            if (DEBUG_SD_INSTALL) Slog.i(TAG, "args=" + outInfo.args);
        }
        return true;
    }

在deleteInstalledPackageLI方法中,主要分為2個步驟:

第一步呼叫removePackageDataLI方法刪除/data/data下面的資料目錄,

並從PMS的內部資料結構上清除當前解除安裝的package資訊;

第二步呼叫createInstallArgsForExisting方法刪除code和resource檔案。

2.1 removePackageDataLI

removePackageDataLI呼叫流程圖如下,


2.1.1 removePackageLI

removePackageLI方法如下,

void removePackageLI(PackageSetting ps, boolean chatty) {
        if (DEBUG_INSTALL) {
            if (chatty)
                Log.d(TAG, "Removing package " + ps.name);
        }

        // writer
        synchronized (mPackages) {
            mPackages.remove(ps.name);
            final PackageParser.Package pkg = ps.pkg;
            if (pkg != null) {
                cleanPackageDataStructuresLILPw(pkg, chatty);
            }
        }
    }

首先刪除包名,然後呼叫cleanPackageDataStructuresLILPw方法清除資料結構中的四大元件以及相關資訊。

cleanPackageDataStructuresLILPw刪除receiver程式碼如下,

N = pkg.receivers.size();
        r = null;
        for (i=0; i<N; i++) {
            PackageParser.Activity a = pkg.receivers.get(i);
            mReceivers.removeActivity(a, "receiver");
            if (DEBUG_REMOVE && chatty) {
                if (r == null) {
                    r = new StringBuilder(256);
                } else {
                    r.append(' ');
                }
                r.append(a.info.name);
            }
        }

2.1.2 removeDataDirsLI

private int removeDataDirsLI(String volumeUuid, String packageName) {
        int[] users = sUserManager.getUserIds();
        int res = 0;
        for (int user : users) {
            int resInner = mInstaller.remove(volumeUuid, packageName, user);
            if (resInner < 0) {
                res = resInner;
            }
        }

        return res;
    }

呼叫Installer的remove方法去刪除/data/data下面的目錄

然後呼叫schedulePackageCleaning方法進一步清理。

最後呼叫Settings的updateSharedUserPermsLPw方法更新資訊。
for (int userId : UserManagerService.getInstance().getUserIds()) {
                            final int userIdToKill = mSettings.updateSharedUserPermsLPw(deletedPs,
                                    userId);
                            if (userIdToKill == UserHandle.USER_ALL
                                    || userIdToKill >= UserHandle.USER_OWNER) {
                                // If gids changed for this user, kill all affected packages.
                                mHandler.post(new Runnable() {
                                    @Override
                                    public void run() {
                                        // This has to happen with no lock held.
                                        killApplication(deletedPs.name, deletedPs.appId,
                                                KILL_APP_REASON_GIDS_CHANGED);
                                    }
                                });
                                break;
                            }
                        }

2.2 createInstallArgsForExisting

createInstallArgsForExisting方法根據安裝目錄的不同, 分別構造FileInstallArgs和AsecInstallArgs來完成code和resource資源的清除。

if (isInAsec) {
            return new AsecInstallArgs(codePath, instructionSets,
                    installOnExternalAsec(installFlags), installForwardLocked(installFlags));
        } else {
            return new FileInstallArgs(codePath, resourcePath, instructionSets);
        }

這裡只是構造了一個FileInstallArgs物件,如何呼叫的呢?

流程圖如下,


在deletePackageX的最後會呼叫FileInstallArgs的doPostDeleteLI方法。

if (info.args != null) {
            synchronized (mInstallLock) {
                if(info.args.isExternalAsec()){
                    Message msg = mHandler.obtainMessage(DEL_APK_FOR_EXTERNAL);
                    msg.obj = info.args;
                    mHandler.sendMessage(msg);
                }else
                {
                    info.args.doPostDeleteLI(true);
                }
            }
        }

FileInstallArgs和PackageRemovedInfo都是PMS的內部類。

cleanUpResourcesLI方法如下,

void cleanUpResourcesLI() {
            // Try enumerating all code paths before deleting
            List<String> allCodePaths = Collections.EMPTY_LIST;
            if (codeFile != null && codeFile.exists()) {
                try {
                    final PackageLite pkg = PackageParser.parsePackageLite(codeFile, 0);
                    allCodePaths = pkg.getAllCodePaths();
                } catch (PackageParserException e) {
                    // Ignored; we tried our best
                }
            }

            cleanUp();
            removeDexFiles(allCodePaths, instructionSets);
        }

Cleanup主要刪除code、resource等檔案。

private boolean cleanUp() {
            if (codeFile == null || !codeFile.exists()) {
                return false;
            }

            if (codeFile.isDirectory()) {
                mInstaller.rmPackageDir(codeFile.getAbsolutePath());
            } else {
                codeFile.delete();
            }

            if (resourceFile != null && !FileUtils.contains(codeFile, resourceFile)) {
                resourceFile.delete();
            }

            return true;
        }

removeDexFiles刪除Dex檔案。

private void removeDexFiles(List<String> allCodePaths, String[] instructionSets) {
        if (!allCodePaths.isEmpty()) {
            if (instructionSets == null) {
                throw new IllegalStateException("instructionSet == null");
            }
            String[] dexCodeInstructionSets = getDexCodeInstructionSets(instructionSets);
            for (String codePath : allCodePaths) {
                for (String dexCodeInstructionSet : dexCodeInstructionSets) {
                    int retCode = mInstaller.rmdex(codePath, dexCodeInstructionSet);
                    if (retCode < 0) {
                        Slog.w(TAG, "Couldn't remove dex file for package: "
                                + " at location " + codePath + ", retcode=" + retCode);
                        // we don't consider this to be a failure of the core package deletion
                    }
                }
            }
        }
    }

最後,在解除安裝apk之前,在deletePackageX方法中會呼叫killApplication方法,

PMS的killApplication方法如下,

private void killApplication(String pkgName, int appId, String reason) {
        // Request the ActivityManager to kill the process(only for existing packages)
        // so that we do not end up in a confused state while the user is still using the older
        // version of the application while the new one gets installed.
        IActivityManager am = ActivityManagerNative.getDefault();
        if (am != null) {
            try {
                am.killApplicationWithAppId(pkgName, appId, reason);
            } catch (RemoteException e) {
            }
        }
    }
顧名思義,該方法會殺死一個程序,呼叫的是AMS的killApplicationWithAppId方法。