安卓系统应用的卸载和恢复(非data分区)

本文最后更新于:2023年5月3日 晚上

安卓系统应用的卸载和恢复(非data分区)

Uninstall and Restore Android System Apps (non-data partition)

网上已经有很多预装应用卸载的方法了,但是他们基本都是设法在第一次开机的时候安装到data分区。

本文需要达到的目标是预装在system分区的应用能够被卸载,且能恢复安装。

命令行方法

安卓5.0开始可以针对用户卸载系统应用

卸载系统应用

1
adb shell pm uninstall -k --user 0 app包名

-k是是否需要保留数据,可以不加。

恢复系统应用

安卓7开始才可以通过命令恢复

1
adb shell cmd package install-existing app包名

代码方法

代码分析(基于目前aosp master分支最新代码)

卸载

com/android/server/pm/PackageManagerShellCommand.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
private int runUninstall() throws RemoteException {
final PrintWriter pw = getOutPrintWriter();
int flags = 0;
int userId = UserHandle.USER_ALL;
long versionCode = PackageManager.VERSION_CODE_HIGHEST;

String opt;
while ((opt = getNextOption()) != null) {
switch (opt) {
case "-k":
flags |= PackageManager.DELETE_KEEP_DATA;
break;
case "--user":
userId = UserHandle.parseUserArg(getNextArgRequired());
break;
case "--versionCode":
versionCode = Long.parseLong(getNextArgRequired());
break;
default:
pw.println("Error: Unknown option: " + opt);
return 1;
}
}

final String packageName = getNextArg();
if (packageName == null) {
pw.println("Error: package name not specified");
return 1;
}

/ if a split is specified, just remove it and not the whole package
ArrayList<String> splitNames = getRemainingArgs();
if (!splitNames.isEmpty()) {
return runRemoveSplits(packageName, splitNames);
}
// 不加--user是卸载所有用户的应用
if (userId == UserHandle.USER_ALL) {
flags |= PackageManager.DELETE_ALL_USERS;
}
final int translatedUserId =
translateUserId(userId, UserHandle.USER_SYSTEM, "runUninstall");
final LocalIntentReceiver receiver = new LocalIntentReceiver();
final PackageManagerInternal internal =
LocalServices.getService(PackageManagerInternal.class);

if (internal.isApexPackage(packageName)) {
internal.uninstallApex(
packageName, versionCode, translatedUserId, receiver.getIntentSender(), flags);
} else {
//如果删除某一个用户的应用,走这里
if ((flags & PackageManager.DELETE_ALL_USERS) == 0) {
final PackageInfo info = mInterface.getPackageInfo(packageName,
PackageManager.MATCH_STATIC_SHARED_AND_SDK_LIBRARIES, translatedUserId);
if (info == null) {
pw.println("Failure [not installed for " + translatedUserId + "]");
return 1;
}
final boolean isSystem =
(info.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0;
// If we are being asked to delete a system app for just one
// user set flag so it disables rather than reverting to system
// version of the app.
if (isSystem) {
flags |= PackageManager.DELETE_SYSTEM_APP;
}
}
mInterface.getPackageInstaller().uninstall(new VersionedPackage(packageName,
versionCode), null /*callerPackageName*/, flags,
receiver.getIntentSender(), translatedUserId);
}

final Intent result = receiver.getResult();
final int status = result.getIntExtra(PackageInstaller.EXTRA_STATUS,
PackageInstaller.STATUS_FAILURE);
if (status == PackageInstaller.STATUS_SUCCESS) {
pw.println("Success");
return 0;
} else {
pw.println("Failure ["
+ result.getStringExtra(PackageInstaller.EXTRA_STATUS_MESSAGE) + "]");
return 1;
}
}

android/content/pm/PackageInstaller.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
/**
* Uninstall the given package with a specific version code, removing it
* completely from the device. If the version code of the package
* does not match the one passed in the versioned package argument this
* method is a no-op. Use {@link PackageManager#VERSION_CODE_HIGHEST} to
* uninstall the latest version of the package.
* <p>
* This method is available to:
* <ul>
* <li>the current "installer of record" for the package
* <li>the device owner
* <li>the affiliated profile owner
* </ul>
*
* @param versionedPackage The versioned package to uninstall.
* @param statusReceiver Where to deliver the result.
*
* @see android.app.admin.DevicePolicyManager
*/
@RequiresPermission(anyOf = {
Manifest.permission.DELETE_PACKAGES,
Manifest.permission.REQUEST_DELETE_PACKAGES})
public void uninstall(@NonNull VersionedPackage versionedPackage,
@NonNull IntentSender statusReceiver) {
uninstall(versionedPackage, 0 /*flags*/, statusReceiver);
}

/**
* Uninstall the given package with a specific version code, removing it
* completely from the device. This method is only available to the current
* "installer of record" for the package. If the version code of the package
* does not match the one passed in the versioned package argument this
* method is a no-op. Use {@link PackageManager#VERSION_CODE_HIGHEST} to
* uninstall the latest version of the package.
*
* @param versionedPackage The versioned package to uninstall.
* @param flags Flags for uninstall.
* @param statusReceiver Where to deliver the result.
*
* @hide
*/
@RequiresPermission(anyOf = {
Manifest.permission.DELETE_PACKAGES,
Manifest.permission.REQUEST_DELETE_PACKAGES})
public void uninstall(@NonNull VersionedPackage versionedPackage, @DeleteFlags int flags,
@NonNull IntentSender statusReceiver) {
Objects.requireNonNull(versionedPackage, "versionedPackage cannot be null");
try {
//binder调用
mInstaller.uninstall(versionedPackage, mInstallerPackageName,
flags, statusReceiver, mUserId);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}

com/android/server/pm/PackageInstallerService.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
@Override
public void uninstall(VersionedPackage versionedPackage, String callerPackageName, int flags,
IntentSender statusReceiver, int userId) {
final Computer snapshot = mPm.snapshotComputer();
final int callingUid = Binder.getCallingUid();
snapshot.enforceCrossUserPermission(callingUid, userId, true, true, "uninstall");
if ((callingUid != Process.SHELL_UID) && (callingUid != Process.ROOT_UID)) {
mAppOps.checkPackage(callingUid, callerPackageName);
}

// Check whether the caller is device owner or affiliated profile owner, in which case we do
// it silently.
DevicePolicyManagerInternal dpmi =
LocalServices.getService(DevicePolicyManagerInternal.class);
final boolean canSilentlyInstallPackage =
dpmi != null && dpmi.canSilentlyInstallPackage(callerPackageName, callingUid);

final PackageDeleteObserverAdapter adapter = new PackageDeleteObserverAdapter(mContext,
statusReceiver, versionedPackage.getPackageName(),
canSilentlyInstallPackage, userId);
if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.DELETE_PACKAGES)
== PackageManager.PERMISSION_GRANTED) {
// Sweet, call straight through!
mPm.deletePackageVersioned(versionedPackage, adapter.getBinder(), userId, flags);
} else if (canSilentlyInstallPackage) {
// Allow the device owner and affiliated profile owner to silently delete packages
// Need to clear the calling identity to get DELETE_PACKAGES permission
final long ident = Binder.clearCallingIdentity();
try {
mPm.deletePackageVersioned(versionedPackage, adapter.getBinder(), userId, flags);
} finally {
Binder.restoreCallingIdentity(ident);
}
DevicePolicyEventLogger
.createEvent(DevicePolicyEnums.UNINSTALL_PACKAGE)
.setAdmin(callerPackageName)
.write();
} else {
ApplicationInfo appInfo = snapshot.getApplicationInfo(callerPackageName, 0, userId);
if (appInfo.targetSdkVersion >= Build.VERSION_CODES.P) {
mContext.enforceCallingOrSelfPermission(Manifest.permission.REQUEST_DELETE_PACKAGES,
null);
}

// Take a short detour to confirm with user
final Intent intent = new Intent(Intent.ACTION_UNINSTALL_PACKAGE);
intent.setData(Uri.fromParts("package", versionedPackage.getPackageName(), null));
intent.putExtra(PackageInstaller.EXTRA_CALLBACK, adapter.getBinder().asBinder());
adapter.onUserActionRequired(intent);
}
}

com/android/server/pm/IPackageManagerBase.java

1
2
3
4
5
6
7
8
@Override
@Deprecated
public final void deletePackageAsUser(String packageName, int versionCode,
IPackageDeleteObserver observer, int userId, int flags) {
deletePackageVersioned(new VersionedPackage(packageName, versionCode),
new PackageManager.LegacyPackageDeleteObserver(observer).getBinder(), userId,
flags);
}
恢复安装

com/android/server/pm/PackageManagerShellCommand.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
private int runInstallExisting() throws RemoteException {
final PrintWriter pw = getOutPrintWriter();
int userId = UserHandle.USER_CURRENT;
int installFlags = PackageManager.INSTALL_ALL_WHITELIST_RESTRICTED_PERMISSIONS;
String opt;
boolean waitTillComplete = false;
while ((opt = getNextOption()) != null) {
switch (opt) {
case "--user":
userId = UserHandle.parseUserArg(getNextArgRequired());
break;
case "--ephemeral":
case "--instant":
installFlags |= PackageManager.INSTALL_INSTANT_APP;
installFlags &= ~PackageManager.INSTALL_FULL_APP;
break;
case "--full":
installFlags &= ~PackageManager.INSTALL_INSTANT_APP;
installFlags |= PackageManager.INSTALL_FULL_APP;
break;
case "--wait":
waitTillComplete = true;
break;
case "--restrict-permissions":
installFlags &= ~PackageManager.INSTALL_ALL_WHITELIST_RESTRICTED_PERMISSIONS;
break;
default:
pw.println("Error: Unknown option: " + opt);
return 1;
}
}

final String packageName = getNextArg();
if (packageName == null) {
pw.println("Error: package name not specified");
return 1;
}
final int translatedUserId =
translateUserId(userId, UserHandle.USER_NULL, "runInstallExisting");

int installReason = PackageManager.INSTALL_REASON_UNKNOWN;
try {
if (waitTillComplete) {
final LocalIntentReceiver receiver = new LocalIntentReceiver();
final IPackageInstaller installer = mInterface.getPackageInstaller();
pw.println("Installing package " + packageName + " for user: " + translatedUserId);
installer.installExistingPackage(packageName, installFlags, installReason,
receiver.getIntentSender(), translatedUserId, null);
final Intent result = receiver.getResult();
final int status = result.getIntExtra(PackageInstaller.EXTRA_STATUS,
PackageInstaller.STATUS_FAILURE);
pw.println("Received intent for package install");
return status == PackageInstaller.STATUS_SUCCESS ? 0 : 1;
}
//走这里
final int res = mInterface.installExistingPackageAsUser(packageName, translatedUserId,
installFlags, installReason, null);
if (res == PackageManager.INSTALL_FAILED_INVALID_URI) {
throw new NameNotFoundException("Package " + packageName + " doesn't exist");
}
pw.println("Package " + packageName + " installed for user: " + translatedUserId);
return 0;
} catch (RemoteException | NameNotFoundException e) {
pw.println(e.toString());
return 1;
}
}

系统应用反射调用接口进行卸载和恢复

虽然命令从安卓5.0才开始支持,但接口应该是从安卓4.2(支持多用户)就开始支持了:

1
2
3
4
5
6
7
8
9
10
11
//安卓8以前
public void deletePackageAsUser(final String packageName,
final IPackageDeleteObserver observer,
final int userId, final int flags)

public int installExistingPackageAsUser(String packageName, int userId)

//安卓8开始
public void deletePackageAsUser(String packageName, int versionCode, IPackageDeleteObserver observer, int userId, int flags)

public int installExistingPackageAsUser(String packageName, int userId, int installFlags,int installReason)

怎么做

系统应用可以通过android.content.pm.IPackageManager$Stub反射分别获得deletePackageAsUser和installExistingPackageAsUser,前者可以卸载,后者可以恢复安装。恢复安装之前可以通过packageManager.getApplicationInfo判断系统是否还存在安装包,否则可以跳应用商店进行安装。

注意:

1、deletePackageAsUser会清除数据,但不会卸载更新。即如果系统应用更新后,会存在两个apk,一个在system分区,一个在data分区。

如果要删除data下的更新,可以先调用卸载接口再调用deletePackageAsUser。

2、/data/dalvik-cache/下面会残留dex文件,可以扫描删除。


安卓系统应用的卸载和恢复(非data分区)
https://iwesley.top/article/5867f171/
作者
Wesley
发布于
2023年5月3日
许可协议