программно установить / удалить 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);

Удалить 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 (...) возвращает значение null.

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

Вопрос

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

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


person Håvard Geithus    schedule 25.07.2011    source источник
comment
Некоторые URI отсутствуют в тексте. Я добавлю их, как только мне будет разрешено (у новых пользователей есть некоторые ограничения для предотвращения спама).   -  person Håvard Geithus    schedule 26.07.2011
comment
как отключить функцию удаления?   -  person    schedule 21.04.2012
comment
@ user938893: как отключить функцию удаления? - Работаем над каким-то вредоносным ПО, которое трудно удалить, не так ли?   -  person Daniel    schedule 28.06.2016


Ответы (10)


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

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

РЕДАКТИРОВАТЬ:

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

person hackbod    schedule 09.10.2011
comment
Обратите внимание, что даже использование отражения или других уловок для доступа к installPackage () не поможет, потому что его могут использовать только системные приложения. Предположим, я делаю приложение для установки / удаления / управления пакетом для данной платформы, кроме самого Android. Как мне получить доступ к установке / удалению? - person dascandy; 10.10.2011
comment
startActivity () с соответствующим образом сформированным намерением. (Я уверен, что на этот вопрос есть ответ в другом месте на StackOverflow, поэтому я не буду пытаться давать здесь точный ответ, рискуя ошибиться.) - person hackbod; 11.10.2011
comment
mmmkay, это вызывает стандартные диалоговые окна установки / удаления Android. Эти детали уже были обработаны - я ищу просто **** установить этот пакет и просто **** удалить функции этого пакета, буквально без вопросов. - person dascandy; 11.10.2011
comment
Как я уже сказал, они недоступны для сторонних приложений. Если вы создаете свой собственный образ системы, у вас есть реализация платформы, и вы можете найти там функции, но они не являются частью API-интерфейсов, доступных для обычных сторонних приложений. - person hackbod; 13.10.2011
comment
Я делаю обозреватель файлов apk с функциями установки, удаления и резервного копирования, так что может ли Google разрешить мне публиковать мое приложение в Google Play? и какую политику мы собираемся нарушить? - person Rahul Mandaliya; 18.11.2014
comment
Можно ли использовать эти методы, если устройство рутировано? или даже тогда? - person yahya; 14.10.2015
comment
Это устаревший ответ. Я бы подумал об обновлении его, чтобы отразить новые API Android for Work. - person Luke Cauthen; 29.07.2016

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

удалить. Вроде проще ...

person JohnyTex    schedule 18.02.2014
comment
Может ли это приложение запускать код? как в onDestroy() методе? - person Mahdi-Malv; 10.06.2017
comment
как насчет ACTION_INSTALL_PACKAGE? можем ли мы загрузить и установить последнюю версию нашего приложения из игрового магазина? - person MAS. John; 03.03.2018
comment
Поскольку для удаления приложений Android P требуется разрешение манифеста android.permission.REQUEST_DELETE_PACKAGES независимо от того, используете ли вы ACTION_DELETE или ACTION_UNINSTALL_PACKAGE developer.android.com/reference/android/content/ - person Darklord5; 06.03.2019
comment
Спасибо, что упомянули разрешение Android P, я застрял и не был уверен, что происходит раньше. - person Avi Parshan; 07.08.2019

Уровень API 14 представил два новых действия: ACTION_INSTALL_PACKAGE и ACTION_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");
        }
    }
}
person Pir Fahim Shah    schedule 15.07.2014
comment
как я могу подтвердить из этого диалогового окна действия, что пользователь нажал ОК или Отменить, чтобы я мог принять решение на основе этого - person Erum; 25.11.2014
comment
@Erum Я добавил пример того, что вы спросили - person Alex Lipov; 13.05.2015
comment
При установке кнопка отмены не возвращала результат в метод onActivityResult - person diyoda_; 19.11.2015
comment
Начиная с API 25, для вызова ACTION_INSTALL_PACKAGE потребуется уровень подписи _2 _ разрешение. Аналогичным образом, начиная с API 28 (Android P), для вызова ACTION_UNINSTALL_PACKAGE потребуется неопасный _ 4_ разрешение. По крайней мере, согласно документам. - person Steve Blackwell; 03.04.2018

Если у вас есть разрешение владельца устройства (или владельца профиля, я не пробовал), вы можете без вывода сообщений устанавливать / удалять пакеты с помощью 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;
}
person Ohad Cohen    schedule 18.07.2016
comment
Я знал, что это должно быть возможно, будучи владельцем устройства. Спасибо за ответ! - person Luke Cauthen; 26.07.2016
comment
@sandeep просто считывает содержимое APK в выходной поток - person Ohad Cohen; 18.01.2017
comment
@LukeCauthen вы пробовали стать владельцем устройства? это сработало? - person NetStarter; 19.08.2017
comment
@NetStarter Да, есть. Получить статус приложения в качестве владельца устройства - это просто головная боль. Как только вы это сделаете, вы получите много возможностей, для которых обычно требуется root. - person Luke Cauthen; 20.08.2017
comment
У меня есть рабочее приложение владельца устройства (COSU). Я пробовал использовать описанный выше метод, чтобы позволить ему обновиться из загруженного .apk файла, но намерение обратного вызова никогда не запускается, и приложение никогда не обновляется. В adb logcat нет ошибок или исключений. (Я изменил все System.outs на Log.es.) - person fadedbee; 27.10.2017
comment
@LukeCauthen Обновляет ли ваше приложение владельца устройства само или устанавливает другое приложение? (У меня проблемы с обновлением работающего приложения владельца устройства.) - person fadedbee; 27.10.2017
comment
@chrisdew Хороший вопрос, решение, которое я придумал, - запустить два приложения. Где приложение владельца устройства обновляет apk отдельного приложения. Если приложение-владелец когда-либо нуждается в обновлении, отдельное приложение может обновить его (поскольку приложение должно закрываться при замене apk). - person Luke Cauthen; 29.10.2017
comment
@LukeCauthen Спасибо за ваш ответ. Теперь я попробовал решение с двумя приложениями, и оно у меня не работает. stackoverflow.com/ questions / 47020457 / Есть совет? - person fadedbee; 30.10.2017
comment
Обратите внимание, что вы должны добавить android.permission.DELETE_PACKAGES в свой манифест, чтобы удаление работало (проверено на уровне Api 22 или ниже) - person benchuk; 26.11.2017
comment
хорошо сыграно, он будет работать в приложении администратора? он может это удалить? или есть способ удалить админку и чем удалить? - person yanivtwin; 01.05.2018
comment
Хорошее решение. что такое объект componentName и для чего он используется? - person javadhme; 16.01.2020

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

person HandlerExploit    schedule 05.10.2011
comment
Это отлично работает с 2.2, но мне не повезло с 2.3. - person Someone Somewhere; 01.03.2012
comment
Отражение нестабильно во всех версиях api - person HandlerExploit; 02.03.2012

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

person njzk2    schedule 02.01.2012
comment
Глядя на этот коммит, я думаю, он должен работать. - person sergio91pt; 29.08.2012

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

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

person 18446744073709551615    schedule 08.05.2015
comment
Есть ли способ установить предварительно загруженное приложение на sdcard? Или вы можете предложить мне перейти на какую-нибудь страницу, чтобы проверить, какие команды мы можем использовать в оболочке на платформе Android? - person yahya; 14.10.2015
comment
Обнаружен @yahya developer.android.com/tools/help/shell.html по фразе pm android, pm = менеджер пакетов - person 18446744073709551615; 14.10.2015
comment
@yahya cheatography.com/citguy/cheat-sheets/android -package-manager-pm установка-установка-расположение - person 18446744073709551615; 14.10.2015
comment
Большое спасибо! Эти ссылки действительно отличное руководство для начала :) - person yahya; 14.10.2015
comment
@ V.Kalyuzhnyu Работало еще в 2015 году. IIRC, это был Samsung Galaxy, может быть, S5. - person 18446744073709551615; 22.08.2017
comment
Вероятно актуально: получение пути apk этого приложения stackoverflow.com/questions/16973248/ - person 18446744073709551615; 22.08.2017

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

    Intent intent=new Intent(Intent.ACTION_DELETE);
    intent.setData(Uri.parse("package:"+packageName));
    startActivity(intent);
person Rashwin S M    schedule 09.03.2016

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

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

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

person Louis CAD    schedule 18.10.2017

Предварительное условие:

Ваш 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);
}
person phoebus    schedule 13.06.2018