Die App stürzt ab, wenn ich versuche, eine Datei zu öffnen. Es funktioniert unter Android Nougat, aber auf Android Nougat stürzt es ab. Es stürzt nur ab, wenn ich versuche, eine Datei von der SD-Karte zu öffnen, nicht von der Systempartition. Irgendein Berechtigungsproblem?
Beispielcode:
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
Log:
Android.os.FileUriExposedException: Datei: ///storage/emulated/0/test.txt über App durch Intent.getData () verfügbar gemacht
Bearbeiten:
Beim Targeting von Android Nougat sind file://
URIs nicht mehr zulässig. Wir sollten stattdessen content://
URIs verwenden. Meine App muss jedoch Dateien in Stammverzeichnissen öffnen. Irgendwelche Ideen?
Wenn Sie targetSdkVersion >= 24
verwenden, müssen Sie die Klasse FileProvider
verwenden, um Zugriff auf die bestimmte Datei oder den Ordner zu gewähren und sie für andere Apps verfügbar zu machen. Wir erstellen eine eigene Klasse, die FileProvider
erbt, um sicherzustellen, dass unser FileProvider nicht mit FileProvidern in Konflikt steht, die in importierten Abhängigkeiten deklariert sind, wie hier beschrieben .
Schritte zum Ersetzen von file://
URI durch content://
URI:
Fügen Sie eine Klasse hinzu, die FileProvider
erweitert.
public class GenericFileProvider extends FileProvider {}
Fügen Sie in <provider>
unter AndroidManifest.xml
ein FileProvider <application>
-Tag hinzu. Geben Sie eine eindeutige Berechtigung für das Attribut Android:authorities
an, um Konflikte zu vermeiden. Importierte Abhängigkeiten können ${applicationId}.provider
und andere häufig verwendete Berechtigungen angeben.
<?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
-Datei im res/xml
-Ordner. Ordner müssen möglicherweise erstellt werden, wenn es nicht vorhanden ist. Der Inhalt der Datei wird unten angezeigt. Es wird beschrieben, dass wir den Zugriff auf den externen Speicher im Stammordner (path=".")
mit dem Namen external_files freigeben möchten.<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:Android="http://schemas.Android.com/apk/res/Android">
<external-path name="external_files" path="."/>
</paths>
Der letzte Schritt besteht darin, die folgende Codezeile in zu ändern
Uri photoURI = Uri.fromFile(createImageFile());
zu
Uri photoURI = FileProvider.getUriForFile(context, context.getApplicationContext().getPackageName() + ".my.package.name.provider", createImageFile());
Bearbeiten: Wenn Sie beabsichtigen, dass das System Ihre Datei öffnet, müssen Sie möglicherweise die folgende Codezeile hinzufügen:
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
Bitte beachten Sie, der vollständige Code und die Lösung wurden hier erklärt .
Neben der Lösung mit FileProvider
gibt es noch eine andere Möglichkeit, dies zu umgehen. Einfach gesagt
StrictMode.VmPolicy.Builder builder = new StrictMode.VmPolicy.Builder();
StrictMode.setVmPolicy(builder.build());
in Application.onCreate()
. Auf diese Weise ignoriert der VM die Belichtung der Datei URI
.
Methode
builder.detectFileUriExposure()
aktiviert die Dateibelichtungsprüfung, die auch das Standardverhalten ist, wenn wir keine VmPolicy einrichten.
Ich bin auf ein Problem gestoßen, bei dem einige Apps etwas nicht verstehen, wenn ich einen content://
URI
zum Senden verwende. Ein Downgrade der target SDK
-Version ist nicht zulässig. In diesem Fall ist meine Lösung nützlich.
pdate:
Wie im Kommentar erwähnt, handelt es sich bei StrictMode um ein Diagnosetool, das für dieses Problem nicht verwendet werden soll. Als ich diese Antwort vor einem Jahr gepostet habe, können viele Apps nur File-Uris empfangen. Sie stürzen gerade ab, als ich versucht habe, einen FileProvider-URL an sie zu senden. Dies ist in den meisten Apps jetzt behoben, daher sollten wir uns für die FileProvider-Lösung entscheiden.
Wenn Ihre App API 24+ als Ziel hat und Sie weiterhin file: // intents verwenden möchten/müssen, können Sie die Laufzeitüberprüfung mithilfe der Hacky-Methode deaktivieren:
if(Build.VERSION.SDK_INT>=24){
try{
Method m = StrictMode.class.getMethod("disableDeathOnFileUriExposure");
m.invoke(null);
}catch(Exception e){
e.printStackTrace();
}
}
Methode StrictMode.disableDeathOnFileUriExposure
ist versteckt und dokumentiert als:
/**
* Used by lame internal apps that haven't done the hard work to get
* themselves off file:// Uris yet.
*/
Das Problem ist, dass meine App nicht lahm ist, sondern durch die Verwendung von content: // intents, die von vielen Apps nicht verstanden werden, nicht behindert werden möchte. Wenn Sie beispielsweise eine MP3-Datei mit dem Inhalt: // Schema öffnen, werden viel weniger Apps angeboten als beim Öffnen derselben mit dem Inhalt: // Schema. Ich möchte nicht für die Designfehler von Google bezahlen, indem ich die Funktionalität meiner App einschränke.
Google möchte, dass Entwickler das Inhaltsschema verwenden, aber das System ist nicht darauf vorbereitet. Jahrelang wurden Apps entwickelt, um Dateien zu verwenden, die nicht "Inhalt" sind. Dateien können bearbeitet und zurückgespeichert werden, während Dateien, die über das Inhaltsschema bereitgestellt werden, nicht (können) Sie?).
Wenn targetSdkVersion
höher als 24 ist, wird FileProvider verwendet, um den Zugriff zu gewähren.
Erstellen Sie eine XML-Datei (Pfad: 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>
Fügen Sie einen Anbieter in AndroidManifest.xml hinzu
<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>
Wenn Sie androidx verwenden, sollte der FileProvider-Pfad wie folgt lauten:
Android:name="androidx.core.content.FileProvider"
und ersetzen
Uri uri = Uri.fromFile(fileImagePath);
zu
Uri uri = FileProvider.getUriForFile(MainActivity.this, BuildConfig.APPLICATION_ID + ".provider",fileImagePath);
Bearbeiten: Wenn Sie die URI mit einem Intent
einschließen, stellen Sie sicher, dass folgende Zeile hinzugefügt wird:
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
und du bist gut zu gehen. Ich hoffe es hilft.
Wenn Ihr targetSdkVersion
24 oder höher ist, Sie können file:
Uri
Werte nicht in Intents
auf Android 7.0+ Geräten verwenden =.
Ihre Wahlmöglichkeiten sind:
Lassen Sie Ihr targetSdkVersion
auf 23 oder niedriger fallen, oder
Stellen Sie Ihren Inhalt in den internen Speicher und verwenden Sie FileProvider
, um ihn selektiv für andere Apps verfügbar zu machen
Zum Beispiel:
Intent i=new Intent(Intent.ACTION_VIEW, FileProvider.getUriForFile(this, AUTHORITY, f));
i.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
startActivity(i);
(von dieses Beispielprojekt )
Zuerst müssen Sie Ihrem AndroidManifest einen Anbieter hinzufügen
<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>
erstellen Sie jetzt eine Datei im XML-Ressourcenordner (wenn Sie Android Studio verwenden, können Sie Alt + Eingabetaste drücken, nachdem Sie file_paths markiert und die Option zum Erstellen einer XML-Ressource ausgewählt haben).
Als nächstes in die Datei file_paths eingeben
<?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>
Dieses Beispiel gilt für den externen Pfad. Weitere Optionen finden Sie unter hier . Auf diese Weise können Sie Dateien freigeben, die sich in diesem Ordner und seinem Unterordner befinden.
Jetzt müssen Sie nur noch die Absicht wie folgt erstellen:
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: Ich habe den Root-Ordner der SD-Karte in den Dateipfaden hinzugefügt. Ich habe diesen Code getestet und es funktioniert.
@palash k Antwort ist korrekt und funktioniert für interne Speicherdateien, aber in meinem Fall möchte ich auch Dateien von externem Speicher öffnen, meine App stürzte ab, wenn ich Dateien von externem Speicher wie SD-Karte und USB öffne, aber ich schaffe es, das Problem durch Ändern zu lösen provider_paths.xml aus der akzeptierten Antwort
ändern Sie das provider_paths.xml wie unten
<?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>
und in Java Klasse (Keine Änderung als akzeptierte Antwort, nur eine kleine Änderung)
Uri uri=FileProvider.getUriForFile(getActivity(), BuildConfig.APPLICATION_ID+".provider", File)
Dies hilft mir, den Absturz für Dateien aus externen Speichern zu beheben.
Fügen Sie einfach den folgenden Code in die Aktivität onCreate () ein.
StrictMode.VmPolicy.Builder builder = new StrictMode.VmPolicy.Builder(); StrictMode.setVmPolicy(builder.build());
URI-Belichtung wird ignoriert
Meine Lösung bestand darin, "Uri.parse" den Dateipfad als Zeichenfolge anstelle von "Uri.fromFile ()" zu verwenden.
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);
Scheint, dass fromFile () einen Dateizeiger verwendet, von dem ich annehme, dass er unsicher ist, wenn Speicheradressen für alle Apps verfügbar sind. Ein Dateipfad-String hat jedoch niemanden verletzt, sodass er funktioniert, ohne FileUriExposedException auszulösen.
Getestet auf API Level 9 bis 27! Öffnet die Textdatei erfolgreich zur Bearbeitung in einer anderen App. Benötigt weder FileProvider noch die Android Support-Bibliothek.
Der fileProvider ist der richtige Weg. Sie können diese einfache Problemumgehung verwenden:
WARNING: Dies wird in der nächsten Android Version - https://issuetracker.google.com) behoben/issues/37122890 # comment4
ersetzen:
startActivity(intent);
durch
startActivity(Intent.createChooser(intent, "Your title"));
Fügen Sie einfach den folgenden Code in die Aktivität onCreate()
ein.
StrictMode.VmPolicy.Builder builder = new StrictMode.VmPolicy.Builder();
StrictMode.setVmPolicy(builder.build());
URI-Belichtung wird ignoriert.
Viel Spaß beim Programmieren :-)
Ich habe die oben gegebene Antwort von Palash verwendet, aber sie war etwas unvollständig, ich musste die Erlaubnis dazu geben
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);
Fügen Sie einfach den folgenden Code in die Aktivität onCreate () ein.
Builder für StrictMode.VmPolicy.Builder = neu StrictMode.VmPolicy.Builder (); StrictMode.setVmPolicy (builder.build ());
URI-Belichtung wird ignoriert
füge diese zwei Zeilen in onCreate hinz
StrictMode.VmPolicy.Builder builder = new StrictMode.VmPolicy.Builder();
StrictMode.setVmPolicy(builder.build());
Share-Methode
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..."));
Fügen Sie zum Herunterladen von PDF-Dateien vom Server den folgenden Code in Ihre Serviceklasse ein. Hoffe das ist hilfreich für dich.
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");
}
Und ja, vergessen Sie nicht, Berechtigungen und Anbieter in Ihrem Manifest hinzuzufügen.
<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>
Ich weiß nicht warum, ich habe alles genauso gemacht wie Pkosta ( https://stackoverflow.com/a/3885804 ), habe aber immer wieder den Fehler erhalten:
Java.lang.SecurityException: Permission Denial: opening provider redacted from ProcessRecord{redacted} (redacted) that is not exported from uid redacted
Ich habe Stunden mit diesem Thema verschwendet. Der Täter? Kotlin.
val playIntent = Intent(Intent.ACTION_VIEW, uri)
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
intent
hat tatsächlich getIntent().addFlags
gesetzt, anstatt auf meinem neu deklarierten playIntent zu arbeiten.
ich setze diese Methode so, dass imageuri Pfad leicht in den Inhalt zu bekommen.
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);
}
Ich weiß, dass dies eine ziemlich alte Frage ist, aber diese Antwort ist für zukünftige Zuschauer bestimmt. Ich bin also auf ein ähnliches Problem gestoßen und habe nach Recherchen eine Alternative zu diesem Ansatz gefunden.
Ihre Absicht hier für zB: Um Ihr Bild von Ihrem Weg in Kotlin zu sehen
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)
Hauptfunktion unten
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
}
}
Ebenso können Sie anstelle eines Bildes ein beliebiges anderes Dateiformat wie pdf verwenden, und in meinem Fall hat es einwandfrei funktioniert
Hier meine Lösung:
in 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>
in 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>
in meinem Fragment habe ich den nächsten Code:
Uri myPhotoFileUri = FileProvider.getUriForFile(getActivity(), getActivity().getApplicationContext().getPackageName() + ".provider", myPhotoFile);
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
intent.putExtra(MediaStore.EXTRA_OUTPUT, myPhotoFileUri);
Das ist alles was Sie brauchen.
Auch nicht nötig zu erstellen
public class GenericFileProvider extends FileProvider {}
Ich teste auf Android 5.0, 6.0 und Android 9.0 und es ist eine erfolgreiche Arbeit.