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。