programowo instaluj / odinstaluj aplikacje (Packagemanager vs Intents)

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 zainstalowanej relacji(a, b) odpowiedzialny powinien być Menedżer pakietów. W rzeczywistości, zgodnie z API jest to:

Public abstract String getInstallerPackageName (String packageName) - Pobierz pakiet nazwa aplikacji, która zainstalowała pakiet. Określa to, z którego rynku pochodzi pakiet.

Obecne podejście

Zainstaluj APK używając Intent

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

Odinstaluj APK używając intencji:

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

To oczywiście nie jest sposób, w jaki np. Android Market instaluje / odinstalowuje Pakiety. Używają bogatszej wersji Packagemanagera. 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 Intencyjnemu. Niestety nie są one dostępne dla zewnętrznych programistów. Ale być może będą w przyszłości?

The better approach

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

odinstalowanie APK za pomocą 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 intents 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 używając intentów? (może nazwę pakietu instalatora można dodać jako dodatek do intencji 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 Javy i umieszczanie ich w folderach zgodnie z hierarchią pakietów możesz sprawdzić ten zgrabny skrypt: zobacz kod źródłowy Androida w Eclipse .

Author: Håvard Geithus, 2011-07-25

10 answers

Obecnie nie jest to dostępne dla aplikacji stron trzecich. Zauważ, że nawet użycie reflection lub innych sztuczek w celu uzyskania dostępu do installPackage() nie pomoże, ponieważ tylko aplikacje systemowe mogą z niej korzystać. (Dzieje się tak, ponieważ jest to mechanizm instalacji niskiego poziomu, po zatwierdzeniu uprawnień przez użytkownika, więc dostęp do zwykłych aplikacji nie jest bezpieczny.)

Również argumenty funkcji installPackage() często zmieniały się między wydaniami platformy, więc wszystko, co robisz próba dostępu nie powiedzie się w różnych innych wersjach platformy.

EDIT:

Warto również zaznaczyć, że ten instalator został dodany dopiero dość niedawno do platformy (2.2?) i pierwotnie nie był faktycznie używany do śledzenia, kto zainstalował aplikację - jest używany przez platformę do określania, kto uruchomić podczas zgłaszania błędów w aplikacji, do wdrażania Android Feedback. (Był to również jeden z przypadków zmiany argumentów metody API.) Przez co najmniej długi podczas gdy 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 użył jej do Ustawienia aplikacji sprzężenia zwrotnego na Androida (która była oddzielona od Rynku) jako "właściciela", aby zająć się sprzężeniem zwrotnym.

 66
Author: hackbod,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-10-09 18:04:59

[Odinstaluj]

A może:

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

Do odinstalowania. Wydaje się łatwiejsze...

 64
Author: JohnyTex,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-12-07 13:30:29

API level 14 wprowadziło dwie nowe akcje: ACTION_INSTALL_PACKAGE oraz ACTION_UNINSTALL_PACKAGE . Te działania pozwalają przekazać EXTRA_RETURN_RESULT boolean extra, aby uzyskać (nie)powiadomienie o wyniku instalacji.

Przykładowy kod do wywołania okna odinstalowywania:

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 otrzymaj powiadomienie w swojej Activity#onActivityResult metodzie:

@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");
        }
    }
}
 36
Author: Pir Fahim Shah,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-05-13 07:14:44

Jeśli masz uprawnienia właściciela urządzenia (lub właściciela profilu, nie próbowałem), możesz bezgłośnie 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;
}
 15
Author: Ohad Cohen,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-07-18 12:03:36

Dostęp do tych metod można uzyskać tylko poprzez refleksję. Możesz uzyskać obsługę PackageManager obiektu przez wywołanie getApplicationContext().getPackageManager() i użycie reflection access tych metod. Checkout this tutorial.

 4
Author: HandlerExploit,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-06-10 13:19:09

Zgodnie z kodem źródłowym Froyo, Intencja.EXTRA_INSTALLER_PACKAGE_NAME dodatkowy klucz jest pytany o nazwę pakietu instalatora w pakiecie Installeractivity.

 3
Author: njzk2,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-01-02 16:16:01

Na zakorzenionym 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 zdefiniowany tutaj.

 2
Author: 18446744073709551615,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 12:18:21

Jeśli przekazujesz nazwę pakietu jako parametr do którejś z funkcji zdefiniowanych przez użytkownika, użyj poniższego kodu:

    Intent intent=new Intent(Intent.ACTION_DELETE);
    intent.setData(Uri.parse("package:"+packageName));
    startActivity(intent);
 1
Author: Rashwin S M,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-03-09 16:39:08

Jeśli używasz Kotlin, API 14+ i chcesz po prostu pokazać okno Odinstaluj dla swojej aplikacji:

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

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

 0
Author: Louis CAD,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-10-18 19:56:01

Warunek:

Twój APK musi być podpisany przez system, jak poprawnie wskazano wcześniej. Jednym ze sposobów, aby to osiągnąć, 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ć APK w następujący sposób:

Zainstaluj:

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 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);
}
 0
Author: phoebus,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-06-13 16:50:18