installa / disinstalla APK a livello di codice (PackageManager vs Intents)


141

La mia applicazione installa altre applicazioni e deve tenere traccia delle applicazioni installate. Naturalmente, questo potrebbe essere ottenuto semplicemente mantenendo un elenco di applicazioni installate. Ma questo non dovrebbe essere necessario! Dovrebbe essere responsabilità del PackageManager mantenere la relazione InstallBy (a, b). Infatti, secondo l'API è:

abstract pubblico String getInstallerPackageName (String nomepacchetto) - Recupera il nome del pacchetto dell'applicazione che ha installato un pacchetto. Ciò identifica da quale mercato proviene il pacchetto.

L'attuale approccio

Installa APK usando Intent

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

Disinstallare APK usando Intent:

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

Questo ovviamente non è il modo in cui Android Market installa / disinstalla i pacchetti. Usano una versione più ricca di PackageManager. Questo può essere visto scaricando il codice sorgente Android dal repository Android Git. Di seguito sono riportati i due metodi nascosti che corrispondono all'approccio Intento. Purtroppo non sono disponibili per sviluppatori esterni. Ma forse saranno in futuro?

L'approccio migliore

Installazione di APK tramite 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);

Disinstallazione di APK mediante 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);

differenze

  • Quando si utilizzano gli intenti, il gestore pacchetti locale non viene a conoscenza di quale applicazione abbia avuto origine l'installazione. In particolare, getInstallerPackageName (...) restituisce null.

  • Il metodo nascosto installPackage (...) accetta il nome del pacchetto di installazione come parametro ed è probabilmente in grado di impostare questo valore.

Domanda

È possibile specificare il nome del programma di installazione del pacchetto utilizzando gli intenti? (Forse il nome del pacchetto di installazione può essere aggiunto come extra all'intenzione di installazione?)

Suggerimento: se si desidera scaricare il codice sorgente Android, è possibile seguire i passaggi descritti qui: Download della struttura dei sorgenti. Per estrarre i file * .java e metterli in cartelle in base alla gerarchia dei pacchetti, puoi dare un'occhiata a questo script accurato: Visualizza il codice sorgente Android in Eclipse .


Alcuni degli URI mancano nel testo. Li aggiungerò non appena sono autorizzato (i nuovi utenti hanno alcune restrizioni per prevenire lo spam).
Håvard Geithus, il

1
come disabilitare la funzionalità di disinstallazione?

2
@ user938893: "come disabilitare la funzionalità di disinstallazione?" - Stiamo lavorando su alcuni malware difficili da disinstallare, vero?
Daniel,

Risposte:


66

Questo non è attualmente disponibile per applicazioni di terze parti. Si noti che anche l'utilizzo di reflection o altri trucchi per accedere a installPackage () non sarà di aiuto, poiché solo le applicazioni di sistema possono utilizzarlo. (Questo perché è il meccanismo di installazione di basso livello, dopo che le autorizzazioni sono state approvate dall'utente, quindi non è sicuro per le normali applicazioni avere accesso.)

Anche gli argomenti della funzione installPackage () sono spesso cambiati tra le versioni della piattaforma, quindi qualsiasi cosa tu faccia provando ad accedervi fallirà su varie altre versioni della piattaforma.

MODIFICARE:

Inoltre, vale la pena sottolineare che questo pacchetto di installazione è stato aggiunto abbastanza di recente alla piattaforma (2.2?) E non è stato originariamente utilizzato per il monitoraggio di chi ha installato l'app, ma viene utilizzato dalla piattaforma per determinare con chi avviare i bug quando si segnalano bug l'app, per l'implementazione di Feedback Android. (Questa è stata anche una delle volte in cui gli argomenti del metodo API sono cambiati.) Per almeno un po 'di tempo dopo che è stato introdotto, Market non l'ha ancora usato per tracciare le app che ha installato (e potrebbe benissimo non usarlo ancora ), ma invece ha usato questo per impostare l'app Feedback Android (che era separata da Market) come "proprietario" per occuparsi del feedback.


"Si noti che anche l'utilizzo di reflection o altri trucchi per accedere a installPackage () non sarà di aiuto, poiché solo le applicazioni di sistema possono utilizzarlo." Supponiamo che stia realizzando un'app di installazione / rimozione / gestione di pacchetti per una determinata piattaforma, diversa dallo stesso Android nativo. Come devo accedere all'installazione / rimozione?
dascandy,

startActivity () con un intento adeguatamente formato. (Sono sicuro che questa è stata risposta altrove su StackOverflow, quindi non proverò a dare la risposta esatta qui a rischio di sbagliare qualcosa.)
hackbod

mmmkay, che visualizza le finestre di dialogo di installazione / rimozione standard di Android. Questi dettagli sono già stati gestiti: sto cercando le funzioni "solo **** installa questo pacchetto" e "solo **** rimuovi questo pacchetto", letteralmente senza fare domande.
dascandy,

2
Come ho già detto, questi non sono disponibili per le applicazioni di terze parti. Se stai creando la tua immagine di sistema, hai l'implementazione della piattaforma e puoi trovare lì le funzioni, ma non fanno parte delle API disponibili per le normali app di terze parti.
hackbod,

sto facendo apk file explorer con funzionalità di installazione, rimozione e backup, quindi Google mi consente di continuare a pubblicare la mia applicazione su Google Play? e quale politica infrangeremo?
Rahul Mandaliya,

85

Android P + richiede questa autorizzazione in AndroidManifest.xml

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

Poi:

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

per disinstallare. Sembra più facile ...


Può essere questa l'app che esegue il codice? come nel onDestroy()metodo?
Mahdi-Malv,

che ne dici di ACTION_INSTALL_PACKAGE? possiamo scaricare e installare l'ultima versione della nostra app dal Play Store?
MAS. Giovanni

3
Poiché Android P elimina le app richiede l'autorizzazione manifest "android.permission.REQUEST_DELETE_PACKAGES", indipendentemente dal fatto che utilizzi "ACTION_DELETE" o "ACTION_UNINSTALL_PACKAGE" developer.android.com/reference/android/content/…
Darklord5

Grazie per aver menzionato l'autorizzazione Android P, ero bloccato e non ero sicuro di cosa stesse succedendo prima.
Avi Parshan,

43

Il livello API 14 ha introdotto due nuove azioni: ACTION_INSTALL_PACKAGE e ACTION_UNINSTALL_PACKAGE . Queste azioni ti consentono di superare EXTRA_RETURN_RESULT extra booleani per ricevere una notifica (non) del risultato dell'installazione.

Codice di esempio per richiamare la finestra di dialogo di disinstallazione:

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

E ricevi la notifica nel tuo metodo 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");
        }
    }
}

come posso confermare da questa finestra di dialogo dell'azione che uno degli utenti ha premuto ok o annulla in modo da poter prendere la decisione in base a questo
Erum

2
@Erum Ho aggiunto un esempio per quello che mi hai chiesto
Alex Lipov,

Durante l'installazione, il pulsante Annulla non ha restituito un risultato al metodo
onActivityResult

2
A partire da API 25, la chiamata ACTION_INSTALL_PACKAGErichiederà l' REQUEST_INSTALL_PACKAGESautorizzazione a livello di firma . Allo stesso modo, a partire da API 28 (Android P), la chiamata ACTION_UNINSTALL_PACKAGErichiederà l' REQUEST_DELETE_PACKAGESautorizzazione non pericolosa . Almeno secondo i documenti.
Steve Blackwell,

22

Se si dispone dell'autorizzazione del proprietario del dispositivo (o del proprietario del profilo, non ho provato) è possibile installare / disinstallare silenziosamente i pacchetti utilizzando l'API del proprietario del dispositivo.

per la disinstallazione:

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

e per installare il pacchetto:

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

Sapevo che doveva essere possibile farlo essendo il dispositivo proprietario. Grazie per la risposta!
Luke Cauthen,

@sandeep legge solo il contenuto dell'APK nel flusso di output
Ohad Cohen

@LukeCauthen hai provato a essere proprietario del dispositivo? ha funzionato?
NetStarter,

@NetStarter Sì, l'ho fatto. È solo un dolore nel culo ottenere un'app come proprietario del dispositivo. Una volta che lo fai, ottieni molta potenza che normalmente richiederebbe il root.
Luke Cauthen,

1
Nota che devi aggiungere android.permission.DELETE_PACKAGES al tuo manifest per far funzionare la disinstallazione (testato su Api livello 22 o inferiore)
Benchuk,

4

L'unico modo per accedere a questi metodi è attraverso la riflessione. È possibile ottenere un handle su un PackageManageroggetto chiamando getApplicationContext().getPackageManager()e usando l'accesso a reflection questi metodi. Dai un'occhiata a questo tutorial.


Funziona alla grande con 2.2, ma non ho avuto fortuna ad usarlo con 2.3
Someone Somewhere

3
La riflessione non è stabile in tutte le versioni di api
HandlerExploit

3

Secondo il codice sorgente Froyo, la chiave aggiuntiva Intent.EXTRA_INSTALLER_PACKAGE_NAME viene richiesta per il nome del pacchetto di installazione in PackageInstallerActivity.


1
Osservando questo impegno penso che dovrebbe funzionare
sergio91pt,

2

Su un dispositivo con root, puoi usare:

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() è definito qui.


Esiste un modo per installare un'applicazione pre-scaricata anche su sdcard? O mi puoi suggerire in qualche pagina per verificare quali comandi possiamo usare sulla shell sulla piattaforma Android?
yahya,

1
@yahya developer.android.com/tools/help/shell.html trovato dalla frase "pm android", pm = gestore pacchetti
18446744073709551615


Molte grazie! Questi link sono delle guide davvero interessanti per cominciare :)
yahya,

@ V.Kalyuzhnyu Un tempo funzionava nel 2015. IIRC era un Samsung Galaxy, forse S5.
18446744073709551615

2

Se si passa il nome del pacchetto come parametro a una qualsiasi delle funzioni definite dall'utente, utilizzare il codice seguente:

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

0

Se stai usando Kotlin, API 14+ e desideri solo mostrare la finestra di disinstallazione per la tua app:

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

È possibile passare packageNamea qualsiasi altro nome di pacchetto se si desidera richiedere all'utente di disinstallare un'altra app sul dispositivo


0

Prerequisiti:

L'APK deve essere firmato dal sistema come indicato correttamente in precedenza. Un modo per ottenerlo è costruire tu stesso l'immagine AOSP e aggiungere il codice sorgente nella build.

Codice:

Una volta installato come app di sistema, è possibile utilizzare i metodi del gestore pacchetti per installare e disinstallare un APK come segue:

Installare:

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

Disinstallare:

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

Per avere una richiamata dopo aver installato / disinstallato l'APK, puoi usare questo:

/**
 * 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);
}
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.