【问题标题】:install / uninstall APKs programmatically (PackageManager vs Intents)以编程方式安装/卸载 APK(PackageManager 与 Intents)
【发布时间】:2011-10-12 09:39:10
【问题描述】:

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

公共抽象字符串 getInstallerPackageName(String 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 文件并根据包层次结构将它们放在文件夹中,您可以查看这个简洁的脚本:View Android Source Code in Eclipse

【问题讨论】:

  • 文本中缺少某些 URI。我会尽快添加它们(新用户有一些限制以防止垃圾邮件)。
  • 如何禁用卸载功能?
  • @user938893:“如何禁用卸载功能?” -- 我们正在处理一些难以卸载的恶意软件,是吗?

标签: android android-intent installation uninstallation apk


【解决方案1】:

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() 方法?
  • ACTION_INSTALL_PACKAGE 怎么样?我们可以从 Play 商店下载并安装我们的最新版本的应用吗?
  • 由于Android P删除应用需要manifest权限“android.permission.REQUEST_DELETE_PACKAGES”无论你使用“ACTION_DELETE”还是“ACTION_UNINSTALL_PACKAGE”developer.android.com/reference/android/content/…
  • 感谢您提及 Android P 权限,我被卡住了,不确定之前发生了什么。
【解决方案2】:

先决条件:

如前所述,您的 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);
}

【讨论】:

    【解决方案3】:

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

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

    如果您想提示用户在设备上卸载另一个应用程序,您可以将packageName 更改为任何其他包名称

    【讨论】:

      【解决方案4】:

      如果您有设备所有者(或配置文件所有者,我没有尝试过)权限,您可以使用设备所有者 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 的内容读入输出流
      • @LukeCauthen 您是否尝试过成为设备所有者?成功了吗?
      • @NetStarter 是的,我有。让应用程序成为设备所有者只是一件痛苦的事情。一旦你这样做了,你就会获得很多通常需要 root 的权力。
      • 请注意,您必须将 android.permission.DELETE_PACKAGES 添加到清单中才能进行卸载(在 Api 级别 22 或以下测试)
      【解决方案5】:

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

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

      【讨论】:

        【解决方案6】:

        API 级别 14 引入了两个新操作:ACTION_INSTALL_PACKAGEACTION_UNINSTALL_PACKAGE。这些操作允许您传递 EXTRA_RETURN_RESULT boolean extra 以获得(卸载)安装结果通知。

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

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

        【讨论】:

        • 我如何从这个操作对话框中确认用户是否按下了确定或取消,以便我可以据此做出决定
        • @Erum 我已经为您的问题添加了一个示例
        • 安装时,取消按钮返回onActivityResult方法没有得到结果
        • 从 API 25 开始,调用 ACTION_INSTALL_PACKAGE 将需要签名级别 REQUEST_INSTALL_PACKAGES 权限。同样,从 API 28 (Android P) 开始,调用 ACTION_UNINSTALL_PACKAGE 将需要非危险的 REQUEST_DELETE_PACKAGES 权限。至少根据文档。
        【解决方案7】:

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

        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() is defined here.

        【讨论】:

        【解决方案8】:

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

        【讨论】:

        • 这在 2.2 中效果很好,但我在 2.3 中没有任何运气
        • 反射在所有 api 版本中都不稳定
        【解决方案9】:

        根据 Froyo 源代码,在 PackageInstallerActivity 中查询 Intent.EXTRA_INSTALLER_PACKAGE_NAME 额外键以获得安装程序包名称。

        【讨论】:

        • 通过查看commit 我认为它应该可以工作
        【解决方案10】:

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

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

        编辑:

        另外值得指出的是,这个安装程序包是最近才添加到平台(2.2?)中的,最初并没有真正用于跟踪谁安装了应用程序——平台使用它来确定何时启动谁报告应用程序的错误,以实现 Android 反馈。 (这也是 API 方法参数发生变化的时期之一。)在引入它之后的至少很长一段时间内,Market 仍然没有使用它来跟踪它已安装的应用程序(而且很可能仍然没有使用它),但只是使用它来将 Android 反馈应用(与 Market 分开)设置为“所有者”来处理反馈。

        【讨论】:

        • "请注意,即使使用反射或其他技巧来访问 installPackage() 也无济于事,因为只有系统应用程序才能使用它。"假设我正在为给定平台制作包安装/删除/管理应用程序,而不是原生 Android 本身。我应该如何访问安装/删除?
        • startActivity() 具有适当的 Intent。 (我确信这已经在 StackOverflow 的其他地方得到了回答,所以我不会尝试在这里给出确切的答案,否则可能会出错。)
        • mmmkay,这会调出标准的 Android 安装/删除对话框。这些细节已经被处理了——我正在寻找“just **** install this package”和“just **** remove this package”功能,实际上没有任何问题。
        • 正如我所说,这些不适用于第三方应用程序。如果您正在制作自己的系统映像,则您有平台实现,您可以在那里找到功能,但它们不是普通第三方应用程序可用的 API 的一部分。
        • 我正在制作具有安装、删除和备份功能的 apk 文件浏览器,那么 google 是否允许我继续在 google play 上发布我的应用程序?我们将打破哪项政策?
        猜你喜欢
        • 1970-01-01
        • 2011-06-13
        • 1970-01-01
        • 2015-08-10
        • 2019-09-07
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多