instaluj / odinstaluj pliki APK programowo (PackageManager vs Intents)


142

Moja aplikacja instaluje inne aplikacje i musi śledzić, jakie aplikacje zainstalowała. Oczywiście można to osiągnąć, po prostu zachowując listę zainstalowanych aplikacji. Ale to nie powinno być konieczne! Za utrzymanie relacji installedBy (a, b) powinien odpowiadać pakiet PackageManager. W rzeczywistości według API jest to:

public abstract String getInstallerPackageName (String nazwa_pakietu) - pobierz nazwę pakietu aplikacji, która zainstalowała pakiet. Pozwala to zidentyfikować rynek, z którego pochodzi pakiet.

Obecne podejście

Zainstaluj APK przy użyciu Intent

Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setDataAndType(apkUri, "application/vnd.android.package-archive");
startActivity(intent);

Odinstaluj APK przy użyciu zamiaru:

Intent intent = new Intent(Intent.ACTION_DELETE, Uri.fromParts("package",
getPackageManager().getPackageArchiveInfo(apkUri.getPath(), 0).packageName,null));
startActivity(intent);

Oczywiście nie jest to sposób, w jaki np. Android Market instaluje / odinstalowuje pakiety. Używają bogatszej wersji PackageManager. Można to zobaczyć, pobierając kod źródłowy Androida z repozytorium Android Git. Poniżej znajdują się dwie ukryte metody, które odpowiadają podejściu Intent. Niestety nie są one dostępne dla zewnętrznych programistów. Ale może będą w przyszłości?

Lepsze podejście

Instalowanie APK przy użyciu 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);

Odinstalowywanie APK przy użyciu 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);

Różnice

  • Podczas używania intencji lokalny menedżer pakietów nie jest informowany, z której aplikacji pochodzi instalacja. W szczególności getInstallerPackageName (...) zwraca null.

  • Ukryta metoda installPackage (...) przyjmuje nazwę pakietu instalatora jako parametr i najprawdopodobniej jest w stanie ustawić tę wartość.

Pytanie

Czy można określić nazwę instalatora pakietu za pomocą intencji? (Może nazwę pakietu instalatora można dodać jako dodatek do zamiaru instalacji?)

Wskazówka: jeśli chcesz pobrać kod źródłowy Androida, możesz wykonać kroki opisane tutaj: Pobieranie drzewa źródłowego. Aby wyodrębnić pliki * .java i umieścić je w folderach zgodnie z hierarchią pakietów, możesz sprawdzić ten zgrabny skrypt: Wyświetl kod źródłowy Androida w Eclipse .


W tekście brakuje niektórych identyfikatorów URI. Dodam je, gdy tylko będę mógł (nowi użytkownicy mają pewne ograniczenia, aby zapobiec spamowi).
Håvard Geithus

1
jak wyłączyć funkcję odinstalowywania?

2
@ user938893: "jak wyłączyć funkcję odinstalowywania?" - Pracujemy nad jakimś trudnym do odinstalowania złośliwym oprogramowaniem?
Daniel

Odpowiedzi:


66

Obecnie nie jest to dostępne dla aplikacji innych firm. Zauważ, że nawet użycie refleksji lub innych sztuczek w celu uzyskania dostępu do installPackage () nie pomoże, ponieważ tylko aplikacje systemowe mogą go używać. (Dzieje się tak, ponieważ jest to mechanizm instalacji niskopoziomowej, po zatwierdzeniu uprawnień przez użytkownika, więc zwykłe aplikacje nie mają do niego dostępu).

Również argumenty funkcji installPackage () często zmieniały się między wydaniami platformy, więc wszystko, co zrobisz, próbując uzyskać dostęp, zakończy się niepowodzeniem w różnych innych wersjach platformy.

EDYTOWAĆ:

Warto również zwrócić uwagę, że ten pakiet instalacyjny został dodany stosunkowo niedawno do platformy (2.2?) I pierwotnie nie był używany do śledzenia, kto zainstalował aplikację - jest używany przez platformę do określenia, kogo uruchomić podczas zgłaszania błędów aplikacja do wdrażania funkcji Android Feedback. (Był to również jeden z przypadków zmiany argumentów metody API). Przez co najmniej długi czas po jej wprowadzeniu Market nadal nie używał jej do śledzenia zainstalowanych aplikacji (i może nadal jej nie używać. ), ale zamiast tego po prostu ustawił aplikację Android Feedback (która była niezależna od Market) jako „właściciela” zajmującego się opiniami.


„Zauważ, że nawet użycie refleksji lub innych sztuczek w celu uzyskania dostępu do installPackage () nie pomoże, ponieważ mogą z niego korzystać tylko aplikacje systemowe”. Załóżmy, że tworzę pakiet instalujący / usuwający / zarządzający aplikacją dla danej platformy, innej niż sam natywny Android. Jak mogę uzyskać dostęp do instalacji / usunięcia?
dascandy

startActivity () z odpowiednio uformowaną intencją. (Jestem pewien, że odpowiedź na to pytanie została udzielona w innym miejscu na StackOverflow, więc nie spróbuję podać dokładnej odpowiedzi,
ponieważ istnieje

mmmkay, co powoduje wyświetlenie standardowych okien dialogowych instalacji / usuwania systemu Android. Te szczegóły zostały już omówione - szukam funkcji „po prostu **** zainstaluj ten pakiet” i „tylko **** usuń ten pakiet”, dosłownie bez zadawania pytań.
dascandy

2
Jak powiedziałem, nie są one dostępne dla aplikacji innych firm. Jeśli tworzysz własny obraz systemu, masz implementację platformy i możesz tam znaleźć funkcje, ale nie są one częścią interfejsów API dostępnych dla zwykłych aplikacji innych firm.
hackbod

Robię eksplorator plików APK z funkcją instalacji, usuwania i tworzenia kopii zapasowych, więc czy Google pozwala mi na publikowanie mojej aplikacji w Google Play? i którą politykę zamierzamy złamać?
Rahul Mandaliya

86

Android P + wymaga tego uprawnienia w AndroidManifest.xml

<uses-permission android:name="android.permission.REQUEST_DELETE_PACKAGES" />

Następnie:

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

odinstalować. Wydaje się łatwiejsze ...


Czy to może być aplikacja, w której działa kod? jak w onDestroy()metodzie?
Mahdi-Malv

a co z ACTION_INSTALL_PACKAGE? czy możemy pobrać i zainstalować najnowszą wersję naszej aplikacji ze sklepu Play?
MAS. Jana

3
Ponieważ usuwanie aplikacji z systemu Android P wymaga wyraźnego pozwolenia „android.permission.REQUEST_DELETE_PACKAGES”, niezależnie od tego, czy używasz „ACTION_DELETE”, czy „ACTION_UNINSTALL_PACKAGE” developer.android.com/reference/android/content/…
Darklord5

Dziękuję za wspomnienie o zezwoleniu na Androida P, utknąłem i nie byłem pewien, co się działo wcześniej.
Avi Parshan

43

Interfejs API poziomu 14 wprowadził dwie nowe akcje: ACTION_INSTALL_PACKAGE i ACTION_UNINSTALL_PACKAGE . Te akcje pozwalają przekazać dodatkowe wartości logiczne EXTRA_RETURN_RESULT, aby otrzymać powiadomienie o (nie) instalacji.

Przykładowy kod wywołujący okno dezinstalacji:

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

I odbierz powiadomienie w metodzie 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");
        }
    }
}

jak mogę potwierdzić w tym oknie dialogowym akcji, że któryś z użytkowników nacisnął przycisk OK lub Anuluj,
abym

2
@Erum Dodałem przykład tego, o co prosiłeś
Alex Lipov

Podczas instalacji przycisk anulowania nie zwrócił wyniku z powrotem do metody
onActivityResult

2
Począwszy od API 25, wywołanie ACTION_INSTALL_PACKAGEbędzie wymagało REQUEST_INSTALL_PACKAGESuprawnień na poziomie podpisu . Podobnie, począwszy od API 28 (Android P), wywołanie ACTION_UNINSTALL_PACKAGEbędzie wymagało REQUEST_DELETE_PACKAGESzezwolenia nieszkodliwego . Przynajmniej według docs.
Steve Blackwell

22

Jeśli masz uprawnienia właściciela urządzenia (lub właściciela profilu, nie próbowałem), możesz po cichu instalować / odinstalowywać pakiety za pomocą interfejsu API właściciela urządzenia.

do odinstalowania:

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

i aby zainstalować pakiet:

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

Wiedziałem, że musi to być możliwe, będąc urządzeniem właściciela. Dziękuję za odpowiedź!
Luke Cauthen

@sandeep po prostu wczytuje zawartość APK do strumienia wyjściowego
Ohad Cohen

@LukeCauthen czy próbowałeś być właścicielem urządzenia? czy to zadziałało?
NetStarter

@NetStarter Tak, mam. To tylko upierdliwe, że aplikacja jest właścicielem urządzenia. Gdy to zrobisz, uzyskasz dużo mocy, która normalnie wymagałaby rootowania.
Luke Cauthen

1
Pamiętaj, że musisz dodać android.permission.DELETE_PACKAGES do swojego manifestu, aby odinstalowanie działało (testowane na poziomie 22 lub niższym Api)
benchuk

4

Jedynym sposobem uzyskania dostępu do tych metod jest refleksja. Możesz uzyskać uchwyt do PackageManagerobiektu, wywołując getApplicationContext().getPackageManager()i używając odbicia dostępu do tych metod. Sprawdź ten samouczek.


Działa to świetnie z wersją 2.2, ale nie miałem szczęścia używając go z wersją 2.3
Someone Somewhere

3
Odbicie nie jest stabilne we wszystkich wersjach API
HandlerExploit

3

Zgodnie z kodem źródłowym Froyo, dodatkowy klucz Intent.EXTRA_INSTALLER_PACKAGE_NAME jest odpytywany o nazwę pakietu instalatora w PackageInstallerActivity.


1
Patrząc na to zobowiązanie , myślę, że powinno działać
sergio91pt

2

Na zrootowanym urządzeniu możesz użyć:

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() jest zdefiniowane tutaj.


Czy istnieje sposób na zainstalowanie wstępnie pobranej aplikacji również na karcie SD? Czy możesz zasugerować mi jakąś stronę, aby sprawdzić, jakich poleceń możemy używać w powłoce na platformie Android?
yahya

1
@yahya developer.android.com/tools/help/shell.html znaleziony przez wyrażenie „pm android”, pm = menedżer pakietów
18446744073709551615


Wielkie dzięki! Te linki to naprawdę fajne przewodniki na początek :)
yahya

@ V.Kalyuzhnyu Kiedyś działało w 2015 roku. IIRC to był Samsung Galaxy, może S5.
18446744073709551615

2

Jeśli przekazujesz nazwę pakietu jako parametr do dowolnej funkcji zdefiniowanej przez użytkownika, użyj poniższego kodu:

    Intent intent=new Intent(Intent.ACTION_DELETE);
    intent.setData(Uri.parse("package:"+packageName));
    startActivity(intent);

0

Jeśli używasz Kotlin, API 14+ i po prostu chcesz wyświetlić okno dezinstalacji swojej aplikacji:

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

Możesz zmienić packageNamena dowolną inną nazwę pakietu, jeśli chcesz poprosić użytkownika o odinstalowanie innej aplikacji na urządzeniu


0

Warunek wstępny:

Twój plik APK musi być podpisany przez system, jak poprawnie wskazano wcześniej. Jednym ze sposobów osiągnięcia tego jest samodzielne zbudowanie obrazu AOSP i dodanie kodu źródłowego do kompilacji.

Kod:

Po zainstalowaniu jako aplikacja systemowa możesz użyć metod menedżera pakietów, aby zainstalować i odinstalować pakiet APK w następujący sposób:

Zainstalować:

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

Odinstaluj:

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

Aby uzyskać oddzwonienie po zainstalowaniu / odinstalowaniu pakietu APK, możesz użyć tego:

/**
 * 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);
}
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.