Как установить/обновить/удалить APK с помощью класса PackageInstaller в Android L?

Пожалуйста, проверьте класс ниже и дайте мне предложение о том, как их использовать https://developer.android.com/reference/android/content/pm/PackageInstaller.html https://developer.android.com/reference/android/content/pm/PackageInstaller.Session.html

Поэтому, пожалуйста, дайте мне пример установки/обновления/удаления приложения. Возможно ли, что новое приложение будет установлено в профиле владельца устройства?


comment
просто скажите, что вам нужно, вы хотите установить приложение на устройство   -  person Naveen Tamrakar    schedule 12.11.2014
comment
Привет, Навин Тамракар, я хочу установить apk без вывода сообщений на устройство, используя класс PackageInsatller, представленный в Android Lollipop.   -  person Sud    schedule 12.11.2014
comment
@Sud не думаю, что это работает тихо, это работает только с намерением.   -  person zaitsman    schedule 28.11.2014
comment
@zaitsman: Не могли бы вы объяснить подробнее?   -  person Sud    schedule 04.12.2014
comment
@zaitsman: я спрашиваю о том, как использовать класс PackageInstaller (представленный в Android Lollipop) для установки apk   -  person Sud    schedule 12.01.2015
comment
Разработчики, впервые пробующие PackageInstaller, не забудьте реализовать прослушиватель широковещательной рассылки, переданный ожидающему намерению. Я был сбит с толку, почему мой код не работал, потому что я оставил обработку возвращенной части намерения.   -  person Rookie    schedule 05.08.2020


Ответы (7)


Это возможно без разрешений системы из Android M и более поздние версии.

if ((mPm.checkUidPermission(android.Manifest.permission.INSTALL_PACKAGES, installerUid)
        == PackageManager.PERMISSION_GRANTED)
        || (installerUid == Process.ROOT_UID)
        || mIsInstallerDeviceOwner) {
    mPermissionsAccepted = true;
} else {
    mPermissionsAccepted = false;
}

Автоматическая установка и удаление приложений владельцем устройства:

Владелец устройства теперь может автоматически устанавливать и удалять приложения с помощью API PackageInstaller, независимо от Google Play for Work.

Подробнее в этом ссылка.


Это возможно с Android 6.0 и выше.

  • Сделайте свое приложение владельцем устройства.

Как только ваше приложение получит разрешение владельца устройства, мы сможем устанавливать, удалять и обновлять его в автоматическом режиме без вмешательства пользователя.

public static boolean installPackage(Context context, InputStream in, String packageName)
        throws IOException {
    PackageInstaller packageInstaller = context.getPackageManager().getPackageInstaller();
    PackageInstaller.SessionParams params = new PackageInstaller.SessionParams(
            PackageInstaller.SessionParams.MODE_FULL_INSTALL);
    params.setAppPackageName(packageName);
    // set params
    int sessionId = packageInstaller.createSession(params);
    PackageInstaller.Session session = packageInstaller.openSession(sessionId);
    OutputStream out = session.openWrite("COSU", 0, -1);
    byte[] buffer = new byte[65536];
    int c;
    while ((c = in.read(buffer)) != -1) {
        out.write(buffer, 0, c);
    }
    session.fsync(out);
    in.close();
    out.close();

    session.commit(createIntentSender(context, sessionId));
    return true;
}



private static IntentSender createIntentSender(Context context, int sessionId) {
        PendingIntent pendingIntent = PendingIntent.getBroadcast(
                context,
                sessionId,
                new Intent(ACTION_INSTALL_COMPLETE),
                0);
        return pendingIntent.getIntentSender();
    }

Удалить:

String appPackage = "com.your.app.package";
Intent intent = new Intent(getActivity(), getActivity().getClass());
PendingIntent sender = PendingIntent.getActivity(getActivity(), 0, intent, 0);
PackageInstaller mPackageInstaller = getActivity().getPackageManager().getPackageInstaller();
mPackageInstaller.uninstall(appPackage, sender.getIntentSender());

Git репозиторий здесь.

person amalBit    schedule 11.05.2016
comment
большое спасибо .. но я не могу установить apk .. весь код работает без ошибок, но apk не установлен. Это мое понимание неправильно. Для автоматической установки apk требуется рутированное устройство..? - person Sandeep R; 18.01.2017
comment
Для автоматической установки не требуется рутирование устройства. Ваш телефон должен работать под управлением Android M и выше. И вы должны дать разрешение владельца устройства на ваше приложение. @sandeep, следуйте этому .. codelabs.developers.google.com/codelabs/cosu/# 0 - person amalBit; 18.01.2017
comment
У меня та же проблема, что и у @sandeep, я запускаю приложение как владелец устройства на уровне API 22, сеанс фиксируется нормально, но затем, если я регистрирую обратный вызов сеанса в установщике пакета, прогресс сеанса всегда зависает на 0,8 ... есть мысли? ? - person Jakub Martyčák; 19.02.2017
comment
@JakubMartyčák Владелец устройства существует из API 21. Но доступ к установщику пакета возможен только из API 23 и выше. - person amalBit; 20.02.2017
comment
это работает для обновления вашего собственного приложения? Я могу установить другой apk, но не новую версию моего. - person Maragues; 24.05.2017
comment
@Maragues Вы также должны иметь возможность обновить свое приложение. Хотя я не помню, чтобы пытался. - person amalBit; 25.05.2017
comment
@amalBit из моих тестов, обновление собственного приложения работает на 6.0+, а не на 5.0. Последний позволяет устанавливать другие приложения. - person Maragues; 25.05.2017
comment
@Maragues Автоматическое обновление работает только с версии 6 и выше. (Для себя и других приложений) - person amalBit; 25.05.2017
comment
Как сделать ваше приложение владельцем устройства. ? Возможно ли это через пользовательский интерфейс ОС? Без рута или подключенного ПК? - person android developer; 22.07.2017
comment
@amalBit Так что обычный пользователь не может включить это, так как это не внутри пользовательского интерфейса ОС, верно? - person android developer; 23.07.2017
comment
@androiddeveloper это можно сделать на новом телефоне без подключенной учетной записи Google. Эта функция предназначена для корпоративных одноразовых устройств (в основном для киоск-приложений). - person amalBit; 23.07.2017
comment
@androiddeveloper ага - person amalBit; 24.07.2017
comment
Обратите внимание, что ACTION_INSTALL_COMPLETE — это переменная, которую вы должны объявить самостоятельно. В репозитории: github.com/googlesamples/android-testdpc/blob/ определяется как public static final String ACTION_INSTALL_COMPLETE = "com.afwsamples.testdpc.INSTALL_COMPLETE";. - person Dick Lucas; 20.09.2017
comment
Обратите внимание, что вы должны добавить android.permission.DELETE_PACKAGES в свой манифест, чтобы удаление работало (проверено на уровне API 22 или ниже). - person benchuk; 26.11.2017
comment
@amalBit @Maragues, как я могу сделать свое приложение владельцем устройства, если у меня есть root-права для моего приложения, поскольку я не хочу использовать EMM (рабочий профиль). Я пробовал dpm create-profile-owner <>, но установка/удаление не работает, пока код работает без ошибок... помощь будет очень признательна, так как я застрял в этой проблеме. со многих дней - person aki92; 22.03.2018
comment
Чтобы стать владельцем устройства, adb shell dpm set-device-owner com.mycompany.realapp/.ota.OTAAdminReceiver, который расширил developer.android.com/reference/android/app/admin/ Мне нужен второй APK (называется relauncher.apk) для прослушивания установки пакетов и перезапуска моего реального приложения. Чтобы это сработало, я запустил действие без головы вручную, иначе Android не уведомит BroadcastReceivers об установке нового пакета adb shell am start -n com.mycompany.relauncher/.HeadlessActivity Я не знаю, если это будет работать с изменениями Android 8 в BroadcastReceivers - person Maragues; 23.03.2018
comment
@Maragues большое спасибо за такой подробный ответ. он работал правильно. Могу ли я попросить вас еще об одной помощи, как я могу получить доступ к папке /data/data, будучи системным привилегированным приложением? ТАК вопросы. stackoverflow .com/questions/49463327/ - person aki92; 24.03.2018
comment
@amalBit, почему он может работать только для установки нового apk / обновления другого apk, но не для самого apk обновления? Нет никаких действий для session.commit по обновлению самого apk, есть предложения? - person paugoo; 12.07.2018
comment
@amalBit, значит, мы не можем напрямую автоматически обновить apk владельца устройства (используя приведенный выше код)? - person paugoo; 13.07.2018
comment
@paugoo Мы можем обновить приложение владельца устройства, но я не помню, как я это сделал. Прошло больше двух лет с тех пор, как я это сделал. - person amalBit; 13.07.2018
comment
@amalBit о, хорошо, я понимаю проблему, нам нужно убедиться, что хранилище ключей должно быть тем же, иначе оно может выйти из строя. Однако я столкнулся с другой проблемой: как обновить, если приложение находится в режиме блокировки. - person paugoo; 13.07.2018
comment
@paugoo Вы можете быть готовы с обновленным apk и запустить установку, когда приложение разблокировано. - person amalBit; 13.07.2018
comment
@amalBit левая проблема заключается в том, что после успешной установки он возвращается в домашнюю программу запуска и закрывает приложение, можно ли перезапустить его автоматически? Я поместил приемник для ожидания трансляции намерений ACTION_INSTALL_COMPLETE, но, похоже, он ничего не вызывает после завершения установки. - person paugoo; 16.07.2018
comment
@paugoo Я не знаю прямого решения этой проблемы. Но если вы можете сделать свое приложение средством запуска по умолчанию, то ваше приложение будет автоматически запущено. - person amalBit; 16.07.2018
comment
@amalBit да, вы правы, он может быть запущен автоматически, если мы установим его в качестве средства запуска по умолчанию. Однако на некоторых устройствах (версия Android 6.0) есть некоторые ошибки с блокировкой (приложение для закрепления), если мы устанавливаем его в качестве средства запуска по умолчанию, поэтому я не могу установить его по умолчанию. На данный момент я делаю какой-то хитрый способ, устанавливаю другую домашнюю программу запуска и помещаю внутрь приемник, затем транслирую перед обновлением и позволяю этой домашней программе запуска вызывать/запускать мое приложение. - person paugoo; 16.07.2018
comment
@paugoo Вы должны написать это как вопрос. Так он будет более заметным. - person amalBit; 16.07.2018

Вы не можете автоматически установить стороннее приложение во вновь созданном пользователе с помощью PackageInstaller.Session.commit() без конкретных "прав".
Вам либо нужно:

  • разрешение INSTALL_PACKAGES. Но это разрешение недоступно для стороннего приложения. Таким образом, даже с приложением владельца вашего профиля у вас не будет этого конкретного разрешения.
  • Запустите процесс как ROOT_UID. Это означает, что вам придется рутировать устройство.

Из Android исходный код:

if ((mPm.checkUidPermission(android.Manifest.permission.INSTALL_PACKAGES, installerUid) == PackageManager.PERMISSION_GRANTED) 
   || (installerUid == Process.ROOT_UID)) {
    mPermissionsAccepted = true;
} else {
    mPermissionsAccepted = false;
}

Если у вас нет корневого доступа и разрешения INSTALL_PACKAGES, пользователю будет предложено сообщение с вопросом, подтверждает ли он разрешения. Затем это подтверждение используется во время фиксации process сеанса PackageInstaller's. Очевидно, что в данном случае это непрозрачно, так как пользователю придется вручную подтверждать установку ваших приложений.

person Florent Dupont    schedule 16.01.2015
comment
Не могли бы вы подробнее рассказать о финальном этапе? Основываясь на вашем ответе, я вижу, что все еще могу использовать этот класс на L, но с некоторыми действиями со стороны пользователя. Я реализовал эту логику, но, к сожалению, при фиксации она не запускает намерение или не показывает ydialog - person Gleichmut; 02.12.2016
comment
Возможно ли вообще запустить процесс как ROOT_UID? - person dknchris; 03.03.2017
comment
Кажется, это так. Но для этого вам придется рутировать устройство. (=все приложения запускаются с правами root... больше информации в Википедии) . - person Florent Dupont; 03.03.2017
comment
Очень полезная ссылка: developer.android.com/reference/android/ спасибо, @FlorentDupont - person benchuk; 26.11.2017
comment
@FlorentDupont, как я могу запустить процесс установки/удаления пакетов как ROOT_UID, если у меня есть права root для моего приложения? - person aki92; 22.03.2018
comment
Не могли бы вы показать, как это сделать, используя root? Можно ли предоставить приложению это разрешение с помощью root (внутри самого приложения)? - person android developer; 15.06.2018

Метод установки, предоставленный @amalBit, у меня не сработал. Это странно, так как именно так это реализовано в Образец Google.

Этот ответ помог мне найти решение. Пришлось изменить некоторые части кода. Вот моя реализация:

public static void installPackage(Context context, InputStream inputStream)
        throws IOException {
    PackageInstaller packageInstaller = context.getPackageManager().getPackageInstaller();
    int sessionId = packageInstaller.createSession(new PackageInstaller
            .SessionParams(PackageInstaller.SessionParams.MODE_FULL_INSTALL));
    PackageInstaller.Session session = packageInstaller.openSession(sessionId);

    long sizeBytes = 0;

    OutputStream out = null;
    out = session.openWrite("my_app_session", 0, sizeBytes);

    int total = 0;
    byte[] buffer = new byte[65536];
    int c;
    while ((c = inputStream.read(buffer)) != -1) {
        total += c;
        out.write(buffer, 0, c);
    }
    session.fsync(out);
    inputStream.close();
    out.close();

    // fake intent
    IntentSender statusReceiver = null;
    Intent intent = new Intent(context, SomeActivity.class);
    PendingIntent pendingIntent = PendingIntent.getBroadcast(context,
            1337111117, intent, PendingIntent.FLAG_UPDATE_CURRENT);

    session.commit(pendingIntent.getIntentSender());
    session.close();
}

Этот метод можно вызвать так:

        InputStream inputStream = getActivity().getAssets().open("my_awesome_app.apk");
        InstallationHelper.installPackage(getActivity(), inputStream);
person devz    schedule 22.12.2016

Вы просто снимаете свои ограничения

public static DevicePolicyManager getDpm(Context context) {
    return (DevicePolicyManager) context.getSystemService(Context.DEVICE_POLICY_SERVICE);
}

public static ComponentName getAdmin(Context context) {
    return new ComponentName(context, MyDevicePolicyReceiver.class);
}

public static void addMyRestrictions(Context context) {
    getDpm(context).addUserRestriction(getAdmin(context), UserManager.DISALLOW_INSTALL_APPS);
    getDpm(context).addUserRestriction(getAdmin(context), UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES);
}

public static void clearMyRestrictions(Context context) {
    getDpm(context).clearUserRestriction(getAdmin(context), UserManager.DISALLOW_INSTALL_APPS);
    getDpm(context).clearUserRestriction(getAdmin(context), UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES);
}

public static void installPackage(Context context, InputStream inputStream)
        throws IOException {
    PackageInstaller packageInstaller = context.getPackageManager().getPackageInstaller();
    int sessionId = packageInstaller.createSession(new PackageInstaller
            .SessionParams(PackageInstaller.SessionParams.MODE_FULL_INSTALL));

    //openSession checks for user restrictions
    clearMyRestrictions(context);
    PackageInstaller.Session session = packageInstaller.openSession(sessionId);

    long sizeBytes = 0;

    OutputStream out = null;
    out = session.openWrite("my_app_session", 0, sizeBytes);

    int total = 0;
    byte[] buffer = new byte[65536];
    int c;
    while ((c = inputStream.read(buffer)) != -1) {
        total += c;
        out.write(buffer, 0, c);
    }
    session.fsync(out);
    inputStream.close();
    out.close();

    // fake intent
    IntentSender statusReceiver = null;
    Intent intent = new Intent(context, SomeActivity.class);
    PendingIntent pendingIntent = PendingIntent.getBroadcast(context,
            1337111117, intent, PendingIntent.FLAG_UPDATE_CURRENT);

    session.commit(pendingIntent.getIntentSender());
    session.close();
}
person Himmat Chavan    schedule 21.08.2017
comment
Это может дать ответ на поставленный вопрос, но, пожалуйста, объясните, почему ваш код отвечает на вопрос и как он это делает, чтобы другие могли извлечь из него уроки. - person Luuklag; 21.08.2017
comment
что такое MyDevicePolicyReceiver.class здесь? - person manjunath kallannavar; 08.12.2019

Это работает и для меня, хотя владелец моего устройства ограничивает установку приложений и неизвестных источников пользователем. Даже если я запускаю этот пример от имени администратора устройства, я получаю java.lang.SecurityException: User restriction prevents installing.

openSession проверяет разрешения. С помощью этой простой модификации можно сбросить пользовательские ограничения только во время короткого вызова метода.

public static DevicePolicyManager getDpm(Context context) {
    return (DevicePolicyManager)context.getSystemService(Context.DEVICE_POLICY_SERVICE);
}

public static ComponentName getAdmin(Context context) {
    return new ComponentName(context, MyDevicePolicyReceiver.class);
}

public static void addMyRestrictions(Context context) {
   getDpm(context).addUserRestriction(getAdmin(context), UserManager.DISALLOW_INSTALL_APPS);
   getDpm(context).addUserRestriction(getAdmin(context), UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES);
}

public static void clearMyRestrictions(Context context) {    
   getDpm(context).clearUserRestriction(getAdmin(context), UserManager.DISALLOW_INSTALL_APPS);
   getDpm(context).clearUserRestriction(getAdmin(context), UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES);
}

public static void installPackage(Context context, InputStream inputStream)
    throws IOException {
    PackageInstaller packageInstaller = context.getPackageManager().getPackageInstaller();
    int sessionId = packageInstaller.createSession(new PackageInstaller
        .SessionParams(PackageInstaller.SessionParams.MODE_FULL_INSTALL));

    //openSession checks for user restrictions
    clearMyRestrictions(context);
    PackageInstaller.Session session = packageInstaller.openSession(sessionId);
    addMyRestrictions(context);

    long sizeBytes = 0;

    OutputStream out = null;
    out = session.openWrite("my_app_session", 0, sizeBytes);

    int total = 0;
    byte[] buffer = new byte[65536];
    int c;
    while ((c = inputStream.read(buffer)) != -1) {
        total += c;
        out.write(buffer, 0, c);
    }
    session.fsync(out);
    inputStream.close();
    out.close();

    // fake intent
    IntentSender statusReceiver = null;
    Intent intent = new Intent(context, SomeActivity.class);
    PendingIntent pendingIntent = PendingIntent.getBroadcast(context,
        1337111117, intent, PendingIntent.FLAG_UPDATE_CURRENT);

    session.commit(pendingIntent.getIntentSender());
    session.close();
}

Пожалуйста, позаботьтесь об обработке исключений.

person Mr. Fish    schedule 24.01.2017
comment
Это для Android L или M? - person user1880957; 13.11.2018
comment
Есть ли способ определить, возникнет ли такая проблема? - person android developer; 26.02.2021

В Android Api-21 ниже приведен фрагмент кода, с помощью которого мы можем установить apk в автоматическом режиме.

private void runInstallWrite() throws IOException, RemoteException {
        long sizeBytes = -1;

        String opt;
        while ((opt = nextOption()) != null) {
            if (opt.equals("-S")) {
                sizeBytes = Long.parseLong(nextOptionData());
            } else {
                throw new IllegalArgumentException("Unknown option: " + opt);
            }
        }

        final int sessionId = Integer.parseInt(nextArg());
        final String splitName = nextArg();

        String path = nextArg();
        if ("-".equals(path)) {
            path = null;
        } else if (path != null) {
            final File file = new File(path);
            if (file.isFile()) {
                sizeBytes = file.length();
            }
        }

        final SessionInfo info = mInstaller.getSessionInfo(sessionId);

        PackageInstaller.Session session = null;
        InputStream in = null;
        OutputStream out = null;
        try {
            session = new PackageInstaller.Session(mInstaller.openSession(sessionId));

            if (path != null) {
                in = new FileInputStream(path);
            } else {
                in = new SizedInputStream(System.in, sizeBytes);
            }
            out = session.openWrite(splitName, 0, sizeBytes);

            int total = 0;
            byte[] buffer = new byte[65536];
            int c;
            while ((c = in.read(buffer)) != -1) {
                total += c;
                out.write(buffer, 0, c);

                if (info.sizeBytes > 0) {
                    final float fraction = ((float) c / (float) info.sizeBytes);
                    session.addProgress(fraction);
                }
            }
            session.fsync(out);

            System.out.println("Success: streamed " + total + " bytes");
        } finally {
            IoUtils.closeQuietly(out);
            IoUtils.closeQuietly(in);
            IoUtils.closeQuietly(session);
        }
    }

Приведенный выше код взят из Framework здесь

Могу ли я использовать этот код с владельцем устройства или обычным пользователем в LoLiipop?

Ответ - нет, так как есть API, которые были тегом @hide в фреймворках Android, хотя PackageManager.Session введен в API 21, но мы не можем использовать новый PAckageManager.Session(), поскольку он @hide в API 21.

Если вы все еще хотите использовать этот код через framework.jar , вам нужно собрать исходный код Lolippop и извлечь jar из out/..../framework.jar и вызвать вышеуказанный API.

person KOTIOS    schedule 17.12.2014
comment
Теперь, с новым Android M, автоматическая установка возможна как владелец устройства. Я хотел бы использовать PackageInstaller API, но, к сожалению, ваш пример кода не помог. У кого-нибудь есть лучший пример использования PackageInstaller? - person ZouBi; 25.06.2015

УСТАНОВИТЬ:

Intent promptInstall = new Intent(Intent.ACTION_VIEW);
        promptInstall.setDataAndType(Uri.fromFile(new File(Environment
                .getExternalStorageDirectory() + "/download/" + APK_NAME)),
                "application/vnd.android.package-archive");
        promptInstall.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
        context.startActivity(promptInstall);

УДАЛИТЬ:

Intent intent = new Intent(Intent.ACTION_DELETE, Uri.fromParts("package",
getPackageManager().getPackageArchiveInfo(apkUri.getPath(), 0).packageName,null));
startActivity(intent);
person Vibhor Chopra    schedule 12.11.2014
comment
Пожалуйста, внимательно прочитайте мой вопрос. Я спрашиваю о том, как использовать класс PackageInstaller (представленный в Android Lollipop) для установки apk. - person Sud; 12.11.2014