L'application se bloque lorsque j'essaie d'ouvrir un fichier. Il fonctionne sous Android Nougat, mais sur Android Nougat, il se bloque. Il se bloque uniquement lorsque j'essaie d'ouvrir un fichier depuis la carte SD, et non depuis la partition système. Un problème de permission?
Exemple de code:
File file = new File("/storage/emulated/0/test.txt");
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setDataAndType(Uri.fromFile(file), "text/*");
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(intent); // Crashes on this line
Bûche:
Android.os.FileUriExposedException: fichier: ///storage/emulated/0/test.txt exposé au-delà de l'application via Intent.getData ()
Edit:
Lorsque vous ciblez Android Nougat, file://
, les URI ne sont plus autorisés. Nous devrions plutôt utiliser content://
URI. Cependant, mon application doit ouvrir des fichiers dans les répertoires racine. Des idées?
Si votre targetSdkVersion >= 24
, nous devons utiliser la classe FileProvider
pour donner accès à un fichier ou à un dossier particulier afin de les rendre accessibles à d'autres applications. Nous créons notre propre classe héritant de FileProvider
afin de nous assurer que notre fournisseur de fichiers FileProvider n'entre pas en conflit avec les fournisseurs de fichiers FileProviders déclarés dans les dépendances importées comme décrit ici .
Étapes pour remplacer file://
URI par content://
URI:
Ajouter une classe d'extension FileProvider
public class GenericFileProvider extends FileProvider {}
Ajoutez une balise FileProvider <provider>
dans la balise AndroidManifest.xml
sous la balise <application>
. Spécifiez une autorité unique pour l'attribut Android:authorities
afin d'éviter les conflits. Les dépendances importées peuvent spécifier ${applicationId}.provider
et d'autres autorités couramment utilisées.
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:Android="http://schemas.Android.com/apk/res/Android"
...
<application
...
<provider
Android:name=".GenericFileProvider"
Android:authorities="${applicationId}.fileprovider"
Android:exported="false"
Android:grantUriPermissions="true">
<meta-data
Android:name="Android.support.FILE_PROVIDER_PATHS"
Android:resource="@xml/provider_paths"/>
</provider>
</application>
</manifest>
provider_paths.xml
dans le dossier res/xml
. Un dossier peut être nécessaire pour créer s'il n'existe pas. Le contenu du fichier est présenté ci-dessous. Il indique que nous souhaitons partager l'accès au stockage externe dans le dossier racine (path=".")
avec le nom external_files .<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:Android="http://schemas.Android.com/apk/res/Android">
<external-path name="external_files" path="."/>
</paths>
La dernière étape consiste à modifier la ligne de code ci-dessous dans
Uri photoURI = Uri.fromFile(createImageFile());
à
Uri photoURI = FileProvider.getUriForFile(context, context.getApplicationContext().getPackageName() + ".my.package.name.provider", createImageFile());
Edit: Si vous utilisez l'intention de faire en sorte que le système ouvre votre fichier, vous devrez peut-être ajouter la ligne de code suivante:
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
S'il vous plaît se référer, le code complet et la solution a été expliquée ici.
Outre la solution utilisant la variable FileProvider
, il existe un autre moyen de contourner ce problème. Tout simplement
StrictMode.VmPolicy.Builder builder = new StrictMode.VmPolicy.Builder();
StrictMode.setVmPolicy(builder.build());
dans Application.onCreate()
. De cette manière, la VM ignore le fichier URI
exposition.
Méthode
builder.detectFileUriExposure()
active la vérification de l'exposition du fichier, qui est également le comportement par défaut si nous ne configurons pas VmPolicy.
J'ai rencontré un problème qui signifie que si j'utilise un content://
URI
pour envoyer quelque chose, certaines applications ne peuvent tout simplement pas le comprendre. Et déclasser la version target SDK
n'est pas autorisé. Dans ce cas, ma solution est utile.
Mise à jour:
Comme mentionné dans le commentaire, StrictMode est un outil de diagnostic et n'est pas censé être utilisé pour résoudre ce problème. Lorsque j'ai posté cette réponse il y a un an, de nombreuses applications ne peuvent recevoir que des fichiers uris. Ils se bloquent juste quand j'ai essayé de leur envoyer un FileProvider URI. Ceci est corrigé dans la plupart des applications, nous devrions donc utiliser la solution FileProvider.
Si votre application cible l'API 24+ et que vous souhaitez/devez toujours utiliser file: // intents, vous pouvez utiliser la méthode hacky pour désactiver le contrôle à l'exécution:
if(Build.VERSION.SDK_INT>=24){
try{
Method m = StrictMode.class.getMethod("disableDeathOnFileUriExposure");
m.invoke(null);
}catch(Exception e){
e.printStackTrace();
}
}
La méthode StrictMode.disableDeathOnFileUriExposure
est masquée et documentée sous la forme:
/**
* Used by lame internal apps that haven't done the hard work to get
* themselves off file:// Uris yet.
*/
Le problème est que mon application n'est pas boiteuse, mais ne veut pas être handicapée par l'utilisation de content: // intentions qui ne sont pas comprises par de nombreuses applications. Par exemple, l’ouverture d’un fichier mp3 avec le schéma content: // offre beaucoup moins d’applications que lors de l’ouverture du même schéma sur fichier: //. Je ne veux pas payer pour les défauts de conception de Google en limitant les fonctionnalités de mon application.
Google souhaite que les développeurs utilisent le modèle de contenu, mais le système n'est pas préparé à cela. Pendant des années, les applications ont été conçues pour utiliser des fichiers non "contenu", les fichiers peuvent être modifiés et sauvegardés, alors que les fichiers servis sur le modèle de contenu ne peuvent pas l'être. ils?).
Si targetSdkVersion
est supérieur à 24 , alors FileProvider est utilisé pour accorder l'accès.
Créez un fichier xml (chemin: 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_files" path="."/>
</paths>
Ajouter un fournisseur dans AndroidManifest.xml
<provider
Android:name="Android.support.v4.content.FileProvider"
Android:authorities="${applicationId}.provider"
Android:exported="false"
Android:grantUriPermissions="true">
<meta-data
Android:name="Android.support.FILE_PROVIDER_PATHS"
Android:resource="@xml/provider_paths"/>
</provider>
Si vous utilisez androidx , le chemin d'accès FileProvider doit être:
Android:name="androidx.core.content.FileProvider"
et remplace
Uri uri = Uri.fromFile(fileImagePath);
à
Uri uri = FileProvider.getUriForFile(MainActivity.this, BuildConfig.APPLICATION_ID + ".provider",fileImagePath);
Edit: Pendant que vous incluez l'URI avec un Intent
assurez-vous d'ajouter la ligne ci-dessous:
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
et vous êtes prêt à partir. J'espère que ça aide.
Si votre targetSdkVersion
est égal ou supérieur à 24, vous ne pouvez pas utiliser file:
Uri
valeurs dans Intents
sur Android 7.0+ appareils =.
Vos choix sont:
Laissez tomber votre targetSdkVersion
à 23 ou moins, ou
Mettez votre contenu en mémoire interne, puis tilisez FileProvider
pour le rendre disponible de manière sélective pour les autres applications
Par exemple:
Intent i=new Intent(Intent.ACTION_VIEW, FileProvider.getUriForFile(this, AUTHORITY, f));
i.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
startActivity(i);
(de cet exemple de projet )
Vous devez d’abord ajouter un fournisseur à votre manifeste Android.
<application
...>
<activity>
....
</activity>
<provider
Android:name="Android.support.v4.content.FileProvider"
Android:authorities="com.your.package.fileProvider"
Android:grantUriPermissions="true"
Android:exported="false">
<meta-data
Android:name="Android.support.FILE_PROVIDER_PATHS"
Android:resource="@xml/file_paths" />
</provider>
</application>
créez maintenant un fichier dans le dossier des ressources xml (si vous utilisez Android studio, vous pouvez appuyer sur Alt + Entrée après avoir mis en évidence chemin_fichiers et sélectionner l'option de création d'une ressource xml)
Suivant dans le fichier chemin_fichiers entrez
<?xml version="1.0" encoding="utf-8"?>
<paths>
<external-path path="Android/data/com.your.package/" name="files_root" />
<external-path path="." name="external_storage_root" />
</paths>
Cet exemple est pour external-path, vous pouvez vous référer à ici pour plus d'options. Cela vous permettra de partager des fichiers qui se trouvent dans ce dossier et son sous-dossier.
Maintenant, tout ce qui reste à faire est de créer l'intention comme suit:
MimeTypeMap mime = MimeTypeMap.getSingleton();
String ext = newFile.getName().substring(newFile.getName().lastIndexOf(".") + 1);
String type = mime.getMimeTypeFromExtension(ext);
try {
Intent intent = new Intent();
intent.setAction(Intent.ACTION_VIEW);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
Uri contentUri = FileProvider.getUriForFile(getContext(), "com.your.package.fileProvider", newFile);
intent.setDataAndType(contentUri, type);
} else {
intent.setDataAndType(Uri.fromFile(newFile), type);
}
startActivityForResult(intent, ACTIVITY_VIEW_ATTACHMENT);
} catch (ActivityNotFoundException anfe) {
Toast.makeText(getContext(), "No activity found to open this attachment.", Toast.LENGTH_LONG).show();
}
EDIT: J'ai ajouté le dossier racine de la carte SD dans les chemins_fichiers. J'ai testé ce code et cela fonctionne.
@palash k answer est correct et fonctionne pour les fichiers de stockage interne, mais dans mon cas, je souhaite également ouvrir des fichiers à partir d'un stockage externe. Mon application s'est bloquée lors de l'ouverture d'un fichier à partir d'un stockage externe, comme sdcard et usb, mais j'ai réussi à résoudre le problème en modifiant provider_paths.xml à partir de la réponse acceptée
changer le provider_paths.xml comme ci-dessous
<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:Android="http://schemas.Android.com/apk/res/Android">
<external-path path="Android/data/${applicationId}/" name="files_root" />
<root-path
name="root"
path="/" />
</paths>
et dans la classe Java (pas de modification car la réponse acceptée est juste une petite modification)
Uri uri=FileProvider.getUriForFile(getActivity(), BuildConfig.APPLICATION_ID+".provider", File)
Cela m'aide à résoudre le problème des fichiers stockés dans des mémoires externes. J'espère que cela aidera quelqu'un qui a le même problème que le mien :)
Il suffit de coller le code ci-dessous dans l’activité onCreate ()
StrictMode.VmPolicy.Builder builder = new StrictMode.VmPolicy.Builder(); StrictMode.setVmPolicy(builder.build());
Il ignorera l'exposition à l'URI
Ma solution consistait à "Uri.parse" le chemin du fichier sous forme de chaîne, au lieu d'utiliser Uri.fromFile ().
String storage = Environment.getExternalStorageDirectory().toString() + "/test.txt";
File file = new File(storage);
Uri uri;
if (Build.VERSION.SDK_INT < 24) {
uri = Uri.fromFile(file);
} else {
uri = Uri.parse(file.getPath()); // My work-around for new SDKs, causes ActivityNotFoundException in API 10.
}
Intent viewFile = new Intent(Intent.ACTION_VIEW);
viewFile.setDataAndType(uri, "text/plain");
startActivity(viewFile);
Il semblerait que fromFile () utilise un pointeur de fichier, ce qui, je suppose, pourrait ne pas être sûr lorsque les adresses de mémoire sont exposées à toutes les applications. Mais un chemin de fichier String ne fait de mal à personne, il fonctionne donc sans exception FileUriExposedException.
Testé aux niveaux 9 à 27 de l'API! Ouvre avec succès le fichier texte pour le modifier dans une autre application. Ne nécessite pas FileProvider, ni la bibliothèque de support Android.
Utiliser FileProvider est la voie à suivre. Mais vous pouvez utiliser cette solution de contournement simple:
WARNING: il sera corrigé dans la prochaine Android version - https://issuetracker.google.com/issues/37122890 # comment4
remplacer:
startActivity(intent);
par
startActivity(Intent.createChooser(intent, "Your title"));
Il suffit de coller le code ci-dessous dans l'activité onCreate()
.
StrictMode.VmPolicy.Builder builder = new StrictMode.VmPolicy.Builder();
StrictMode.setVmPolicy(builder.build());
Il ignorera l'exposition à l'URI.
Bonne codage :-)
J'ai utilisé la réponse de Palash donnée ci-dessus, mais elle était quelque peu incomplète, je devais donner une autorisation comme celle-ci.
Intent intent = new Intent(Intent.ACTION_VIEW);
Uri uri;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
uri = FileProvider.getUriForFile(this, getPackageName() + ".provider", new File(path));
List<ResolveInfo> resInfoList = getPackageManager().queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY);
for (ResolveInfo resolveInfo : resInfoList) {
String packageName = resolveInfo.activityInfo.packageName;
grantUriPermission(packageName, uri, Intent.FLAG_GRANT_WRITE_URI_PERMISSION | Intent.FLAG_GRANT_READ_URI_PERMISSION);
}
}else {
uri = Uri.fromFile(new File(path));
}
intent.setDataAndType(uri, "application/vnd.Android.package-archive");
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(intent);
Il suffit de coller le code ci-dessous dans l’activité onCreate ()
Constructeur StrictMode.VmPolicy.Builder = nouveau StrictMode.VmPolicy.Builder (); StrictMode.setVmPolicy (builder.build ());
Il ignorera l'exposition à l'URI
ajoutez ces deux lignes dans onCreate
StrictMode.VmPolicy.Builder builder = new StrictMode.VmPolicy.Builder();
StrictMode.setVmPolicy(builder.build());
Méthode de partage
File dir = new File(Environment.getExternalStorageDirectory(), "ColorStory");
File imgFile = new File(dir, "0.png");
Intent sendIntent = new Intent(Intent.ACTION_VIEW);
sendIntent.setType("image/*");
sendIntent.setAction(Intent.ACTION_SEND);
sendIntent.putExtra(Intent.EXTRA_STREAM, Uri.parse("file://" + imgFile));
sendIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
startActivity(Intent.createChooser(sendIntent, "Share images..."));
Pour télécharger le pdf depuis le serveur, ajoutez le code ci-dessous dans votre classe de service. J'espère que cela vous sera utile.
File file = new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS), fileName + ".pdf");
intent = new Intent(Intent.ACTION_VIEW);
//Log.e("pathOpen", file.getPath());
Uri contentUri;
contentUri = Uri.fromFile(file);
intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_NEW_TASK);
if (Build.VERSION.SDK_INT >= 24) {
Uri apkURI = FileProvider.getUriForFile(context, context.getApplicationContext().getPackageName() + ".provider", file);
intent.setDataAndType(apkURI, "application/pdf");
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
} else {
intent.setDataAndType(contentUri, "application/pdf");
}
Et oui, n'oubliez pas d'ajouter des autorisations et un fournisseur dans votre manifeste.
<uses-permission Android:name="Android.permission.INTERNET" />
<uses-permission Android:name="Android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission Android:name="Android.permission.READ_EXTERNAL_STORAGE" />
<application
<provider
Android:name="Android.support.v4.content.FileProvider"
Android:authorities="${applicationId}.provider"
Android:exported="false"
Android:grantUriPermissions="true">
<meta-data
Android:name="Android.support.FILE_PROVIDER_PATHS"
Android:resource="@xml/provider_paths" />
</provider>
</application>
Je ne sais pas pourquoi, j'ai tout fait exactement de la même façon que Pkosta ( https://stackoverflow.com/a/3885804 ), mais j'ai continué à avoir des erreurs:
Java.lang.SecurityException: Permission Denial: opening provider redacted from ProcessRecord{redacted} (redacted) that is not exported from uid redacted
J'ai perdu des heures sur cette question. Le coupable? Kotlin.
val playIntent = Intent(Intent.ACTION_VIEW, uri)
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
intent
définissait en fait getIntent().addFlags
au lieu de fonctionner sur mon playIntent nouvellement déclaré.
je mets cette méthode afin chemin imageuri facilement entrer dans le contenu.
enter code here
public Uri getImageUri(Context context, Bitmap inImage)
{
ByteArrayOutputStream bytes = new ByteArrayOutputStream();
inImage.compress(Bitmap.CompressFormat.PNG, 100, bytes);
String path = MediaStore.Images.Media.insertImage(context.getContentResolver(),
inImage, "Title", null);
return Uri.parse(path);
}
Je sais que la question est assez ancienne mais cette réponse s'adresse aux futurs téléspectateurs. J'ai donc rencontré un problème similaire et, après des recherches, j'ai trouvé une alternative à cette approche.
Votre intention ici, par exemple: Voir votre image depuis votre chemin à Kotlin
val intent = Intent()
intent.setAction(Intent.ACTION_VIEW)
val file = File(currentUri)
intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
val contentURI = getContentUri(context!!, file.absolutePath)
intent.setDataAndType(contentURI,"image/*")
startActivity(intent)
Fonction principale ci-dessous
private fun getContentUri(context:Context, absPath:String):Uri? {
val cursor = context.getContentResolver().query(
MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
arrayOf<String>(MediaStore.Images.Media._ID),
MediaStore.Images.Media.DATA + "=? ",
arrayOf<String>(absPath), null)
if (cursor != null && cursor.moveToFirst())
{
val id = cursor.getInt(cursor.getColumnIndex(MediaStore.MediaColumns._ID))
return Uri.withAppendedPath(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, Integer.toString(id))
}
else if (!absPath.isEmpty())
{
val values = ContentValues()
values.put(MediaStore.Images.Media.DATA, absPath)
return context.getContentResolver().insert(
MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values)
}
else
{
return null
}
}
De même, au lieu d’une image, vous pouvez utiliser n’importe quel autre format de fichier tel que pdf et dans mon cas, cela a très bien fonctionné.
Voici ma solution:
dans Manifest.xml
<application
Android:name=".main.MainApp"
Android:allowBackup="true"
Android:icon="@drawable/ic_app"
Android:label="@string/application_name"
Android:logo="@drawable/ic_app_logo"
Android:theme="@style/MainAppBaseTheme">
<provider
Android:name="androidx.core.content.FileProvider"
Android:authorities="${applicationId}.provider"
Android:exported="false"
Android:grantUriPermissions="true">
<meta-data
Android:name="Android.support.FILE_PROVIDER_PATHS"
Android:resource="@xml/provider_paths"/>
</provider>
dans 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_files" path="."/>
</paths>
dans mon fragment, j'ai le code suivant:
Uri myPhotoFileUri = FileProvider.getUriForFile(getActivity(), getActivity().getApplicationContext().getPackageName() + ".provider", myPhotoFile);
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
intent.putExtra(MediaStore.EXTRA_OUTPUT, myPhotoFileUri);
C'est tout ce dont vous avez besoin.
Aussi pas besoin pour créer
public class GenericFileProvider extends FileProvider {}
Je teste sur Android 5.0, 6.0 et Android 9.0 et sa réussite fonctionne.