ARTICLE · 1250412
Android 7 静默安装/卸载实现与 Framework 修改记录
在 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 命令,实现静默安装 / 卸载的整体思路与代码封装。















































































