Installieren Sie die Anwendung programmgesteuert unter Android


211

Ich bin daran interessiert zu wissen, ob es möglich ist, eine dynamisch heruntergeladene apk programmgesteuert von einer benutzerdefinierten Android-Anwendung zu installieren.


Ich weiß nicht, was "dynamischer Loader, abhängig von der aktuellen Benutzerumgebung" bedeutet. Die Antwort von @Lie Ryan zeigt, wie Sie eine APK installieren können, die mit den von Ihnen gewählten Mitteln heruntergeladen wurde.
CommonsWare

Antworten:


240

Sie können ganz einfach einen Play Store-Link oder eine Installationsaufforderung starten:

Intent promptInstall = new Intent(Intent.ACTION_VIEW)
    .setDataAndType(Uri.parse("content:///path/to/your.apk"), 
                    "application/vnd.android.package-archive");
startActivity(promptInstall); 

oder

Intent goToMarket = new Intent(Intent.ACTION_VIEW)
    .setData(Uri.parse("https://play.google.com/store/apps/details?id=com.package.name"));
startActivity(goToMarket);

Sie können .apks jedoch nicht ohne die ausdrückliche Erlaubnis des Benutzers installieren . Nicht, wenn das Gerät und Ihr Programm nicht gerootet sind.


35
Gute Antwort, aber kein Hardcode /sdcard, da dies auf Android 2.2+ und anderen Geräten falsch ist. Verwenden Sie Environment.getExternalStorageDirectory()stattdessen.
CommonsWare

3
Das Verzeichnis / asset / ist nur auf Ihrem Entwicklungscomputer vorhanden. Wenn die Anwendung zu einer APK kompiliert wird, ist das Verzeichnis / asset / nicht mehr vorhanden, da alle Assets in der APK komprimiert sind. Wenn Sie aus Ihrem Verzeichnis / asset / installieren möchten, müssen Sie dies zuerst in einen anderen Ordner extrahieren.
Lie Ryan

2
@LieRyan. Schön, Ihre Antwort zu sehen. Ich habe das gerootete Gerät mit benutzerdefiniertem ROM. Ich möchte Themen dynamisch installieren, ohne den Benutzer zu bitten, die Installationsschaltfläche zu drücken. Kann ich das machen.
Sharanabasu Angadi

1
Dies scheint nicht mehr zu funktionieren, wenn targetSdk 25 ist. Es gibt eine Ausnahme: "android.os.FileUriExposedException: ... apk über Intent.getData () ... hinaus außerhalb der App verfügbar gemacht." Woher?
Android-Entwickler

4
@ android Entwickler: Ich kann dies derzeit nicht testen, aber auf targetSdkVersion> = 24 gilt Folgendes: stackoverflow.com/questions/38200282/… Sie müssen also FileProvider verwenden.
Lie Ryan

56
File file = new File(dir, "App.apk");
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setDataAndType(Uri.fromFile(file), "application/vnd.android.package-archive");
startActivity(intent);

Ich hatte das gleiche Problem und nach mehreren Versuchen hat es für mich so geklappt. Ich weiß nicht warum, aber das separate Einstellen von Daten und Typ hat meine Absicht durcheinander gebracht.


Ich kann diese Antwort nicht genug bewerten. Aus irgendeinem Grund führt das separate Festlegen der Absichtsdaten und des MIME-Typs zu ActivityNotFoundException in API-Ebene 17.
Brent M. Spell

Ich habe das auch gerade hochgestimmt. Diese Form war die einzige, die ich zur Arbeit bringen konnte. Noch Jahre später ... was ist das für ein verdammter Käfer? Stunden verschwendet. Ich benutze übrigens Eclipse (Helios).
Tam

10
@ BrentM.Spell und andere: Wenn Sie in die Dokumentation schauen, werden Sie feststellen, dass jedes Mal, wenn Sie nur Daten ODER-Typ festlegen, der andere automatisch ungültig wird, z. B.: setData()Würde dazu führen, dass der Typparameter entfernt wird. Sie MÜSSEN verwenden, setDataAndType()wenn Sie Werte für beide angeben möchten. Hier: developer.android.com/reference/android/content/…
Bogdan Alexandru

41

Die Lösungen für diese Frage gelten alle für targetSdkVersions von 23 und darunter. Für Android N, dh API-Level 24 und höher, funktionieren sie jedoch nicht und stürzen mit der folgenden Ausnahme ab:

android.os.FileUriExposedException: file:///storage/emulated/0/... exposed beyond app through Intent.getData()

Dies liegt daran, dass sich ab Android 24 die UriAdressierung der heruntergeladenen Datei geändert hat. Zum Beispiel wurde eine Installationsdatei appName.apkauf dem primären externe Dateisystem des App mit Paketnamen gespeichert com.example.testwären als

file:///storage/emulated/0/Android/data/com.example.test/files/appName.apk

für API 23und unter, während so etwas wie

content://com.example.test.authorityStr/pathName/Android/data/com.example.test/files/appName.apk

für API 24und darüber.

Weitere Details dazu finden Sie hier und ich werde es nicht durchgehen.

Zur Beantwortung der Frage nach targetSdkVersiondem 24und oben, muss man folgendermaßen vor: Fügen Sie folgende zu dem AndroidManifest.xml:

<application
        android:allowBackup="true"
        android:label="@string/app_name">
        <provider
            android:name="android.support.v4.content.FileProvider"
            android:authorities="${applicationId}.authorityStr"
            android:exported="false"
            android:grantUriPermissions="true">
            <meta-data
                android:name="android.support.FILE_PROVIDER_PATHS"
                android:resource="@xml/paths"/>
        </provider>
</application>

2. Fügen Sie dem Ordner in src, main, die folgende paths.xmlDatei hinzu :xmlres

<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
    <external-path
        name="pathName"
        path="pathValue"/>
</paths>

Das pathNameist , dass oben in dem beispielhaften Inhalt uri Beispiel gezeigt und pathValueist der tatsächliche Pfad auf dem System. Es wäre eine gute Idee, ein "." (ohne Anführungszeichen) für pathValue oben, wenn Sie kein zusätzliches Unterverzeichnis hinzufügen möchten.

  1. Schreiben Sie den folgenden Code, um die apk mit dem Namen appName.apkauf dem primären externen Dateisystem zu installieren :

    File directory = context.getExternalFilesDir(null);
    File file = new File(directory, fileName);
    Uri fileUri = Uri.fromFile(file);
    if (Build.VERSION.SDK_INT >= 24) {
        fileUri = FileProvider.getUriForFile(context, context.getPackageName(),
                file);
    }
    Intent intent = new Intent(Intent.ACTION_VIEW, fileUri);
    intent.putExtra(Intent.EXTRA_NOT_UNKNOWN_SOURCE, true);
    intent.setDataAndType(fileUri, "application/vnd.android" + ".package-archive");
    intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK | Intent.FLAG_ACTIVITY_NEW_TASK);
    intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
    context.startActivity(intent);
    activity.finish();
    

Es ist auch keine Berechtigung erforderlich, wenn Sie in das private Verzeichnis Ihrer eigenen App im externen Dateisystem schreiben.

Ich habe hier eine AutoUpdate-Bibliothek geschrieben , in der ich die oben genannten verwendet habe.


2
Ich folgte dieser Methode. Es heißt jedoch, dass die Datei beschädigt ist, wenn ich auf die Schaltfläche "Installieren" drücke. Ich kann dieselbe Datei installieren, indem ich sie über Bluetooth übertrage. warum so?

HI Wenn Sie den Fehler "Datei beschädigt" erhalten, welche Art von Transport haben Sie Ihrer App gegeben, um die APK zu bringen. Wenn Sie vom Server herunterladen, überprüfen Sie, ob die Streams in der Kopierdatei vom Server gelöscht werden sollen. Da die Installation der APK, die über Bluetooth übertragen wurde, funktioniert, denke ich, ist dies das Problem.
Sridhar S

2
java.lang.NullPointerException: Versuch virtuelle Methode 'android.content.res.XmlResourceParser android.content.pm.ProviderInfo.loadXmlMetaData (android.content.pm.PackageManager, java.lang.String)' auf einem Null - Objekt Bezug aufzurufen
Makvin

Ihre Bibliothek könnte endlich eine einfache Readme-
Datei

2
Vielen Dank, nach einer Woche des Kampfes mit dieser Antwort habe ich mein Problem gelöst! @SridharS, weiß ich es vor langer Zeit gewesen ist , wie ich sehe, aber wenn Sie Interesse an der Linie 5, sollten Sie hinzufügen , .authorityStrnach dem context.getPackageName()dann sollte es funktionieren.
sehrob

30

Nun, ich habe tiefer gegraben und Quellen für die PackageInstaller-Anwendung von Android Source gefunden.

https://github.com/android/platform_packages_apps_packageinstaller

Aus dem Manifest habe ich herausgefunden, dass es eine Erlaubnis erfordert:

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

Der eigentliche Installationsvorgang erfolgt nach Bestätigung

Intent newIntent = new Intent();
newIntent.putExtra(PackageUtil.INTENT_ATTR_APPLICATION_INFO, mPkgInfo.applicationInfo);
newIntent.setData(mPackageURI);
newIntent.setClass(this, InstallAppProgress.class);
String installerPackageName = getIntent().getStringExtra(Intent.EXTRA_INSTALLER_PACKAGE_NAME);
if (installerPackageName != null) {
   newIntent.putExtra(Intent.EXTRA_INSTALLER_PACKAGE_NAME, installerPackageName);
}
startActivity(newIntent);

33
android.permission.INSTALL_PACKAGES ist nur für vom System signierte Apps verfügbar. Das würde also nicht viel helfen
Hubert Liberacki

21

Ich möchte nur die Tatsache teilen, dass meine APK-Datei in meinem App-Verzeichnis "Daten" gespeichert wurde und dass ich die Berechtigungen für die APK-Datei so ändern musste, dass sie weltweit lesbar ist, damit sie auf diese Weise installiert werden kann, andernfalls auf dem System warf "Parse error: Es gibt ein Problem beim Parsen des Pakets"; Verwenden Sie also eine Lösung von @Horaceman, die Folgendes ergibt:

File file = new File(dir, "App.apk");
file.setReadable(true, false);
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setDataAndType(Uri.fromFile(file), "application/vnd.android.package-archive");
startActivity(intent);

Ich erhalte den gleichen Parserfehler! Ich weiß nicht, wie ich es lösen soll? Ich habe file.setReadable (true, false) festgelegt, aber es funktioniert nicht für mich
Jai

15

Dies kann anderen sehr helfen!

Zuerst:

private static final String APP_DIR = Environment.getExternalStorageDirectory().getAbsolutePath() + "/MyAppFolderInStorage/";

private void install() {
    File file = new File(APP_DIR + fileName);

    if (file.exists()) {
        Intent intent = new Intent(Intent.ACTION_VIEW);
        String type = "application/vnd.android.package-archive";

        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
            Uri downloadedApk = FileProvider.getUriForFile(getContext(), "ir.greencode", file);
            intent.setDataAndType(downloadedApk, type);
            intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
        } else {
            intent.setDataAndType(Uri.fromFile(file), type);
            intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
        }

        getContext().startActivity(intent);
    } else {
        Toast.makeText(getContext(), "ّFile not found!", Toast.LENGTH_SHORT).show();
    }
}

Zweitens: Für Android 7 und höher sollten Sie einen Anbieter im Manifest wie unten definieren!

    <provider
        android:name="android.support.v4.content.FileProvider"
        android:authorities="ir.greencode"
        android:exported="false"
        android:grantUriPermissions="true">
        <meta-data
            android:name="android.support.FILE_PROVIDER_PATHS"
            android:resource="@xml/paths" />
    </provider>

Drittens: Definieren Sie path.xml im Ordner res / xml wie unten! Ich verwende diesen Pfad für den internen Speicher, wenn Sie ihn in etwas anderes ändern möchten. Es gibt einige Möglichkeiten! Sie können zu diesem Link gehen: FileProvider

<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
    <external-path name="your_folder_name" path="MyAppFolderInStorage/"/>
</paths>

Viertens: Sie sollten diese Berechtigung im Manifest hinzufügen:

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

Ermöglicht einer Anwendung, die Installation von Paketen anzufordern. Apps, die auf APIs mit mehr als 25 abzielen, müssen über diese Berechtigung verfügen, um Intent.ACTION_INSTALL_PACKAGE verwenden zu können.

Bitte stellen Sie sicher, dass die Anbieterbehörden gleich sind!



3
Danke, der vierte Schritt war das, was in allen fehlte.
Amin Keshavarzian

1
In der Tat ist der 4. Punkt absolut notwendig. Alle anderen Antworten haben es komplett verpasst.
Whiteagle

Ist android.permission.REQUEST_INSTALL_PACKAGES nicht nur für System-Apps oder Apps verfügbar, die mit dem System-Keystore oder was auch immer signiert sind?
pavi2410

@Pavitra Ermöglicht einer Anwendung, die Installation von Paketen anzufordern. Apps, die auf APIs mit mehr als 25 abzielen, müssen über diese Berechtigung verfügen, um Intent.ACTION_INSTALL_PACKAGE verwenden zu können. Schutzstufe: Unterschrift
Hadi Note

Dieser Code verwendet nicht Intent.ACTION_INSTALL_PACKAGE. Warum also diese Erlaubnis?
Alexander Dyagilev

5

Eine weitere Lösung, bei der die empfangende App nicht fest codiert werden muss und die daher sicherer ist:

Intent intent = new Intent(Intent.ACTION_INSTALL_PACKAGE);
intent.setData( Uri.fromFile(new File(pathToApk)) );
startActivity(intent);

4

Ja es ist möglich. Dafür benötigen Sie jedoch das Telefon, um nicht überprüfte Quellen zu installieren. Zum Beispiel macht slideMe das. Ich denke, das Beste, was Sie tun können, ist zu überprüfen, ob die Anwendung vorhanden ist, und eine Absicht für den Android Market zu senden. Sie sollten etwas das URL-Schema für Android Market verwenden.

market://details?id=package.name

Ich weiß nicht genau, wie ich die Aktivität starten soll, aber wenn Sie eine Aktivität mit dieser Art von URL starten. Es sollte den Android-Markt öffnen und Ihnen die Wahl geben, die Apps zu installieren.


Wie ich sehe, ist diese Lösung der Wahrheit am nächsten :). Aber es ist nicht passend für meinen Fall. Ich brauche einen dynamischen Lader, der von der aktuellen Benutzerumgebung abhängt und auf den Markt kommt - keine gute Lösung. Aber trotzdem danke.
Alkersan

4

Es ist erwähnenswert, dass Sie den DownloadManagerDownload an einem externen Ort speichern müssen, z setDestinationInExternalFilesDir(c, null, "<your name here>).apk";. Die Absicht mit einem Paketarchivtyp scheint das content:Schema, das beim Herunterladen an einen internen Speicherort verwendet wird, nicht zu mögen, gefällt aber file:. (Der Versuch, den internen Pfad in ein Dateiobjekt zu verpacken und dann den Pfad file:abzurufen, funktioniert ebenfalls nicht, obwohl dies zu einer URL führt, da die App die apk nicht analysiert. Sieht so aus, als müsste sie extern sein.)

Beispiel:

int uriIndex = cursor.getColumnIndex(DownloadManager.COLUMN_LOCAL_URI);
String downloadedPackageUriString = cursor.getString(uriIndex);
File mFile = new File(Uri.parse(downloadedPackageUriString).getPath());
Intent promptInstall = new Intent(Intent.ACTION_VIEW)
        .setDataAndType(Uri.fromFile(mFile), "application/vnd.android.package-archive")
        .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
appContext.startActivity(promptInstall);

3

Vergessen Sie nicht, Berechtigungen anzufordern:

android.Manifest.permission.WRITE_EXTERNAL_STORAGE 
android.Manifest.permission.READ_EXTERNAL_STORAGE

Fügen Sie in AndroidManifest.xml den Anbieter und die Berechtigung hinzu:

<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES"/>
...
<application>
    ...
    <provider
        android:name="android.support.v4.content.FileProvider"
        android:authorities="${applicationId}"
        android:exported="false"
        android:grantUriPermissions="true">
        <meta-data
            android:name="android.support.FILE_PROVIDER_PATHS"
            android:resource="@xml/provider_paths"/>
    </provider>
</application>

Erstellen Sie den XML-Dateianbieter res / xml / provider_paths.xml

<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
    <external-path
        name="external"
        path="." />
    <external-files-path
        name="external_files"
        path="." />
    <cache-path
        name="cache"
        path="." />
    <external-cache-path
        name="external_cache"
        path="." />
    <files-path
        name="files"
        path="." />
</paths>

Verwenden Sie den folgenden Beispielcode:

   public class InstallManagerApk extends AppCompatActivity {

    static final String NAME_APK_FILE = "some.apk";
    public static final int REQUEST_INSTALL = 0;

     @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        // required permission:
        // android.Manifest.permission.WRITE_EXTERNAL_STORAGE 
        // android.Manifest.permission.READ_EXTERNAL_STORAGE

        installApk();

    }

    ...

    /**
     * Install APK File
     */
    private void installApk() {

        try {

            File filePath = Environment.getExternalStorageDirectory();// path to file apk
            File file = new File(filePath, LoadManagerApkFile.NAME_APK_FILE);

            Uri uri = getApkUri( file.getPath() ); // get Uri for  each SDK Android

            Intent intent = new Intent(Intent.ACTION_INSTALL_PACKAGE);
            intent.setData( uri );
            intent.setFlags( Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_ACTIVITY_NEW_TASK );
            intent.putExtra(Intent.EXTRA_NOT_UNKNOWN_SOURCE, true);
            intent.putExtra(Intent.EXTRA_RETURN_RESULT, true);
            intent.putExtra(Intent.EXTRA_INSTALLER_PACKAGE_NAME, getApplicationInfo().packageName);

            if ( getPackageManager().queryIntentActivities(intent, 0 ) != null ) {// checked on start Activity

                startActivityForResult(intent, REQUEST_INSTALL);

            } else {
                throw new Exception("don`t start Activity.");
            }

        } catch ( Exception e ) {

            Log.i(TAG + ":InstallApk", "Failed installl APK file", e);
            Toast.makeText(getApplicationContext(), e.getMessage(), Toast.LENGTH_LONG)
                .show();

        }

    }

    /**
     * Returns a Uri pointing to the APK to install.
     */
    private Uri getApkUri(String path) {

        // Before N, a MODE_WORLD_READABLE file could be passed via the ACTION_INSTALL_PACKAGE
        // Intent. Since N, MODE_WORLD_READABLE files are forbidden, and a FileProvider is
        // recommended.
        boolean useFileProvider = Build.VERSION.SDK_INT >= Build.VERSION_CODES.N;

        String tempFilename = "tmp.apk";
        byte[] buffer = new byte[16384];
        int fileMode = useFileProvider ? Context.MODE_PRIVATE : Context.MODE_WORLD_READABLE;
        try (InputStream is = new FileInputStream(new File(path));
             FileOutputStream fout = openFileOutput(tempFilename, fileMode)) {

            int n;
            while ((n = is.read(buffer)) >= 0) {
                fout.write(buffer, 0, n);
            }

        } catch (IOException e) {
            Log.i(TAG + ":getApkUri", "Failed to write temporary APK file", e);
        }

        if (useFileProvider) {

            File toInstall = new File(this.getFilesDir(), tempFilename);
            return FileProvider.getUriForFile(this,  BuildConfig.APPLICATION_ID, toInstall);

        } else {

            return Uri.fromFile(getFileStreamPath(tempFilename));

        }

    }

    /**
     * Listener event on installation APK file
     */
    @Override
    protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
        super.onActivityResult(requestCode, resultCode, data);

        if(requestCode == REQUEST_INSTALL) {

            if (resultCode == Activity.RESULT_OK) {
                Toast.makeText(this,"Install succeeded!", Toast.LENGTH_SHORT).show();
            } else if (resultCode == Activity.RESULT_CANCELED) {
                Toast.makeText(this,"Install canceled!", Toast.LENGTH_SHORT).show();
            } else {
                Toast.makeText(this,"Install Failed!", Toast.LENGTH_SHORT).show();
            }

        }

    }

    ...

}


2

Versuche dies

String filePath = cursor.getString(cursor.getColumnIndex(DownloadManager.COLUMN_LOCAL_FILENAME));
String title = filePath.substring( filePath.lastIndexOf('/')+1, filePath.length() );
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setDataAndType(Uri.fromFile(new File(filePath)), "application/vnd.android.package-archive");
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); // without this flag android returned a intent error!
MainActivity.this.startActivity(intent);

1

Fügen Sie zunächst die folgende Zeile zu AndroidManifest.xml hinzu:

<uses-permission android:name="android.permission.INSTALL_PACKAGES"
    tools:ignore="ProtectedPermissions" />

Verwenden Sie dann den folgenden Code, um apk zu installieren:

File sdCard = Environment.getExternalStorageDirectory();
            String fileStr = sdCard.getAbsolutePath() + "/MyApp";// + "app-release.apk";
            File file = new File(fileStr, "TaghvimShamsi.apk");
            Intent promptInstall = new Intent(Intent.ACTION_VIEW).setDataAndType(Uri.fromFile(file),
                    "application/vnd.android.package-archive");
            startActivity(promptInstall);

0

UpdateNode bietet eine API für Android, mit der APK-Pakete in einer anderen App installiert werden können.

Sie können Ihr Update einfach online definieren und die API in Ihre App integrieren - fertig.
Derzeit befindet sich die API im Beta-Status, Sie können jedoch bereits einige Tests selbst durchführen.

Darüber hinaus bietet UpdateNode auch die Anzeige von Nachrichten über das System an - sehr nützlich, wenn Sie Ihren Benutzern etwas Wichtiges mitteilen möchten.

Ich bin Teil des Client-Entwicklerteams und verwende mindestens die Nachrichtenfunktionalität für meine eigene Android-App.

Hier finden Sie eine Beschreibung zur Integration der API


Es gibt einige Probleme mit der Website. Ich kann den api_key nicht bekommen und die Registrierung nicht fortsetzen.
infinite_loop_

Hallo @infinite_loop_, Sie können den API-Schlüssel in Ihrem Benutzerkonto finden: updatenode.com/profile/view_keys
sarahara

0

Versuchen Sie dies - Schreiben Sie auf Manifest:

uses-permission android:name="android.permission.INSTALL_PACKAGES"
        tools:ignore="ProtectedPermissions"

Schreiben Sie den Code:

File sdCard = Environment.getExternalStorageDirectory();
String fileStr = sdCard.getAbsolutePath() + "/Download";// + "app-release.apk";
File file = new File(fileStr, "app-release.apk");
Intent promptInstall = new Intent(Intent.ACTION_VIEW).setDataAndType(Uri.fromFile(file),
                        "application/vnd.android.package-archive");

startActivity(promptInstall);

1
Sie benötigen diese Nur-System-Berechtigung nicht, um die
Paketinstallationsaktivität
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.