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


141

我的应用程序将安装其他应用程序,并且需要跟踪已安装的应用程序。当然,这可以通过简单地保留已安装的应用程序列表来实现。但这不是必须的!PackageManager应该负责维护installedBy(a,b)关系。实际上,根据API,它是:

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

目前的做法

使用Intent安装APK

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

使用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源代码可以看出这一点。以下是与Intent方法相对应的两个隐藏方法。不幸的是,它们对外部开发人员不可用。但是也许它们会在未来吗?

更好的方法

使用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);

差异性

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

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

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

提示:如果要下载Android源代码,可以按照此处描述的步骤进行操作:下载源代码树。要提取* .java文件并将其根据包层次结构放置在文件夹中,您可以检出以下简洁的脚本:在Eclipse中查看Android源代码


文本中缺少某些URI。我将在允许的情况下尽快添加它们(新用户对防止垃圾邮件有一些限制)。
赫瓦尔德·盖瑟斯

1
如何禁用卸载功能?

2
@ user938893:“如何禁用卸载功能?” -正在研究一些难以卸载的恶意软件吗?
丹尼尔(Daniel)

Answers:


66

当前不适用于第三方应用程序。请注意,即使使用反射或其他技巧来访问installPackage()也无济于事,因为只有系统应用程序才能使用它。(这是因为这是低级安装机制,在用户批准权限之后,因此常规应用程序无法访问它是不安全的。)

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

编辑:

还需要指出的是,这个installerPackage只是最近才添加到平台(2.2?)上,最初实际上并未用于跟踪谁安装了该应用程序-平台使用它来确定在报告错误时由谁启动。该应用程序,用于实现Android反馈。(这也是API方法参数更改的时代之一。)在引入之后至少很长一段时间,Market仍未使用它来跟踪已安装的应用程序(并且很可能仍未使用它),而只是使用它来将Android反馈应用(与Market分开)设置为负责反馈的“所有者”。


“请注意,即使使用反射或其他技巧来访问installPackage()也无济于事,因为只有系统应用程序才能使用它。” 假设我正在为给定平台(本地Android本身除外)制作软件包安装/删除/管理应用程序。我应该如何访问安装/删除?
dascandy 2011年

具有适当形式的Intent的startActivity()。(我肯定这已经在StackOverflow的其他地方得到了解答,所以我不会尝试在此处给出确切答案的风险,以免出错。)
hackbod 2011年

mmmkay,会弹出标准的Android安装/删除对话框。这些细节已经处理完毕-我正在寻找“只需****安装此程序包”和“仅****删除此程序包”功能,实际上没有问题。
dascandy 2011年

2
如我所说,这些不适用于第三方应用程序。如果您要制作自己的系统映像,则可以使用平台实现,并且可以在其中找到功能,但是这些功能不是普通第三方应用程序可用的API的一部分。
hackbod 2011年

我正在制作具有安装,删除和备份功能的APK文件浏览器,所以Google允许我继续在Google Play上发布我的应用程序吗?以及我们将要打破的政策?
拉胡尔·曼达里亚

84

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);

卸载。似乎更容易...


这可以是运行代码的应用吗?喜欢onDestroy()方法吗?
Mahdi-Malv

ACTION_INSTALL_PACKAGE怎么样?我们可以从Play商店下载并安装最新版本的应用程序吗?
MAS。约翰

3
由于删除Android P应用程序需要清单权限“ android.permission.REQUEST_DELETE_PACKAGES”,无论您使用的是“ ACTION_DELETE”还是“ ACTION_UNINSTALL_PACKAGE” developer.android.com/reference/android/content/…–
Darklord5

感谢您提及Android P权限,我对此感到困惑,不确定之前发生了什么。
阿维·帕山

43

API级别14引入了两个新操作:ACTION_INSTALL_PACKAGEACTION_UNINSTALL_PACKAGE。这些操作使您可以额外传递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);

并在您的Activity#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");
        }
    }
}

我如何从此操作对话框中确认用户已按下OK或取消,以便我可以
据此

2
@Erum我为您提出了一个示例
Alex Lipov 2015年

在安装过程中,“取消”按钮没有将结果返回给onActivityResult方法
diyoda_

2
从API 25开始,调用ACTION_INSTALL_PACKAGE将需要签名级别REQUEST_INSTALL_PACKAGES权限。同样,从API 28(Android P)开始,调用ACTION_UNINSTALL_PACKAGE将需要非危险REQUEST_DELETE_PACKAGES权限。至少根据文档。
史蒂夫·布莱克韦尔

22

如果您具有设备所有者(或我没有尝试过配置文件所有者)的权限,则可以使用设备所有者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;
}

我知道必须作为所有者设备才能做到这一点。感谢你的回答!
路加·考森

@sandeep它只是将APK的内容读入输出流
Ohad Cohen

@LukeCauthen已成为设备所有者了吗?它奏效了吗?
NetStarter

@NetStarter是的,我有。让应用程序成为设备所有者是一件痛苦的事情。完成此操作后,您将获得通常需要root用户使用的大量功能。
路加·考森

1
请注意,您必须将android.permission.DELETE_PACKAGES添加到清单中,才能进行卸载(在Api级别22或
更低

4

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


这在2.2上很好用,但在2.3上使用我却没有运气
某处某人2012年

3
反思是不是稳定在所有版本的API
HandlerExploit

3

根据Froyo的源代码,在PackageInstallerActivity中查询Intent.EXTRA_INSTALLER_PACKAGE_NAME附加键以获取安装程序包名称。


1
通过查看此提交,我认为它应该起作用
sergio91pt 2012年

2

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

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() 在这里定义。


有没有办法在sdcard中安装预下载的应用程序?还是可以建议我在某个页面上查看我们可以在Android Platform的Shell上使用哪些命令?
yahya

1
@yahya developer.android.com/tools/help/shell.html由短语“ pm android”找到,pm =软件包管理器
18446744073709551615 2015年


非常感谢!这些链接真的是很酷的入门指南:)
yahya 2015年

@ V.Kalyuzhnyu它曾经在2015年工作。IIRC是三星Galaxy,也许是S5。
18446744073709551615 '17

2

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

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

0

如果您使用的是Kotlin,API 14+,并且只想显示应用程序的卸载对话框:

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

packageName如果要提示用户卸载设备上的另一个应用程序,则可以更改为任何其他程序包名称。


0

先决条件:

您的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);
}
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.