参考了其他大神的一些方法,
1.简介:
Android的每个应用程序,都有自己的可控的目录。在Setting/Application info里面,可以看到每个应用程序,都有Clear data和Clear cache选项。具体这些目录在哪里呢?用adb连接上设备。如果是连接真实设备,需要有设备的root权限。
在/data/data目录下,每个应用程序都有自己的目录,目录名就是应用程序在AndroidManifest.xml文件中定义的包。每个应用程序的代码,对自己的目录是有绝对的控制权限的。在每个目录下,一般有如下几个子目录:
databases : 存放数据库
cache : 存放缓存数据
files : 存放应用程序自己控制的文件
lib : 存放使用的包
应用安装的流程及路径
应用安装涉及到如下几个目录:
system/app 系统自带的应用程序,无法删除
data/app用户程序安装的目录,有删除权限。安装时把apk文件复制到此目录。
data/data存放应用程序的数据
Data/dalvik-cache将apk中的dex文件安装到dalvik-cache目录下(dex文件是dalvik虚拟机的可执行文件,其大小约为原始apk文件大小的四分之一)
安装过程:复制APK安装包到data/app目录下,解压并扫描安装包,把dex文件(Dalvik字节码)保存到dalvik-cache目录,并在data/data目录下创建对应的应用数据目录。
卸载过程:删除安装过程中在上述三个目录下创建的文件及目录。主要步骤如下:
1.从PMS的内部结构上删除acitivity、service、provider等信息
2.删除code、library和resource等信息
3.调用installd删除/data/data/packageName以及/data/dalvik-cache下面的文件
4.更新Settings中的package信息
2.具体分析
当我们在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) {
-
- }
- }
通过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.");
- }
- }
- }
- });
- }
在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,
- true,
- true,
- false,
- 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 {
-
- 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]));
- }
-
- 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) {
-
- 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);
-
- }
- }
- }
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]);
- }
-
- 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。