APKs programmgesteuert installieren / deinstallieren (PackageManager vs Intents)

141

Meine Anwendung installiert andere Anwendungen und muss nachverfolgen, welche Anwendungen sie installiert hat. Dies könnte natürlich erreicht werden, indem einfach eine Liste der installierten Anwendungen geführt wird. Das sollte aber nicht nötig sein! Es sollte in der Verantwortung des PackageManager liegen, die installierte By (a, b) -Beziehung aufrechtzuerhalten. In der Tat ist es laut API:

public abstract String getInstallerPackageName (String packageName) - Ruft den Paketnamen der Anwendung ab, die ein Paket installiert hat. Dies identifiziert, von welchem ​​Markt das Paket kam.

Der aktuelle Ansatz

Installieren Sie APK mit Intent

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

Deinstallieren Sie APK mit Intent:

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

Dies ist offensichtlich nicht der Weg, z. B. installiert / deinstalliert Android Market Pakete. Sie verwenden eine umfangreichere Version des PackageManager. Dies kann durch Herunterladen des Android-Quellcodes aus dem Android Git-Repository festgestellt werden. Nachfolgend sind die beiden versteckten Methoden aufgeführt, die dem Intent-Ansatz entsprechen. Leider stehen sie externen Entwicklern nicht zur Verfügung. Aber vielleicht werden sie es in Zukunft sein?

Der bessere Ansatz

Installieren von APK mit dem 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);

Deinstallation von APK mit dem 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);

Unterschiede

  • Bei Verwendung von Absichten wird dem lokalen Paketmanager nicht mitgeteilt, von welcher Anwendung die Installation stammt. Insbesondere gibt getInstallerPackageName (...) null zurück.

  • Die versteckte Methode installPackage (...) verwendet den Namen des Installationspakets als Parameter und kann diesen Wert höchstwahrscheinlich festlegen.

Frage

Ist es möglich, den Namen des Paketinstallationsprogramms mit Absichten anzugeben? (Vielleicht kann der Name des Installationspakets als Extra zur Installationsabsicht hinzugefügt werden?)

Tipp: Wenn Sie den Android-Quellcode herunterladen möchten, können Sie die hier beschriebenen Schritte ausführen: Herunterladen des Quellbaums. Um die * .java-Dateien zu extrahieren und sie gemäß der Pakethierarchie in Ordnern abzulegen, können Sie dieses übersichtliche Skript überprüfen: Anzeigen des Android-Quellcodes in Eclipse .

Håvard Geithus
quelle
Einige der URIs fehlen im Text. Ich werde sie hinzufügen, sobald ich darf (neue Benutzer haben einige Einschränkungen, um Spam zu verhindern).
Håvard Geithus
1
Wie deaktiviere ich die Deinstallationsfunktion?
2
@ user938893: "Wie deaktiviere ich die Deinstallationsfunktion?" - Arbeiten wir an schwer zu deinstallierender Malware?
Daniel

Antworten:

66

Dies ist derzeit nicht für Anwendungen von Drittanbietern verfügbar. Beachten Sie, dass selbst die Verwendung von Reflection oder anderen Tricks für den Zugriff auf installPackage () nicht hilfreich ist, da nur Systemanwendungen es verwenden können. (Dies liegt daran, dass es sich um den Installationsmechanismus auf niedriger Ebene handelt, nachdem die Berechtigungen vom Benutzer genehmigt wurden. Daher ist der Zugriff auf reguläre Anwendungen nicht sicher.)

Außerdem haben sich die Funktionsargumente von installPackage () zwischen den Plattformversionen häufig geändert, sodass alles, was Sie versuchen, darauf zuzugreifen, auf verschiedenen anderen Versionen der Plattform fehlschlägt.

BEARBEITEN:

Es ist auch erwähnenswert, dass dieses installerPackage erst vor relativ kurzer Zeit zur Plattform hinzugefügt wurde (2.2?) Und ursprünglich nicht zum Nachverfolgen verwendet wurde, wer die App installiert hat. Es wird von der Plattform verwendet, um zu bestimmen, wer gestartet werden soll, wenn Fehler gemeldet werden die App zur Implementierung von Android Feedback. (Dies war auch einer der Fälle, in denen sich die Argumente der API-Methode geändert haben.) Zumindest lange Zeit nach ihrer Einführung verwendete Market sie immer noch nicht, um die installierten Apps zu verfolgen (und sie wird sie möglicherweise immer noch nicht verwenden ), sondern nur die Android-Feedback-App (die vom Markt getrennt war) als "Eigentümer" festgelegt, um sich um das Feedback zu kümmern.

Hackbod
quelle
"Beachten Sie, dass selbst die Verwendung von Reflection oder anderen Tricks für den Zugriff auf installPackage () nicht hilfreich ist, da nur Systemanwendungen es verwenden können." Angenommen, ich erstelle ein Paket zum Installieren / Entfernen / Verwalten einer App für eine bestimmte Plattform, außer für natives Android. Wie soll ich auf install / remove zugreifen?
Dascandy
startActivity () mit einer entsprechend geformten Absicht. (Ich bin sicher, dass dies an anderer Stelle auf StackOverflow beantwortet wurde, daher werde ich nicht versuchen, die genaue Antwort hier zu geben, da die Gefahr besteht, dass etwas schief geht.)
Hackbod
mmmkay, das ruft die Standarddialoge zum Installieren / Entfernen von Android auf. Diese Details wurden bereits behandelt - ich suche nach den Funktionen "Nur dieses Paket installieren" und "Dieses Paket nur entfernen", buchstäblich ohne Fragen.
Dascandy
2
Wie gesagt, diese stehen Anwendungen von Drittanbietern nicht zur Verfügung. Wenn Sie ein eigenes System-Image erstellen, verfügen Sie über die Plattformimplementierung und können die Funktionen dort finden. Sie sind jedoch nicht Teil der APIs, die normalen Apps von Drittanbietern zur Verfügung stehen.
Hackbod
Ich mache Apk File Explorer mit Installations-, Entfernungs- und Sicherungsfunktionen. Erlaubt Google mir also, meine Anwendung weiterhin auf Google Play zu veröffentlichen? und welche Politik werden wir brechen?
Rahul Mandaliya
84

Android P + erfordert diese Berechtigung in AndroidManifest.xml

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

Dann:

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

zu deinstallieren. Scheint einfacher ...

JohnyTex
quelle
Kann dies die App sein, auf der der Code ausgeführt wird? wie in der onDestroy()Methode?
Mahdi-Malv
Wie wäre es mit ACTION_INSTALL_PACKAGE? Können wir die neueste Version unserer App aus dem Play Store herunterladen und installieren?
MAS. John
3
Da das Löschen von Apps für Android P die Manifest-Berechtigung "android.permission.REQUEST_DELETE_PACKAGES" erfordert, unabhängig davon, ob Sie "ACTION_DELETE" oder "ACTION_UNINSTALL_PACKAGE" verwenden. Developer.android.com/reference/android/content/…
Darklord5
Vielen Dank, dass Sie die Android P-Berechtigung erwähnt haben. Ich steckte fest und war mir nicht sicher, was vorher los war.
Avi Parshan
43

API-Level 14 führte zwei neue Aktionen ein: ACTION_INSTALL_PACKAGE und ACTION_UNINSTALL_PACKAGE . Mit diesen Aktionen können Sie das boolesche Extra EXTRA_RETURN_RESULT übergeben , um eine Benachrichtigung über das (Un-) Installationsergebnis zu erhalten.

Beispielcode zum Aufrufen des Deinstallationsdialogs:

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

Und erhalten Sie die Benachrichtigung in Ihrer Activity # onActivityResult- Methode:

@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");
        }
    }
}
Pir Fahim Shah
quelle
Wie kann ich in diesem Aktionsdialogfeld bestätigen, dass entweder der Benutzer
OK
2
@Erum Ich habe ein Beispiel für das hinzugefügt, was Sie gefragt haben
Alex Lipov
Bei der Installation erhielt die Schaltfläche Abbrechen kein Ergebnis zurück zur onActivityResult-Methode
diyoda_
2
Ab API 25 ACTION_INSTALL_PACKAGEerfordert der Aufruf die REQUEST_INSTALL_PACKAGESBerechtigung auf Signaturstufe . Ab API 28 (Android P) ist für Anrufe ACTION_UNINSTALL_PACKAGEebenfalls die ungefährliche REQUEST_DELETE_PACKAGESBerechtigung erforderlich . Zumindest nach den Unterlagen.
Steve Blackwell
22

Wenn Sie über die Berechtigung Gerätebesitzer (oder Profilbesitzer, die ich nicht ausprobiert habe) verfügen, können Sie Pakete mithilfe der Gerätebesitzer-API unbeaufsichtigt installieren / deinstallieren.

zur Deinstallation:

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

und um das Paket zu installieren:

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;
}
Ohad Cohen
quelle
Ich wusste, dass es möglich sein musste, dies als Besitzergerät zu tun. Danke für die Antwort!
Luke Cauthen
@ Sandeep liest es nur den Inhalt der APK in den Ausgabestream
Ohad Cohen
@LukeCauthen haben Sie versucht, als Gerätebesitzer? hat es funktioniert
NetStarter
@ NetStarter Ja, das habe ich. Es ist nur eine Qual, eine App zum Gerätebesitzer zu machen. Sobald Sie dies tun, erhalten Sie eine Menge Leistung, die normalerweise root erfordern würde.
Luke Cauthen
1
Bitte beachten Sie, dass Sie android.permission.DELETE_PACKAGES zu Ihrem Manifest hinzufügen müssen, damit die Deinstallation funktioniert (getestet auf API-Level 22 oder darunter)
Benchuk
4

Der einzige Weg, auf diese Methoden zuzugreifen, ist die Reflexion. Sie können ein PackageManagerObjekt in den Griff bekommen , getApplicationContext().getPackageManager()indem Sie diese Methoden aufrufen und Reflection Access verwenden. Kasse dieses Tutorial.

HandlerExploit
quelle
Dies funktioniert gut mit 2.2, aber ich hatte kein Glück mit 2.3
Someone Somewhere
3
Die Reflexion ist nicht in allen API-Versionen stabil
HandlerExploit
3

Gemäß dem Froyo-Quellcode wird der zusätzliche Schlüssel Intent.EXTRA_INSTALLER_PACKAGE_NAME nach dem Namen des Installationspakets in der PackageInstallerActivity abgefragt.

njzk2
quelle
1
Wenn ich mir dieses Commit anschaue , denke ich, dass es funktionieren sollte
sergio91pt
2

Auf einem gerooteten Gerät können Sie Folgendes verwenden:

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() ist hier definiert.

18446744073709551615
quelle
Gibt es eine Möglichkeit, eine vorab heruntergeladene Anwendung auch auf SD-Karte zu installieren? Oder können Sie mir eine Seite vorschlagen, um zu überprüfen, welche Befehle wir für die Shell auf der Android-Plattform verwenden können?
Yahoo
1
@yahya developer.android.com/tools/help/shell.html gefunden durch die Phrase "pm android", pm = Paketmanager
18446744073709551615
1
@yahya cheatography.com/citguy/cheat-sheets/android-package-manager-pm set-install-location
18446744073709551615
Vielen Dank! Diese Links sind wirklich coole Anleitungen, um damit zu beginnen :)
yahya
@ V.Kalyuzhnyu Es hat 2015 funktioniert. IIRC war ein Samsung Galaxy, vielleicht S5.
18446744073709551615
2

Wenn Sie den Paketnamen als Parameter an eine Ihrer benutzerdefinierten Funktionen übergeben, verwenden Sie den folgenden Code:

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

Wenn Sie Kotlin, API 14+ verwenden und nur den Deinstallationsdialog für Ihre App anzeigen möchten:

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

Sie können packageNamezu einem anderen Paketnamen wechseln , wenn Sie den Benutzer auffordern möchten, eine andere App auf dem Gerät zu deinstallieren

Louis CAD
quelle
0

Voraussetzung:

Ihre APK muss vom System signiert werden, wie bereits erwähnt. Eine Möglichkeit, dies zu erreichen, besteht darin, das AOSP-Image selbst zu erstellen und den Quellcode zum Build hinzuzufügen.

Code:

Nach der Installation als System-App können Sie die APP mithilfe der Paketmanager-Methoden wie folgt installieren und deinstallieren:

Installieren:

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

Deinstallieren:

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

Um einen Rückruf zu erhalten, sobald Ihr APK installiert / deinstalliert ist, können Sie Folgendes verwenden:

/**
 * 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);
}
Phoebus
quelle