在 RK 平台 Android 7 项目中,因为客户需要支持 APK 静默安装/卸载,只能从 Framework 层下手做一套完整方案。
网上常见做法基本都是直接调用 pm 命令。实测下来:
- pm install :基本没有问题,可以绕过“未知来源”提示完成安装
- pm uninstall :在应用内调用时会抛异常
最终还是回到系统源代码,从 Framework 层分析调用链并做了小改动,实现了可用的静默安装 / 卸载能力。
先看系统自带的卸载行为:从桌面拖动应用到“卸载”区域,本质上最终会调用 PackageManager.deletePackage 来完成卸载:
frameworks/base/core/java/android/content/pm/PackageManager.java
/**
* Attempts to delete a package. Since this may take a little while, the
* result will be posted back to the given observer. A deletion will fail if
* the calling context lacks the
* {@link android.Manifest.permission#DELETE_PACKAGES} permission, if the
* named package cannot be found, or if the named package is a system
* package.
*
* @param packageName The name of the package to delete
* @param observer An observer callback to get notified when the package
* deletion is complete.
* {@link android.content.pm.IPackageDeleteObserver#packageDeleted}
* will be called when that happens. observer may be null to
* indicate that no callback is desired.
* @hide
*/
@SuppressWarnings("HiddenAbstractMethod")
@RequiresPermission(Manifest.permission.DELETE_PACKAGES)
@UnsupportedAppUsage
public abstract void deletePackage(@NonNull String packageName,
@Nullable IPackageDeleteObserver observer, @DeleteFlags int flags);
不过 deletePackage 是 @hide 的隐藏 API,虽然可以通过引入 framework.jar 反射调用,但工程侵入性较大、维护成本也高,因此还是优先选择基于 pm 命令的方式来做静默卸载。
原理
pm 命令最终是落到 Framework 层的 PackageInstallerService 这个类来执行的。
相关源码文件位置如下:
frameworks/base/services/core/java/com/android/server/pm/PackageInstallerService.java
@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");
// Create by yeruilai 2025-11-01 20:46:06 Enable silent loading for System users
// if ((callingUid != Process.SHELL_UID) && (callingUid != Process.ROOT_UID)) {
if ((callingUid != Process.SHELL_UID) && (callingUid != Process.ROOT_UID) && (callingUid != Process.SYSTEM_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);
uninstall 相关逻辑默认只允许 ROOT_UID 与 SHELL_UID 这两个用户执行,所以:
- 通过 ADB shell 执行 pm uninstall 可以正常卸载(shell 权限)
- 在普通应用里直接执行 pm uninstall 就会因权限校验失败而抛异常
如果业务 APK 本身是系统应用(Manifest 中指定 android.uid.system),那么只需要在对应逻辑里把 SYSTEM_UID 也加入白名单,即可允许系统应用通过 pm uninstall 执行卸载。
如果希望 普通应用 也能调用 pm uninstall,那就更“简单粗暴”——把下面这行校验直接注释掉即可:
mAppOps.checkPackage(callingUid, callerPackageName);
但这等于完全放开了调用方校验,风险较大,仅建议在强可控的定制系统或调试环境里使用,不推荐在量产环境这样修改。
封装实现:静默安装 / 卸载工具类
下面是对静默安装 / 卸载做的一层简单封装,方便在业务代码中统一调用与回调处理。
1. 安装 / 卸载回调接口
public interface ApkInstallListener {
void onSuccess(String packageName);
void onFail(Exception e);
}
2. 静默安装方法(基于 pm install)
public synchronized static void installApk(Context context,
String apkPath,
ApkInstallListener installListener) {
try {
PackageInfo info = getApkInfo(context, apkPath);
String[] args = {
"pm", "install",
"-i", info.packageName,
"–user", "0",
"-r", apkPath
};
ProcessBuilder processBuilder = new ProcessBuilder(args);
Process process = null;
BufferedReader successResult = null;
BufferedReader errorResult = null;
StringBuilder successMsg = new StringBuilder();
StringBuilder errorMsg = new StringBuilder();
try {
process = processBuilder.start();
successResult = new BufferedReader(
new InputStreamReader(process.getInputStream()));
errorResult = new BufferedReader(
new InputStreamReader(process.getErrorStream()));
String line;
while ((line = successResult.readLine()) != null) {
successMsg.append(line);
}
while ((line = errorResult.readLine()) != null) {
errorMsg.append(line);
}
Log.i(TAG, "install success_msg " + successMsg);
Log.i(TAG, "install error_msg " + errorMsg);
if (successMsg.toString().contains("Success")) {
if (installListener != null) {
installListener.onSuccess(info.packageName);
}
} else {
if (installListener != null) {
installListener.onFail(
new RuntimeException(errorMsg.toString()));
}
}
} catch (Exception e) {
Log.i(TAG, "install fail ", e);
if (installListener != null) {
installListener.onFail(e);
}
} finally {
try {
if (successResult != null) {
successResult.close();
}
} catch (IOException ignored) {
}
try {
if (errorResult != null) {
errorResult.close();
}
} catch (IOException ignored) {
}
try {
if (process != null) {
process.destroy();
}
} catch (Exception ignored) {
}
}
} catch (Exception e) {
if (installListener != null) {
installListener.onFail(e);
}
}
}
3. 获取 APK 信息的工具方法
/**
* 获取 apk 包的信息:版本号,名称,图标等
*
* @param absPath apk 包的绝对路径
* @param context Context
*/
private static PackageInfo getApkInfo(Context context, String absPath) {
PackageInfo pkgInfo = null;
try {
PackageManager pm = context.getPackageManager();
pkgInfo = pm.getPackageArchiveInfo(absPath,
PackageManager.GET_ACTIVITIES);
if (pkgInfo != null) {
ApplicationInfo appInfo = pkgInfo.applicationInfo;
// 必须加这两句,否则 icon 获取不到 apk 自身的图标
appInfo.sourceDir = absPath;
appInfo.publicSourceDir = absPath;
String appName = pm.getApplicationLabel(appInfo).toString();
String packageName = appInfo.packageName;
String version = pkgInfo.versionName;
String pkgInfoStr = String.format(
"PackageName:%s, Version:%s, AppName:%s",
packageName, version, appName);
Log.i("YTUtils",
String.format("PkgInfo: %s", pkgInfoStr));
}
} catch (Exception e) {
Log.e("YTUtils",
"apk 文件信息读取失败: " + absPath, e);
}
return pkgInfo;
}
4. 静默卸载方法(基于 pm uninstall)
public synchronized static void uninstallApk(Context context,
String packageName,
ApkInstallListener installListener) {
try {
String[] args = {
"pm", "uninstall",
"-k",
"–user", "0",
packageName
};
ProcessBuilder processBuilder = new ProcessBuilder(args);
Process process = null;
BufferedReader successResult = null;
BufferedReader errorResult = null;
StringBuilder successMsg = new StringBuilder();
StringBuilder errorMsg = new StringBuilder();
try {
process = processBuilder.start();
successResult = new BufferedReader(
new InputStreamReader(process.getInputStream()));
errorResult = new BufferedReader(
new InputStreamReader(process.getErrorStream()));
String line;
while ((line = successResult.readLine()) != null) {
successMsg.append(line);
}
while ((line = errorResult.readLine()) != null) {
errorMsg.append(line);
}
Log.i(TAG, "uninstall success_msg " + successMsg);
Log.i(TAG, "uninstall error_msg " + errorMsg);
if (successMsg.toString().contains("Success")) {
if (installListener != null) {
installListener.onSuccess(packageName);
}
} else {
if (installListener != null) {
installListener.onFail(
new RuntimeException(errorMsg.toString()));
}
}
} catch (Exception e) {
Log.i(TAG, "uninstall fail ", e);
if (installListener != null) {
installListener.onFail(e);
}
} finally {
try {
if (successResult != null) {
successResult.close();
}
} catch (IOException ignored) {
}
try {
if (errorResult != null) {
errorResult.close();
}
} catch (IOException ignored) {
}
try {
if (process != null) {
process.destroy();
}
} catch (Exception ignored) {
}
}
} catch (Exception e) {
if (installListener != null) {
installListener.onFail(e);
}
}
}
以上就是在 Android 7 上,通过少量 Framework 修改配合 pm 命令,实现静默安装 / 卸载的整体思路与代码封装。
网硕互联帮助中心




评论前必须登录!
注册