这一章我们介绍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方法,并调用如下卸载函数:
[java] getPackageManager().deletePackage(mAppInfo.packageName, observer, mAllUsers ? PackageManager.DELETE_ALL_USERS : 0);
上面的mAllUsers默认是false。getPackageManager()函数的实现在ContextImpl.java,它最后会调用到ApplicantPackageManger.java的deletePackage方法:
[java] 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方法:
[java] 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方法去执行卸载任务:
[java] 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的实现:
[java] view plaincopy 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方法的实现:
[java] view plaincopy 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文件。我们先来看第一步:
[java] view plaincopy 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信息:
[java] view plaincopy 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下面的目录:
[java] view plaincopy 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方法:
[cpp] view plaincopy 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来安排清理动作:
[java] view plaincopy 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方法:
[java] view plaincopy 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的信息:
[java] view plaincopy 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信息,防止权限泄露:
[java] view plaincopy 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方法中的第一步已经完成,来看第二部分:
[java] view plaincopy 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方法:
[java] view plaincopy 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文件:
[java] view plaincopy 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文件:
[cpp] view plaincopy 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把卸载结果返回给UninstallAppProgre