1. 程式人生 > >Android PackageManagerService分析三:解除安裝APK

Android PackageManagerService分析三:解除安裝APK

這一章我們介紹APK的解除安裝過程,從前一章分析安裝APK的過程,我們應該大致瞭解這裡的解除安裝的過程如下:

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

2.刪除code、library和resource等資訊

3.呼叫installd刪除/data/data/packageName以及/data/dalvik-cache下面的檔案

4.更新Settings中的package資訊

當我們在Settings中的應用頁面找到一個安裝了的應用程式,並點選解除安裝後,就會發送一個Intent給UninstallerActivity,在UninstallerActivity最後會啟動UninstallAppProgress的initView方法,並呼叫如下解除安裝函式:

        getPackageManager().deletePackage(mAppInfo.packageName, observer,
                mAllUsers ? PackageManager.DELETE_ALL_USERS : 0);

上面的mAllUsers預設是false。getPackageManager()函式的實現在ContextImpl.java,它最後會呼叫到ApplicantPackageManger.java的deletePackage方法:
    public void deletePackage(String packageName, IPackageDeleteObserver observer, int flags) {
        try {
            mPM.deletePackageAsUser(packageName, observer, UserHandle.myUserId(), flags);
        } catch (RemoteException e) {
            // Should never happen!
        }
    }

通過Binder呼叫,我們來看PMS中的deletePackageAsUser方法:
    public void deletePackageAsUser(final String packageName,
                                    final IPackageDeleteObserver observer,
                                    final int userId, final int flags) {
        mContext.enforceCallingOrSelfPermission(
                android.Manifest.permission.DELETE_PACKAGES, null);
        final int uid = Binder.getCallingUid();
        if (isUserRestricted(userId, UserManager.DISALLOW_UNINSTALL_APPS)) {
            try {
                observer.packageDeleted(packageName, PackageManager.DELETE_FAILED_USER_RESTRICTED);
            } catch (RemoteException re) {
            }
            return;
        }

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

在deletePackageAsUser方法中,首先做許可權檢查,然後就呼叫deletePackageX方法去執行解除安裝任務:

    private int deletePackageX(String packageName, int userId, int flags) {
        final PackageRemovedInfo info = new PackageRemovedInfo();
        final boolean res;

        boolean removedForAllUsers = false;
        boolean systemUpdate = false;

        int[] allUsers;
        boolean[] perUserInstalled;
        synchronized (mPackages) {
            PackageSetting ps = mSettings.mPackages.get(packageName);
            allUsers = sUserManager.getUserIds();
            perUserInstalled = new boolean[allUsers.length];
            for (int i = 0; i < allUsers.length; i++) {
                perUserInstalled[i] = ps != null ? ps.getInstalled(allUsers[i]) : false;
            }
        }

        synchronized (mInstallLock) {
            res = deletePackageLI(packageName,
                    (flags & PackageManager.DELETE_ALL_USERS) != 0
                            ? UserHandle.ALL : new UserHandle(userId),
                    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);
        }

        if (res) {
            info.sendBroadcast(true, systemUpdate, removedForAllUsers);

        }
        Runtime.getRuntime().gc();
        if (info.args != null) {
            synchronized (mInstallLock) {
                info.args.doPostDeleteLI(true);
            }
        }

        return res ? PackageManager.DELETE_SUCCEEDED : PackageManager.DELETE_FAILED_INTERNAL_ERROR;
    }

deletePackageX在這裡我們只考慮當前只有一個user的情況,來看deletePackageLI的實現:
    private boolean deletePackageLI(String packageName, UserHandle user,
            boolean deleteCodeAndResources, int[] allUserHandles, boolean[] perUserInstalled,
            int flags, PackageRemovedInfo outInfo,
            boolean writeSettings) {
        PackageSetting ps;
        boolean dataOnly = false;
        int removeUser = -1;
        int appId = -1;
        synchronized (mPackages) {
            ps = mSettings.mPackages.get(packageName);
            if (ps == null) {
                Slog.w(TAG, "Package named '" + packageName + "' doesn't exist.");
                return false;
            }
            if ((!isSystemApp(ps) || (flags&PackageManager.DELETE_SYSTEM_APP) != 0) && user != null
                    && user.getIdentifier() != UserHandle.USER_ALL) {
                ps.setUserState(user.getIdentifier(),
                        COMPONENT_ENABLED_STATE_DEFAULT,
                        false, //installed
                        true,  //stopped
                        true,  //notLaunched
                        false, //blocked
                        null, null, null);
                if (!isSystemApp(ps)) {
                    if (ps.isAnyInstalled(sUserManager.getUserIds())) {
                       
                } else {
                    removeUser = user.getIdentifier();
                    appId = ps.appId;
                    mSettings.writePackageRestrictionsLPr(removeUser);
                }
            }
        }

        boolean ret = false;
        mSettings.mKeySetManager.removeAppKeySetData(packageName);
        if (isSystemApp(ps)) {
            ret = deleteSystemPackageLI(ps, allUserHandles, perUserInstalled,
                    flags, outInfo, writeSettings);
        } else {
            // Kill application pre-emptively especially for apps on sd.
            killApplication(packageName, ps.appId, "uninstall pkg");
            ret = deleteInstalledPackageLI(ps, deleteCodeAndResources, flags,
                    allUserHandles, perUserInstalled,
                    outInfo, writeSettings);
        }

        return ret;
    }

在deletePackageLI函式中根據是否是systemApp呼叫不同的流程,如果是systemApp,則呼叫deleteSystemPackageLI完成解除安裝;如果非systemApp,則呼叫deleteInstalledPackageLI完成解除安裝,當然在解除安裝之前,首先會呼叫AMS的killApplication方法先讓這個APP停止執行。我們主要介紹非systemApp的解除安裝過程,來看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;
        }

        removePackageDataLI(ps, allUserHandles, perUserInstalled, outInfo, flags, writeSettings);

        if (deleteCodeAndResources && (outInfo != null)) {
            outInfo.args = createInstallArgs(packageFlagsToInstallFlags(ps), ps.codePathString,
                    ps.resourcePathString, ps.nativeLibraryPathString);
        }
        return true;
    }

在deleteInstalledPackageLI方法中,分為兩步去解除安裝應用:第一步刪除/data/data下面的資料目錄,並從PMS的內部資料結構上清除當前解除安裝的package資訊;第二步就刪除code和resource檔案。我們先來看第一步:
    private void removePackageDataLI(PackageSetting ps,
            int[] allUserHandles, boolean[] perUserInstalled,
            PackageRemovedInfo outInfo, int flags, boolean writeSettings) {
        String packageName = ps.name;
        removePackageLI(ps, (flags&REMOVE_CHATTY) != 0);
        final PackageSetting deletedPs;

        synchronized (mPackages) {
            deletedPs = mSettings.mPackages.get(packageName);
            if (outInfo != null) {
                outInfo.removedPackage = packageName;
                outInfo.removedUsers = deletedPs != null
                        ? deletedPs.queryInstalledUsers(sUserManager.getUserIds(), true)
                        : null;
            }
        }
        if ((flags&PackageManager.DELETE_KEEP_DATA) == 0) {
            removeDataDirsLI(packageName);
            schedulePackageCleaning(packageName, UserHandle.USER_ALL, true);
        }

removePackageDataLI用於刪除應用的/data/data資料目錄,並且從PMS內部資料結構裡面清除package的資訊。首先呼叫removePackageLI從PMS內部的資料結構上刪除要解除安裝的package資訊:

    void removePackageLI(PackageSetting ps, boolean chatty) {
        synchronized (mPackages) {
            mPackages.remove(ps.name);
            if (ps.codePathString != null) {
                mAppDirs.remove(ps.codePathString);
            }

            final PackageParser.Package pkg = ps.pkg;
            if (pkg != null) {
                cleanPackageDataStructuresLILPw(pkg, chatty);
            }
        }
    }

cleanPackageDataStructuresLILPw用於將package的providers、services、receivers、activities等資訊去PMS的全域性資料結構上移除,這部分程式碼比較簡單。如果沒有設定DELETE_KEEP_DATA這個flag,就會首先呼叫removeDataDirsLI去刪除/data/data下面的目錄:
    private int removeDataDirsLI(String packageName) {
        int[] users = sUserManager.getUserIds();
        int res = 0;
        for (int user : users) {
            int resInner = mInstaller.remove(packageName, user);
            if (resInner < 0) {
                res = resInner;
            }
        }

        final File nativeLibraryFile = new File(mAppLibInstallDir, packageName);
        NativeLibraryHelper.removeNativeBinariesFromDirLI(nativeLibraryFile);
        if (!nativeLibraryFile.delete()) {
            Slog.w(TAG, "Couldn't delete native library directory " + nativeLibraryFile.getPath());
        }

        return res;
    }

這裡首先呼叫installd的remove方法去刪除/data/data下面的目錄。然後去刪除/data/app-lib下面的應用程式的library資訊,但因為這裡的nativeLibraryFile為/data/app-lib/packageName,和前面介紹的APK安裝過程中的目錄/data/app-lib/packageName-num不一樣,所以實際上,這裡並沒有真正的去刪除library目錄。先來看installd的remove方法:
static int do_remove(char **arg, char reply[REPLY_MAX])
{
    return uninstall(arg[0], atoi(arg[1])); /* pkgname, userid */
}

int uninstall(const char *pkgname, userid_t userid)
{
    char pkgdir[PKG_PATH_MAX];

    if (create_pkg_path(pkgdir, pkgname, PKG_DIR_POSTFIX, userid))
        return -1;

    return delete_dir_contents(pkgdir, 1, NULL);
}

int delete_dir_contents(const char *pathname,
                        int also_delete_dir,
                        const char *ignore)
{
    int res = 0;
    DIR *d;

    d = opendir(pathname);
    if (d == NULL) {
        ALOGE("Couldn't opendir %s: %s\n", pathname, strerror(errno));
        return -errno;
    }
    res = _delete_dir_contents(d, ignore);
    closedir(d);
    if (also_delete_dir) {
        if (rmdir(pathname)) {
            ALOGE("Couldn't rmdir %s: %s\n", pathname, strerror(errno));
            res = -1;
        }
    }
    return res;
}

create_pkg_path方法構造/data/data/packageName的檔案路徑名,然後呼叫delete_dir_contents來刪除檔案內容以及目錄,前面介紹過,/data/data/packageName的檔案其實都是符號連結,所以_delete_dir_contents的實現中都是呼叫unlinkat去刪除這些符號連結。回到removePackageDataLI中,接著呼叫schedulePackageCleaning來安排清理動作:
    void schedulePackageCleaning(String packageName, int userId, boolean andCode) {
        mHandler.sendMessage(mHandler.obtainMessage(START_CLEANING_PACKAGE,
                userId, andCode ? 1 : 0, packageName));
    }

這裡向PackageHandler傳送START_CLEANING_PACKAGE訊息,PMS會呼叫ContainService的函式去刪除/storage/sdcard0/Android/data和/storage/sdcard0/Android/media下面與package相關的檔案,有興趣可以去看一下這部分的code。接著來看removePackageDataLI方法:

        synchronized (mPackages) {
            if (deletedPs != null) {
                if ((flags&PackageManager.DELETE_KEEP_DATA) == 0) {
                    if (outInfo != null) {
                        outInfo.removedAppId = mSettings.removePackageLPw(packageName);
                    }
                    if (deletedPs != null) {
                        updatePermissionsLPw(deletedPs.name, null, 0);
                        if (deletedPs.sharedUser != null) {
                            // remove permissions associated with package
                            mSettings.updateSharedUserPermsLPw(deletedPs, mGlobalGids);
                        }
                    }
                    clearPackagePreferredActivitiesLPw(deletedPs.name, UserHandle.USER_ALL);
                }
            }

            if (writeSettings) {
                mSettings.writeLPr();
            }
        }
        if (outInfo != null) {
            removeKeystoreDataIfNeeded(UserHandle.USER_ALL, outInfo.removedAppId);
        }
    }

這裡首先從Settings中刪除PackageSettings的資訊:
    int removePackageLPw(String name) {
        final PackageSetting p = mPackages.get(name);
        if (p != null) {
            mPackages.remove(name);
            if (p.sharedUser != null) {
                p.sharedUser.removePackage(p);
                if (p.sharedUser.packages.size() == 0) {
                    mSharedUsers.remove(p.sharedUser.name);
                    removeUserIdLPw(p.sharedUser.userId);
                    return p.sharedUser.userId;
                }
            } else {
                removeUserIdLPw(p.appId);
                return p.appId;
            }
        }
        return -1;
    }

removePackageLPw首先從mPackages這個map中刪除PackageSettings資訊,如果不存在sharedUser,則從mUserIds這個陣列中刪除對應的Package UID資訊;如果存在sharedUser,則首先檢查這個sharedUser是否所有的package都已經被解除安裝了,如果都被解除安裝了,這個sharedUser也就可以刪除。然後removePackageDataLI呼叫updatePermissionsLPw去檢查mPermissionTrees和mPermissions兩個陣列中的許可權是否是被刪除的Package提供,如果有,則刪除。Settings的updateSharedUserPermsLPw方法用於清除sharedUser不用的gid資訊,防止許可權洩露:
    void updateSharedUserPermsLPw(PackageSetting deletedPs, int[] globalGids) {
        SharedUserSetting sus = deletedPs.sharedUser;

        for (String eachPerm : deletedPs.pkg.requestedPermissions) {
            boolean used = false;
            if (!sus.grantedPermissions.contains(eachPerm)) {
                continue;
            }
            for (PackageSetting pkg:sus.packages) {
                if (pkg.pkg != null &&
                        !pkg.pkg.packageName.equals(deletedPs.pkg.packageName) &&
                        pkg.pkg.requestedPermissions.contains(eachPerm)) {
                    used = true;
                    break;
                }
            }
            if (!used) {
                sus.grantedPermissions.remove(eachPerm);
            }
        }
        int newGids[] = globalGids;
        for (String eachPerm : sus.grantedPermissions) {
            BasePermission bp = mPermissions.get(eachPerm);
            if (bp != null) {
                newGids = PackageManagerService.appendInts(newGids, bp.gids);
            }
        }
        sus.gids = newGids;
    }

迴圈的從要被解除安裝的Package所在的sharedUser組中找被申請的許可權是否還被同一組的其它package使用,如果沒有使用者,就從sharedUser的grantedPermissions刪除。clearPackagePreferredActivitiesLPw與AMS相關,我們留到以後再來介紹。在removePackageDataLI方法最好呼叫Settings.writeLPr()方法將改動的資訊寫到Package.xml中。到這裡,我們前面所說的deleteInstalledPackageLI方法中的第一步已經完成,來看第二部分:

        if (deleteCodeAndResources && (outInfo != null)) {
            outInfo.args = createInstallArgs(packageFlagsToInstallFlags(ps), ps.codePathString,
                    ps.resourcePathString, ps.nativeLibraryPathString);
        }

    private InstallArgs createInstallArgs(int flags, String fullCodePath, String fullResourcePath,
            String nativeLibraryPath) {
        final boolean isInAsec;
        if (installOnSd(flags)) {
            isInAsec = true;
        } else if (installForwardLocked(flags)
                && !fullCodePath.startsWith(mDrmAppPrivateInstallDir.getAbsolutePath())) {
            isInAsec = true;
        } else {
            isInAsec = false;
        }

        if (isInAsec) {
            return new AsecInstallArgs(fullCodePath, fullResourcePath, nativeLibraryPath,
                    installOnSd(flags), installForwardLocked(flags));
        } else {
            return new FileInstallArgs(fullCodePath, fullResourcePath, nativeLibraryPath);
        }
    }

這裡根據安裝目錄的不同,分別構造FileInstallArgs和AsecInstallArgs來完成code和resource資源的清除。這裡我們主要介紹解除安裝內部儲存空間上面的APK,來看FileInstallArgs的doPostDeleteLI方法:

        boolean doPostDeleteLI(boolean delete) {
            cleanUpResourcesLI();
            return true;
        }

        void cleanUpResourcesLI() {
            String sourceDir = getCodePath();
            if (cleanUp()) {
                int retCode = mInstaller.rmdex(sourceDir);
                if (retCode < 0) {
                    Slog.w(TAG, "Couldn't remove dex file for package: "
                            +  " at location "
                            + sourceDir + ", retcode=" + retCode);
                    // we don't consider this to be a failure of the core package deletion
                }
            }
        }

cleanUpResourcesLI方法中首先呼叫cleanUp方法去刪除code、resource以及library檔案:
        private boolean cleanUp() {
            boolean ret = true;
            String sourceDir = getCodePath();
            String publicSourceDir = getResourcePath();
            if (sourceDir != null) {
                File sourceFile = new File(sourceDir);
                if (!sourceFile.exists()) {
                    Slog.w(TAG, "Package source " + sourceDir + " does not exist.");
                    ret = false;
                }

                sourceFile.delete();
            }
            if (publicSourceDir != null && !publicSourceDir.equals(sourceDir)) {
                final File publicSourceFile = new File(publicSourceDir);
                if (!publicSourceFile.exists()) {
                    Slog.w(TAG, "Package public source " + publicSourceFile + " does not exist.");
                }
                if (publicSourceFile.exists()) {
                    publicSourceFile.delete();
                }
            }

            if (libraryPath != null) {
                File nativeLibraryFile = new File(libraryPath);
                NativeLibraryHelper.removeNativeBinariesFromDirLI(nativeLibraryFile);
                if (!nativeLibraryFile.delete()) {
                    Slog.w(TAG, "Couldn't delete native library directory " + libraryPath);
                }
            }

            return ret;
        }

然後cleanUpResourcesLI呼叫installd的rmdex方法去刪除存在/data/dalvik-cache檔案:
static int do_rm_dex(char **arg, char reply[REPLY_MAX])
{
    return rm_dex(arg[0]); /* pkgname */
}

int rm_dex(const char *path)
{
    char dex_path[PKG_PATH_MAX];

    if (validate_apk_path(path)) return -1;
    if (create_cache_path(dex_path, path)) return -1;

    ALOGV("unlink %s\n", dex_path);
    if (unlink(dex_path) < 0) {
        ALOGE("Couldn't unlink %s: %s\n", dex_path, strerror(errno));
        return -1;
    } else {
        return 0;
    }
}

create_cache_path依據path構造/data/dalvik-cache下的檔案目錄,呼叫unlink去刪除檔案。到這裡解除安裝APK的deletePackageAsUser函式就已經分析完了。這時會通過observer把解除安裝結果返回給UninstallAppProgress。