встановити / видалити APK програми програмно (PackageManager vs Intents)


142

У моєму додатку встановлено інші програми, і йому потрібно відслідковувати, які програми встановлені. Звичайно, цього можна досягти, просто утримуючи список встановлених додатків. Але це не повинно бути необхідним! Відповідальність PackageManager повинна підтримувати відносини встановленогоBy (a, b). Насправді згідно API це:

публічний конспект String getInstallerPackageName (String packageName) - Отримайте назву пакета програми, яка встановила пакет. Це визначає, з якого ринку прийшов пакет.

Сучасний підхід

Встановіть 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. Нижче наведені два прихованих методу, що відповідають підходу Інтенції. На жаль, вони недоступні для зовнішніх розробників. Але, можливо, вони будуть у майбутньому?

Кращий підхід

Встановлення 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 .


Деякі з URI відсутні в тексті. Я додаю їх, як тільки я дозволю (нові користувачі мають деякі обмеження для запобігання спаму).
Håvard Geithus

1
як відключити функцію видалення?

2
@ user938893: "як відключити функцію видалення?" - Працюємо над деякими важко видалими шкідливими програмами?
Даніель

Відповіді:


66

Наразі це недоступно для сторонніх додатків. Зауважте, що навіть використання роздумів або інших хитрощів для доступу до installPackage () не допоможе, оскільки ним можуть користуватися лише системні програми. (Це тому, що це низькорівневий механізм встановлення після затвердження дозволу користувачем, тому для регулярних додатків не безпечно мати доступ.)

Також аргументи функції installPackage () часто змінюються між випусками платформи, тому все, що ви намагаєтеся отримати доступ до неї, буде невдалим у різних інших версіях платформи.

Редагувати:

Також варто зазначити, що цей інсталяторPackage був доданий порівняно недавно на платформу (2.2?) І спочатку фактично не використовувався для відстеження того, хто встановив додаток - він використовується платформою, щоб визначити, з ким запускати, коли повідомляти про помилки з додаток, для реалізації зворотного зв’язку Android. (Це також було одним із випадків, коли аргументи методу API змінювалися.) Принаймні довгий час після його введення Market все ще не використовував його для відстеження встановлених додатків (і він, можливо, все ще не використовує його ), а натомість просто використали це, щоб встановити додаток Android Feedback (який був окремим від Market) як "власника", щоб піклуватися про зворотній зв'язок.


"Зауважте, що навіть використання роздумів або інших хитрощів для доступу до installPackage () не допоможе, оскільки ним можуть користуватися лише системні програми." Припустимо, я створюю пакет програм для встановлення / видалення / управління для певної платформи, окрім власного Android. Як я маю доступ до встановлення / видалення?
дасканді

startActivity () з відповідно сформованим Намір. (Я впевнений, що на це відповіли в іншому місці на StackOverflow, тому я не намагатимусь дати точну відповідь тут, ризикуючи отримати щось не так.)
hackbod

mmmkay, що відкриває стандартні діалогові вікна встановлення / видалення Android. Ці деталі вже оброблені - я шукаю функції "просто **** встановити цей пакет" та "просто **** видалити цей пакет", буквально жодних питань не задавали.
дасканді

2
Як я вже сказав, вони не доступні стороннім додаткам. Якщо ви створюєте власний образ системи, у вас є реалізація платформи, і ви можете знайти там функції, але вони не є частиною API, доступним для звичайних програм сторонніх розробників.
хакбод

я роблю apk file Explorer із функцією встановлення, видалення та резервного копіювання, тож Google дозволяє мені залишати публікацію своєї програми в Google Play? і яку політику ми будемо ламати?
Рахул Мандалія

86

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? чи можемо ми завантажити та встановити останню версію нашого додатка з магазину ігор?
МАС. Іоанна

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

як я можу підтвердити з цього діалогового вікна дії, що будь-який користувач натиснув кнопку "ОК" або скасувати, щоб я міг прийняти рішення на основі цього
Erum

2
@Erum Я додав приклад того, про що ви запитували
Олексій Липов

Після встановлення кнопка "Скасувати" не повернула результат назад до методу
onActivityResult

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 у вихідний потік
Охад Коен

@LukeCauthen ви намагалися бути власником пристрою? це спрацювало?
NetStarter

@NetStarter Так. Це просто біль у дупі, яка отримує додаток для власника пристрою. Як тільки ви це зробите, ви отримуєте багато енергії, яка, як правило, вимагає кореня.
Люк Каутен

1
Зауважте, що ви повинні додати android.permission.DELETE_PACKAGES у свій маніфест для видалення роботи (перевірена на рівні Api 22 або нижче)
benuk

4

Єдиний спосіб отримати доступ до цих методів - через рефлексію. Ви можете отримати ручку на PackageManagerоб’єкт, зателефонувавши getApplicationContext().getPackageManager()та використовуючи відображення доступу до цих методів. Ознайомтеся з цим підручником.


Це чудово працює з 2.2, але мені не пощастило використовувати його з 2.3
Хтось десь

3
Рефлексія не є стабільною для всіх версій api
HandlerExploit

3

Відповідно до вихідного коду Froyo, додатковий ключ Intent.EXTRA_INSTALLER_PACKAGE_NAME запитується на ім'я пакета інсталятора в PackageInstallerActivity.


1
Дивлячись на це зобов'язання , я думаю , він повинен працювати
sergio91pt

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?
yahya

1
@yahya developer.android.com/tools/help/shell.html знайдено фразою "pm android", pm = менеджер пакунків
18446744073709551615


Дуже дякую! Ці посилання - це дуже цікаві посібники для початку :)
yahya

@ V.Kalyuzhnyu Раніше він працював ще у 2015 році. IIRC це був Samsung Galaxy, можливо S5.
18446744073709551615

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);
}
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.