90 votes

Android installer apk avec Intent.VIEW_ACTION ne fonctionne pas avec le fournisseur de fichiers

Mon application dispose d'une fonctionnalité de mise à jour automatique qui télécharge un APK et lorsque le téléchargement est terminé, un Intent.VIEW_ACTION est lancé pour ouvrir l'application et permettre à l'utilisateur d'installer l'APK téléchargé

Uri uri = Uri.parse("file://" + destination);
Intent install = new Intent(Intent.ACTION_VIEW);
install.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
install.setDataAndType(uri,
    manager.getMimeTypeForDownloadedFile(downloadId));
activity.startActivity(install);

Cela fonctionne très bien pour tous les appareils < 24

Maintenant, avec Android 24, il semble que nous ne sommes plus autorisés à démarrer des intents avec file:/// et après quelques recherches, il a été conseillé d'utiliser un Fournisseur de fichiers (File Provider)

nouveau code:

Intent install = new Intent(Intent.ACTION_VIEW);
install.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
install.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
Uri apkUri = FileProvider.getUriForFile(AutoUpdate.this,
    BuildConfig.APPLICATION_ID + ".provider", file);
install.setDataAndType(apkUri,
    manager.getMimeTypeForDownloadedFile(downloadId));
activity.startActivity(install);

Maintenant, activity.startActivity(install); lance une erreur

Aucune activité trouvée pour gérer l'Intent { act=android.intent.action.VIEW dat=content://com.xxxx.xx.provider/MyFolder/Download/MyApkFile.apk typ=application/vnd.android.package-archive flg=0x4000000 }

Y a-t-il un moyen d'ouvrir le visualiseur d'APK sur Android 7 (24) ?

3 votes

Utilisez ACTION_INSTALL_PACKAGE. Cela a fonctionné pour moi à partir de l'édition de juin de la prévisualisation pour les développeurs N.

2 votes

Aucune activité n'a été trouvée pour gérer l'intention { act=android.intent.action.INSTALL_PACKAGE ...

1 votes

Je n'ai aucun problème avec ACTION_INSTALL_PACKAGE, utilisant un FileProvider pour servir le APK, lorsqu'il a été testé sur un Nexus 9 fonctionnant sous Android 7.0 (build NRD90M). Comparé à votre Intent, en plus de la différence dans la chaîne d'action, je n'utilise pas FLAG_ACTIVITY_CLEAR_TOP et j'utilise setData() plutôt que setDataAndType().

186voto

just_user Points 1446

Après de nombreux essais, j'ai réussi à résoudre ce problème en créant des Intent différents pour tout ce qui est inférieur à Nougat, car l'utilisation de FileProvider pour créer un intent d'installation avec des versions d'Android antérieures à Nougat provoque l'erreur :

ActivityNotFoundException: No Activity found to handle Intent { act=android.intent.action.INSTALL_PACKAGE dat=content://XXX.apk flg=0x1 }

Alors qu'utiliser une Uri normale sur Android Nougat crée l'erreur suivante :

FileUriExposedException: file:///XXX.apk exposed beyond app through Intent.getData()

Ma solution qui fonctionne pour moi avec Android N sur l'émulateur et un téléphone sous Android M.

Fichier à installer = nouveau Fichier(dossierApp, nomApp + ".apk");
Intent intent;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
    Uri apkUri = FileProvider.getUriForFile(activity, BuildConfig.APPLICATION_ID + ".fileprovider", toInstall);
    intent = new Intent(Intent.ACTION_INSTALL_PACKAGE);
    intent.setData(apkUri);
    intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
} else {
    Uri apkUri = Uri.fromFile(toInstall);
    intent = new Intent(Intent.ACTION_VIEW);
    intent.setDataAndType(apkUri, "application/vnd.android.package-archive");
    intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
}
activity.startActivity(intent);

MAJ pour Android Nougat 7.1 :

Vous devez également ajouter l'autorisation REQUEST_INSTALL_PACKAGES dans votre manifeste. Elle est disponible à partir du niveau API 23 (Android 6.0 Marshmallow) et requise à partir du niveau 25 (Android 7.1 Nougat).

MAJ :

N'oubliez pas de demander les autorisations de lecture et d'écriture sur le stockage externe si le fichier que vous essayez d'installer est sur le stockage externe. Et aussi de configurer un FileProvider correct pour Android Nougat et versions supérieures.

Vérifiez d'abord si vous avez l'autorisation d'écriture en appelant canReadWriteExternal()ci-dessous, sinon appelez requestPermission() avant :

private static final int REQUEST_WRITE_PERMISSION = 786;

@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
    if (requestCode == REQUEST_WRITE_PERMISSION && grantResults[0] == PackageManager.PERMISSION_GRANTED)
        Toast.makeText(this, "Autorisation accordée", Toast.LENGTH_LONG).show();
}

private void requestPermission() {
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M)
        requestPermissions(new String[]{ Manifest.permission.WRITE_EXTERNAL_STORAGE}, REQUEST_WRITE_PERMISSION);
}

private boolean canReadWriteExternal() {
    return Build.VERSION.SDK_INT < Build.VERSION_CODES.M ||
            ContextCompat.checkSelfPermission(MainActivity.this, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED;
}

Voici un exemple de fournisseur de fichiers pour le dossier Téléchargement sur le stockage externe. AndroidManifest.xml:

    ...

resources/xml/filepaths.xml:

Si vous obtenez une erreur lors de l'installation du fichier .apk disant quelque chose comme "There is a problem parsing the package.", cela pourrait être que vous n'avez pas demandé l'autorisation de lecture/écriture ou que le fichier que vous essayez d'installer n'existe pas ou est corrompu.

MAJ pour Android Oreo 8.0 :

Vous devez vérifier si l'application actuelle est autorisée à installer l'APK sur Android Oreo 8.0 ou supérieur.

Vous pouvez vérifier si votre application est autorisée à installer des APK en utilisant la méthode [canRequestPackageInstalls](https://developer.android.com/reference/android/content/pm/PackageManager#canRequestPackageInstalls()) de la classe PackageManager. S'il renvoie false, alors vous pouvez lancer l'intent avec l'action ACTION_MANAGE_UNKNOWN_APP_SOURCES pour ouvrir la boîte de dialogue des paramètres où l'utilisateur peut autoriser l'application à installer des APK.

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O 
        && !getPackageManager().canRequestPackageInstalls()) {
    Intent unknownAppSourceIntent = new Intent()
            .setAction(Settings.ACTION_MANAGE_UNKNOWN_APP_SOURCES)
            .setData(Uri.parse(String.format("package:%s", getPackageName())));

    unknownAppSourceDialog.launch(unknownAppSourceIntent);
} else {
    // L'application possède déjà l'autorisation d'installation, donc lancer l'installation de l'APK.
    startActivity(intent);
}

Assurez-vous d'ajouter le code suivant à votre activité pour recevoir le résultat de l'intent.

ActivityResultLauncher unknownAppSourceDialog = registerForActivityResult(
    new ActivityResultContracts.StartActivityForResult(),
    result -> {
        if (result.getResultCode() == Activity.RESULT_OK) {
            // L'utilisateur a autorisé l'application à installer des APK, 
            // nous pouvons donc maintenant lancer l'installation de l'APK.
            startActivity(intent);
        }
    });

0 votes

Pouvez-vous poster d'où vous obtenez toInstall ?

0 votes

@emaillenin Je l'ai ajouté. C'est simplement un fichier pointant vers l'APK que vous souhaitez installer.

3 votes

Mon problème était que j'appelais setFlags deux fois, ce qui réinitialisait essentiellement le premier indicateur. J'ai dû utiliser addFlag à la place.

17voto

Hossein Karami Points 31

J'ai eu ce problème en appelant start activity. Après avoir mis en pause mon activité en cours, elle est soudainement revenue et a appelé onResume, comme si de rien n'était. Mon problème était avec cette permission dans le manifeste:

presque personne n'en a parlé. Alors souvenez-vous-en. Dans SDK >= 24, vous devez utiliser un fournisseur car il a besoin d'un intent commençant par file:/// pour les versions inférieures à 24, vous devez donner une uri commençant par content:/// c'est pourquoi nous avons besoin de FileProvider pour SDK 24 et supérieur. Je ne pense pas avoir besoin d'écrire du code pour ça, car @just_user a écrit la bonne réponse. https://stacklearn.ir

0 votes

Je crois que votre commentaire est inversé. Pour les SDK >= 24, l'URI doit commencer par 'content:///'. En dessous du SDK 24, il doit commencer par 'file:///'.

6voto

Rob Francis Points 41

Cela pourrait être le problème que vous avez

intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); 

dans votre exemple, cela devrait être

install.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);

car install est le nom de l'intent.

0 votes

Est-ce que quelque chose a fonctionné pour vous? Je ne peux pas partager mon uri de fichier avec l'application de messagerie électronique.

2 votes

J'ai basculé de l'utilisation de file:/// vers content:// et j'avais du mal à ouvrir les fichiers du fournisseur de fichiers depuis un certain temps. Cela a résolu mon problème.

2voto

EBLiS Points 120

Vous devez noter que pour les API < 24, vous devez utiliser :

        setDataAndType(Uri.fromFile(apkFile), "application/vnd.android.package-archive")

au lieu de définir les données et le type séparément :

data = Uri.fromFile(apkFile)
    type = "application/vnd.android.package-archive"

sinon, vous obtiendrez une ActivityNotFoundException

1 votes

Il suffit d'utiliser toujours DataAndType et il n'y a aucun problème.

0voto

Yaegaki2 Points 11

Pour tous ceux qui rencontrent le problème "Il y avait un problème d'analyse de ce package.".

Définissez manuellement les autorisations.

ContextCompat.checkSelfPermission(getApplicationContext(), READ_EXTERNAL_STORAGE);

Voir cet exemple : Télécharger et installer une application de manière programmée

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X