установить / удалить APK программно (PackageManager vs Intents) - PullRequest
136 голосов
/ 25 июля 2011

Мое приложение устанавливает другие приложения, и оно должно отслеживать, какие приложения оно установило. Конечно, этого можно достичь, просто ведя список установленных приложений. Но это не должно быть необходимым! PackageManager должен нести ответственность за поддержание отношения установленныхBy (a, b). На самом деле, согласно API это:

публичная абстрактная строка getInstallerPackageName (строка имя_пакета) - Получить имя пакета приложения, которое установило пакет. Это определяет, с какого рынка пришла посылка.

Текущий подход

Установить APK с помощью Intent

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 из репозитория Android Git. Ниже приведены два скрытых метода, которые соответствуют подходу Intent. К сожалению, они не доступны для внешних разработчиков. Но, возможно, они будут в будущем?

лучший подход

Установка APK с помощью PackageManager

/**
 * @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);

Удаление APK с помощью PackageManager

/**
 * 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 (...) возвращает ноль.

  • Скрытый метод installPackage (...) принимает имя пакета установщика в качестве параметра и, скорее всего, может установить это значение.

Вопрос

Можно ли указать имя установщика пакета с помощью намерений? (Может быть, имя установочного пакета можно добавить как дополнение к намерению установки?)

Подсказка. Если вы хотите загрузить исходный код Android, вы можете выполнить действия, описанные здесь: Загрузка исходного кода. Чтобы извлечь файлы * .java и поместить их в папки в соответствии с иерархией пакетов, вы можете проверить этот аккуратный скрипт: Просмотр исходного кода Android в Eclipse .

Ответы [ 10 ]

70 голосов
/ 18 февраля 2014

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

для удаления. Кажется проще ...

66 голосов
/ 09 октября 2011

В настоящее время недоступно для сторонних приложений. Обратите внимание, что даже использование отражения или других приемов для доступа к installPackage () не поможет, потому что его могут использовать только системные приложения. (Это потому, что это низкоуровневый механизм установки после того, как разрешения были утверждены пользователем, поэтому для обычных приложений небезопасно иметь доступ к ним.)

Кроме того, аргументы функции installPackage () часто менялись между выпусками платформы, поэтому все, что вы делаете, пытаясь получить к нему доступ, не будет работать в других других версиях платформы.

EDIT:

Также стоит отметить, что этот пакет installerPackage был добавлен в платформу только недавно (2.2?) И изначально фактически не использовался для отслеживания того, кто установил приложение - он используется платформой для определения, кого запускать, когда сообщение об ошибках с приложением, для реализации Android Feedback. (Это также был один из случаев, когда изменились аргументы метода API.) По крайней мере, долгое время после его появления, Маркет все еще не использовал его для отслеживания установленных приложений (и вполне может не использовать его). ), но вместо этого просто использовал это, чтобы установить приложение Android Feedback (которое было отдельно от Market) в качестве «владельца», чтобы заботиться о обратной связи.

36 голосов
/ 15 июля 2014

Уровень API 14 представил два новых действия: ACTION_INSTALL_PACKAGE и ACTION_UNINSTALL_PACKAGE .Эти действия позволяют вам передать EXTRA_RETURN_RESULT логическое значение 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");
        }
    }
}
22 голосов
/ 18 июля 2016

Если у вас есть право владельца устройства (или владельца профиля, я не пробовал), вы можете устанавливать / удалять пакеты в режиме без вывода сообщений с помощью 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;
}
4 голосов
/ 05 октября 2011

Единственный способ получить доступ к этим методам - ​​через отражение. Вы можете получить дескриптор объекта PackageManager, вызвав getApplicationContext().getPackageManager() и используя отражение, чтобы получить доступ к этим методам. Оформить заказ этот учебник.

3 голосов
/ 02 января 2012

Согласно исходному коду Froyo, дополнительный ключ Intent.EXTRA_INSTALLER_PACKAGE_NAME запрашивается для имени пакета установщика в PackageInstallerActivity.

2 голосов
/ 08 мая 2015

На рутованном устройстве вы можете использовать:

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() определяется здесь.

1 голос
/ 09 марта 2016

Если вы передаете имя пакета в качестве параметра любой из ваших пользовательских функций, используйте следующий код:

    Intent intent=new Intent(Intent.ACTION_DELETE);
    intent.setData(Uri.parse("package:"+packageName));
    startActivity(intent);
0 голосов
/ 13 июня 2018

Необходимое условие:

Ваш 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;
    }
}

Uninstall:

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);
}
0 голосов
/ 18 октября 2017

Если вы используете Kotlin, API 14+ и просто хотите показать диалоговое окно удаления для своего приложения:

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

Вы можете изменить packageName на любое другое имя пакета, если хотите запроситьпользователь для удаления другого приложения на устройстве

...