以编程方式安装/卸载 APK (PackageManager 与意图)

我的应用程序安装了其他应用程序,它需要跟踪已安装的应用程序。当然,这可以通过保留已安装应用程序的列表来实现。但这没有必要!PackageManager 应该负责维护 installedBy (a,b)关系。事实上,根据空气污染指数,它是:

公共抽象字符串 GetInstallerPackageName(字符串 packageName)- 检索安装软件包的应用程序的软件包名称。这标识软件包来自哪个市场。

目前的方法

使用意图安装 APK

Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setDataAndType(apkUri, "application/vnd.android.package-archive");
startActivity(intent);

使用意图卸载 APK:

Intent intent = new Intent(Intent.ACTION_DELETE, Uri.fromParts("package",
getPackageManager().getPackageArchiveInfo(apkUri.getPath(), 0).packageName,null));
startActivity(intent);

这显然不是 Android Market 安装/卸载软件包的方式。它们使用更丰富的 PackageManager 版本。通过从 Android Git 存储库下载 Android 源代码可以看到这一点。下面是与意图方法相对应的两个隐藏方法。不幸的是,外部开发人员无法使用它们。但也许将来会是这样呢?

更好的方法

使用 PackageManager 安装 APK

/**
* @hide
*
* Install a package. Since this may take a little while, the result will
* be posted back to the given observer.  An installation will fail if the calling context
* lacks the {@link android.Manifest.permission#INSTALL_PACKAGES} permission, if the
* package named in the package file's manifest is already installed, or if there's no space
* available on the device.
*
* @param packageURI The location of the package file to install.  This can be a 'file:' or a
* 'content:' URI.
* @param observer An observer callback to get notified when the package installation is
* complete. {@link IPackageInstallObserver#packageInstalled(String, int)} will be
* called when that happens.  observer may be null to indicate that no callback is desired.
* @param flags - possible values: {@link #INSTALL_FORWARD_LOCK},
* {@link #INSTALL_REPLACE_EXISTING}, {@link #INSTALL_ALLOW_TEST}.
* @param installerPackageName Optional package name of the application that is performing the
* installation. This identifies which market the package came from.
*/
public abstract void installPackage(
Uri packageURI, IPackageInstallObserver observer, int flags,
String installerPackageName);

使用 PackageManager 卸载 APK

/**
* 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".
* (TODO: include pointer to documentation on "system packages")
*
* @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(boolean)} will be
* called when that happens.  observer may be null to indicate that no callback is desired.
* @param flags - possible values: {@link #DONT_DELETE_DATA}
*
* @hide
*/
public abstract void deletePackage(
String packageName, IPackageDeleteObserver observer, int flags);

差异

  • 使用意图时,本地包管理器不知道安装源自哪个应用程序。具体来说,getInstallerPackageName (...)返回 null。

  • 隐藏方法 installPackage (...)将安装程序包名称作为参数,并且很可能能够设置此值。

提问

是否可以使用意图指定包安装程序名称? (也许安装程序包的名称可以作为安装意图的附加内容?)

提示: 如果你想下载 Android 的源代码,你可以按照这里描述的步骤: 下载源代码树。提取 * 。Java 文件,并把他们放在文件夹根据包的层次结构,你可以检查出这个整洁的脚本: 在 Eclipse 中查看 Android 源代码

164217 次浏览

访问这些方法的唯一方法是通过反射。通过调用 getApplicationContext().getPackageManager()并使用反射访问这些方法,可以获得 PackageManager对象的句柄。了解 这个教程。

这目前不可用于第三方应用程序。请注意,即使使用反射或其他技巧来访问 installPackage ()也没有帮助,因为只有系统应用程序可以使用它。(这是因为它是底层安装机制,在用户批准权限之后,所以常规应用程序访问它是不安全的。)

此外,installPackage ()函数参数在不同的平台版本之间经常发生变化,因此您尝试访问它的任何操作在平台的其他版本上都会失败。

编辑:

同样值得指出的是,这个 installerPackage 是最近才添加到平台的(2.2?)最初并不是用来跟踪谁安装了这个应用程序——它被平台用来决定在报告这个应用程序的错误时启动谁,用来实现 Android 反馈。(这也是 API 方法参数改变的次数之一。)在它被引入之后的很长一段时间里,Market 仍然没有使用它来追踪它已经安装的应用程序(而且它很可能仍然没有使用它) ,而是仅仅使用它来设置安卓反馈应用程序(它是独立于 Market 的)作为“所有者”来处理反馈。

根据 Froyo 源代码,将在 PackageInstallerActivity 中查询 Inent.EXTRA _ INSTALLER _ PACKAGE _ NAME 额外键以获得安装程序包名称。

Android P + 需要 AndroidManifest.xml 中的此权限

<uses-permission android:name="android.permission.REQUEST_DELETE_PACKAGES" />

然后:

Intent intent = new Intent(Intent.ACTION_DELETE);
intent.setData(Uri.parse("package:com.example.mypackage"));
startActivity(intent);

卸载。似乎更容易..。

API 级别14引入了两个新操作: ACTION _ install _ PackageACTION _ 卸载 _ 包。这些操作允许您额外传递 EXTRA _ RETURN _ result布尔值以获得(解除)安装结果通知。

调用卸载对话框的示例代码:

String app_pkg_name = "com.example.app";
int UNINSTALL_REQUEST_CODE = 1;


Intent intent = new Intent(Intent.ACTION_UNINSTALL_PACKAGE);
intent.setData(Uri.parse("package:" + app_pkg_name));
intent.putExtra(Intent.EXTRA_RETURN_RESULT, true);
startActivityForResult(intent, UNINSTALL_REQUEST_CODE);

并在您的 活动 # onActivityResult方法中接收通知:

@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == UNINSTALL_REQUEST_CODE) {
if (resultCode == RESULT_OK) {
Log.d("TAG", "onActivityResult: user accepted the (un)install");
} else if (resultCode == RESULT_CANCELED) {
Log.d("TAG", "onActivityResult: user canceled the (un)install");
} else if (resultCode == RESULT_FIRST_USER) {
Log.d("TAG", "onActivityResult: failed to (un)install");
}
}
}

在有根设备上,您可以使用:

String pkg = context.getPackageName();
String shellCmd = "rm -r /data/app/" + pkg + "*.apk\n"
+ "rm -r /data/data/" + pkg + "\n"
// TODO remove data on the sd card
+ "sync\n"
+ "reboot\n";
Util.sudo(shellCmd);

这里定义了 Util.sudo()

如果要将包名作为参数传递给任何用户定义的函数,请使用以下代码:

    Intent intent=new Intent(Intent.ACTION_DELETE);
intent.setData(Uri.parse("package:"+packageName));
startActivity(intent);

如果您有设备所有者(或配置文件所有者,我还没有尝试过)权限,您可以使用设备所有者 API 悄悄地安装/卸载包。

卸载:

public boolean uninstallPackage(Context context, String packageName) {
ComponentName name = new ComponentName(MyAppName, MyDeviceAdminReceiver.class.getCanonicalName());
PackageManager packageManger = context.getPackageManager();
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.LOLLIPOP) {
PackageInstaller packageInstaller = packageManger.getPackageInstaller();
PackageInstaller.SessionParams params = new PackageInstaller.SessionParams(
PackageInstaller.SessionParams.MODE_FULL_INSTALL);
params.setAppPackageName(packageName);
int sessionId = 0;
try {
sessionId = packageInstaller.createSession(params);
} catch (IOException e) {
e.printStackTrace();
return false;
}
packageInstaller.uninstall(packageName, PendingIntent.getBroadcast(context, sessionId,
new Intent("android.intent.action.MAIN"), 0).getIntentSender());
return true;
}
System.err.println("old sdk");
return false;
}

以及安装软件包:

public boolean installPackage(Context context,
String packageName, String packagePath) {
ComponentName name = new ComponentName(MyAppName, MyDeviceAdminReceiver.class.getCanonicalName());
PackageManager packageManger = context.getPackageManager();
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.LOLLIPOP) {
PackageInstaller packageInstaller = packageManger.getPackageInstaller();
PackageInstaller.SessionParams params = new PackageInstaller.SessionParams(
PackageInstaller.SessionParams.MODE_FULL_INSTALL);
params.setAppPackageName(packageName);
try {
int sessionId = packageInstaller.createSession(params);
PackageInstaller.Session session = packageInstaller.openSession(sessionId);
OutputStream out = session.openWrite(packageName + ".apk", 0, -1);
readTo(packagePath, out); //read the apk content and write it to out
session.fsync(out);
out.close();
System.out.println("installing...");
session.commit(PendingIntent.getBroadcast(context, sessionId,
new Intent("android.intent.action.MAIN"), 0).getIntentSender());
System.out.println("install request sent");
return true;
} catch (IOException e) {
e.printStackTrace();
return false;
}
}
System.err.println("old sdk");
return false;
}

如果你正在使用 Kotlin,API 14 + ,并希望显示你的应用程序的卸载对话框:

startActivity(Intent(Intent.ACTION_UNINSTALL_PACKAGE).apply {
data = Uri.parse("package:$packageName")
})

如果希望提示用户卸载设备上的其他应用程序,可以将 packageName更改为任何其他包名

先决条件:

您的 APK 需要由系统签名,正如前面正确指出的那样。实现这一点的一种方法是自己构建 AOSP 映像并将源代码添加到构建中。

密码:

一旦安装为系统应用程序,您可以使用包管理器方法来安装和卸载 APK,如下所示:

安装:

public boolean install(final String apkPath, final Context context) {
Log.d(TAG, "Installing apk at " + apkPath);
try {
final Uri apkUri = Uri.fromFile(new File(apkPath));
final String installerPackageName = "MyInstaller";
context.getPackageManager().installPackage(apkUri, installObserver, PackageManager.INSTALL_REPLACE_EXISTING, installerPackageName);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}

卸载:

public boolean uninstall(final String packageName, final Context context) {
Log.d(TAG, "Uninstalling package " + packageName);
try {
context.getPackageManager().deletePackage(packageName, deleteObserver, PackageManager.DELETE_ALL_USERS);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}

要在安装/卸载 APK 后进行回调,可以使用以下命令:

/**
* Callback after a package was installed be it success or failure.
*/
private class InstallObserver implements IPackageInstallObserver {


@Override
public void packageInstalled(String packageName, int returnCode) throws RemoteException {


if (packageName != null) {
Log.d(TAG, "Successfully installed package " + packageName);
callback.onAppInstalled(true, packageName);
} else {
Log.e(TAG, "Failed to install package.");
callback.onAppInstalled(false, null);
}
}


@Override
public IBinder asBinder() {
return null;
}
}


/**
* Callback after a package was deleted be it success or failure.
*/
private class DeleteObserver implements IPackageDeleteObserver {


@Override
public void packageDeleted(String packageName, int returnCode) throws RemoteException {
if (packageName != null) {
Log.d(TAG, "Successfully uninstalled package " + packageName);
callback.onAppUninstalled(true, packageName);
} else {
Log.e(TAG, "Failed to uninstall package.");
callback.onAppUninstalled(false, null);
}
}


@Override
public IBinder asBinder() {
return null;
}
}


/**
* Callback to give the flow back to the calling class.
*/
public interface InstallerCallback {
void onAppInstalled(final boolean success, final String packageName);
void onAppUninstalled(final boolean success, final String packageName);
}