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

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

public abstract String getInstallerPackageName (String packageName) – 检索安装包的应用程序的包名称。 这标识了包装来自哪个市场。

目前的做法

使用意图安装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); 

差异

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

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

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

提示:如果您想下载Android源代码,您可以按照以下步骤操作:下载源代码树。 要提取* .java文件,并根据软件包层次将它们放入文件夹中,可以查看以下简洁的脚本: 在Eclipse中查看Android源代码 。

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

此外,installPackage()函数参数在平台版本之间经常发生变化,所以您尝试访问它的任何内容都将在各种其他版本的平台上失败。

编辑:

另外值得指出的是,这个installerPackage最近才刚刚添加到平台(2.2?),最初并没有实际用于跟踪谁安装了应用程序 – 平台使用它来确定在向该应用程序,用于实现Android反馈。 (这也是API方法参数改变的时代之一。)在推出之后的至少很长一段时间,Market还没有用它来跟踪它安装的应用程序(它可能还没有使用它),而只是用它来设置Android反馈应用程序(与市场分开)作为“所有者”来处理反馈。

[卸载]

怎么样:

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

卸载。 似乎更容易…

API级别14引入了两个新操作: ACTION_INSTALL_PACKAGE和ACTION_UNINSTALL_PACKAGE 。 这些操作允许您传递EXTRA_RETURN_RESULT布尔额外以获取(un)安装结果通知。

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

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

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

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

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

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

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

如果您使用的是Kotlin,API 14+,只希望显示您的应用的卸载对话框:

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

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

Interesting Posts