android.os.FileUriExposedException: Datei: ///storage/emulated/0/test.txt über Intent.getData () hinaus über die App hinaus verfügbar gemacht


738

Die App stürzt ab, wenn ich versuche, eine Datei zu öffnen. Es funktioniert unter Android Nougat, aber unter 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. Ein 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 Intent.getData () hinaus über die App hinaus verfügbar gemacht

Bearbeiten:

Bei der Ausrichtung auf Android Nougat sind file://URIs nicht mehr zulässig. Wir sollten content://stattdessen URIs verwenden. Meine App muss jedoch Dateien in Stammverzeichnissen öffnen. Irgendwelche Ideen?


20
Ich denke, dies war ein Fehler, der App-Entwicklern das Leben unnötig schwer macht. Die Notwendigkeit, mit jeder App einen "FileProvider" und eine "Autorität" zu bündeln, scheint ein Enterprisey-Boilerplate zu sein. Das Hinzufügen eines Flags zu jeder Dateiabsicht erscheint umständlich und möglicherweise unnötig. Das elegante Konzept der "Wege" zu brechen ist unangenehm. Und was ist der Vorteil? Selektiven Speicherzugriff auf Apps gewähren (während die meisten Apps vollen SD-Kartenzugriff haben, insbesondere diejenigen, die mit Dateien arbeiten)?
Nyanpasu64

2
Versuchen Sie dies, kleiner und perfekter Code stackoverflow.com/a/52695444/4997704
Binesh Kumar

Antworten:


1316

Wenn ja targetSdkVersion >= 24, müssen wir FileProviderclass verwenden, um Zugriff auf die bestimmte Datei oder den Ordner zu gewähren, damit diese für andere Apps zugänglich sind. Wir erstellen eine eigene Klasse, die erbt FileProvider, um sicherzustellen, dass unser FileProvider nicht mit FileProviders in Konflikt steht, die in importierten Abhängigkeiten deklariert sind, wie hier beschrieben .

Schritte zum Ersetzen des file://URI durch den content://URI:

  • Fügen Sie eine erweiterte Klasse hinzu FileProvider

    public class GenericFileProvider extends FileProvider {}
  • Fügen Sie einen FileProvider <provider>Tag in AndroidManifest.xmlunter - <application>Tag. Geben Sie eine eindeutige Berechtigung für das android:authoritiesAttribut an, um Konflikte, importierte Abhängigkeiten ${applicationId}.providerund andere häufig verwendete Berechtigungen zu vermeiden .

<?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}.provider"
            android:exported="false"
            android:grantUriPermissions="true">
            <meta-data
                android:name="android.support.FILE_PROVIDER_PATHS"
                android:resource="@xml/provider_paths"/>
        </provider>
    </application>
</manifest>
  • Erstellen Sie dann eine provider_paths.xmlDatei im res/xmlOrdner. Möglicherweise wird ein Ordner zum Erstellen benötigt, wenn er 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() + ".provider", createImageFile());
  • Bearbeiten: Wenn Sie beabsichtigen, das System zum Öffnen Ihrer Datei zu veranlassen, müssen Sie möglicherweise die folgende Codezeile hinzufügen:

    intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);

Bitte beachten Sie, dass der vollständige Code und die Lösung hier erläutert wurden.


62
Ich musste nur intent.setFlags (Intent.FLAG_GRANT_READ_URI_PERMISSION) hinzufügen.
Alorma

24
Funktioniert es für alle Android-Versionen oder nur für API 24?
Android-Entwickler

9
Dieser Artikel hat mir geholfen medium.com/@ali.muzaffar/…
AbdulMomen

11
@rockhammer Ich habe dies gerade mit Android 5.0, 6.0, 7.1 und 8.1 getestet, es funktioniert in allen Fällen. Der (Build.VERSION.SDK_INT > M)Zustand ist also nutzlos.
Sébastien

66
FileProvidersollte nur erweitert werden, wenn Sie eines der Standardverhalten überschreiben möchten, andernfalls verwenden android:name="android.support.v4.content.FileProvider". Siehe developer.android.com/reference/android/support/v4/content/…
JP Ventura

313

Neben der Lösung mit FileProvidergibt 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 die VM die URIDateibelichtung.

Methode

builder.detectFileUriExposure()

Aktiviert die Überprüfung der Dateibelichtung. Dies ist auch das Standardverhalten, wenn keine VmPolicy eingerichtet wird.

Ich bin auf ein Problem gestoßen, dass content:// URIeinige Apps es einfach nicht verstehen können , wenn ich etwas zum Senden verwende. Ein Downgrade der target SDKVersion ist nicht zulässig. In diesem Fall ist meine Lösung nützlich.

Aktualisieren:

Wie im Kommentar erwähnt, ist StrictMode ein Diagnosetool und sollte für dieses Problem nicht verwendet werden. Als ich diese Antwort vor einem Jahr gepostet habe, können viele Apps nur Datei-Uris empfangen. Sie stürzen gerade ab, als ich versuchte, ihnen eine FileProvider-URL zu senden. Dies ist jetzt in den meisten Apps behoben, daher sollten wir uns für die FileProvider-Lösung entscheiden.


1
@LaurynasG Von API 18 bis 23 prüft Android standardmäßig nicht, ob die Datei uri verfügbar ist. Das Aufrufen dieser Methode aktiviert diese Prüfung. Ab API 24 führt Android diese Überprüfung standardmäßig durch. Wir können es jedoch deaktivieren, indem wir ein neues festlegen VmPolicy.
Hqzxzwb

Ist noch ein weiterer Schritt erforderlich, damit dies funktioniert? Funktioniert nicht, da es für mein Moto G mit Android 7.0 steht.
CKP78

3
Wie dies dieses Problem lösen kann, ist StrictMode ein Diagnosetool, das im Entwicklermodus und nicht im Release-Modus aktiviert werden sollte.
Imene Noomene

1
@ImeneNoomene Eigentlich deaktivieren wir hier StrictMode. Es erscheint vernünftig, StrictMode nicht im Release-Modus zu aktivieren, aber tatsächlich aktiviert Android standardmäßig einige StrictMode-Optionen, unabhängig vom Debug-Modus oder Release-Modus. Auf die eine oder andere Weise sollte diese Antwort nur eine Problemumgehung sein, wenn einige Ziel-Apps nicht auf den Empfang von Content-Uris vorbereitet waren. Nachdem die meisten Apps Unterstützung für Content-Uris hinzugefügt haben, sollten wir das FileProvider-Muster verwenden.
Hqzxzwb

3
@ImeneNoomene Ich bin total bei dir in deiner Empörung. Sie haben Recht, dies ist ein Diagnosetool, oder zumindest war es lange her, als ich es meinen Projekten hinzufügte. Das ist super frustrierend! StrictMode.enableDefaults();, das ich nur in meinen Entwicklungs-Builds ausführe, verhindert, dass dieser Absturz auftritt. Daher habe ich jetzt eine Produktionsanwendung, die abstürzt, aber in der Entwicklung nicht abstürzt. Wenn Sie hier ein Diagnosetool aktivieren, wird ein ernstes Problem verborgen. Vielen Dank an @hqzxzwb, dass Sie mir dabei geholfen haben, dies zu entmystifizieren.
Jon

174

Wenn targetSdkVersionhöher als 24 , 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 Provider 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: Während Sie den URI mit einem Intenthinzufügen, stellen Sie sicher, dass Sie die folgende Zeile hinzufügen:

intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);

und du bist gut zu gehen. Ich hoffe es hilft.


2
@MaksimKniazev Kannst du deinen Fehler kurz beschreiben? Damit ich dir helfen kann.
Pankaj Lilan

1
@PankajLilan, ich habe genau das getan, was du gesagt hast. Aber jedes Mal, wenn ich mein PDF in der anderen Anwendung öffne, erscheint es leer (es wird korrekt gespeichert). Sollte ich die XML bearbeiten müssen? Ich habe bereits die FLAG_GRANT_READ_URI_PERMISSION hinzugefügt.
Felipe Castilhos

1
Mein Fehler, ich habe die Erlaubnis der falschen Absicht hinzugefügt. Dies ist die beste und einfachste richtige Antwort. Vielen Dank!
Felipe Castilhos

2
Es wird eine Ausnahme ausgelöst: java.lang.IllegalArgumentException: Das konfigurierte Stammverzeichnis mit / Mein Dateipfad ist /storage/emulated/0/GSMManage_DCIM/Documents/Device7298file_74.pdf konnte nicht gefunden werden. können Sie bitte helfen ?
Jagdish Bhavsar

1
Ich weiß nicht warum, aber ich musste sowohl READ- als auch WRITE-Berechtigungen hinzufügen: target.addFlags (Intent.FLAG_GRANT_READ_URI_PERMISSION) target.addFlags (Intent.FLAG_GRANT_WRITE_URI_PERMISSION)
Feld

160

Wenn Ihre App auf API 24+ abzielt und Sie weiterhin file: // intents verwenden möchten / müssen, können Sie die Laufzeitprüfung auf hackige Weise deaktivieren:

if(Build.VERSION.SDK_INT>=24){
   try{
      Method m = StrictMode.class.getMethod("disableDeathOnFileUriExposure");
      m.invoke(null);
   }catch(Exception e){
      e.printStackTrace();
   }
}

Methode StrictMode.disableDeathOnFileUriExposureist 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 nicht durch die Verwendung von content: // Intents verkrüppelt werden möchte, die von vielen Apps da draußen nicht verstanden werden. Zum Beispiel bietet das Öffnen einer MP3-Datei mit dem Schema content: // viel weniger Apps als beim Öffnen derselben über das Schema file: //. Ich möchte nicht für Googles Designfehler 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?).


3
"Während Dateien, die über das Inhaltsschema bereitgestellt werden, nicht sein können (oder?)." - Sicher, wenn Sie Schreibzugriff auf den Inhalt haben. ContentResolverhat beides openInputStream()und openOutputStream(). Eine weniger hackige Methode besteht darin , die VM-Regeln einfach selbst zu konfigurieren und die file UriRegel nicht zu aktivieren .
CommonsWare

1
Genau. Es ist harte Arbeit, wenn Sie Ihre gesamte App erstellt haben und dann nach dem Targeting von 25 alle Ihre Kameramethoden nicht mehr funktionieren. Dies funktioniert für mich, bis ich Zeit habe, es richtig zu machen.
Matt W

5
Funktioniert auf Android 7. Danke
Anton Kizema

4
Funktioniert auch auf Android 8, getestet auf Huawei Nexus 6P.
Gonzalo Ledezma Torres

4
Ich bestätige, dass dies in der Produktion funktioniert (ich habe mehr als 500.000 Benutzer). Derzeit ist Version 8.1 die höchste Version und funktioniert darauf.
Eli

90

Wenn Sie mindestenstargetSdkVersion 24 Jahre alt sind , können Sie auf Android 7.0+ Geräten keine file: UriWerte Intentsverwenden .

Sie haben folgende Möglichkeiten:

  1. Lassen Sie Ihre targetSdkVersionauf 23 oder niedriger fallen, oder

  2. Legen Sie Ihre Inhalte auf internen Speicher, dann verwendenFileProvider , um es selektiv zu anderen Anwendungen zur Verfügung zu stellen

Zum Beispiel:

Intent i=new Intent(Intent.ACTION_VIEW, FileProvider.getUriForFile(this, AUTHORITY, f));

i.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
startActivity(i);

(aus diesem Beispielprojekt )


Danke für die Antwort. Was passiert, wenn ich dies mit Dateien auf der /systemPartition verwende? Jede App sollte in der Lage sein, auf diese Partition ohne Root zuzugreifen.
Thomas Vos

2
@SuperThomasLab: Ich würde nicht darauf zählen, dass alles weltlesbar ist /system. Abgesehen davon denke ich, dass Sie diese Ausnahme immer noch bekommen werden. Ich vermute, dass sie nur das Schema überprüfen und nicht versuchen festzustellen, ob die Datei wirklich weltlesbar ist. Es wird FileProviderIhnen jedoch nicht helfen, da Sie es nicht lehren können, von zu dienen /system. Sie können eine benutzerdefinierte Strategie für mich erstellenStreamProvider oder Ihre eigene Strategie erstellenContentProvider , um das Problem zu umgehen.
CommonsWare

Ich denke immer noch darüber nach, wie ich das lösen soll. Die App, die ich mit Android N-Unterstützung aktualisiere, ist ein Root-Browser. Jetzt können Sie keine Dateien mehr in Root-Verzeichnissen öffnen. ( /data, /system), wegen dieser "guten Veränderung".
Thomas Vos

1
Was sind die wichtigsten Nachteile, um targetSdkVersion auf 23 zu senken? thnx
rommex

2
@rommex: Ich weiß nicht, was als "am wichtigsten" qualifiziert ist. Beispielsweise wird Benutzern, die im Split-Screen-Modus oder auf Freiform-Geräten mit mehreren Fenstern (Chromebooks, Samsung DeX) arbeiten, mitgeteilt, dass Ihre App möglicherweise nicht mit mehreren Fenstern funktioniert. Ob das wichtig ist oder nicht, liegt bei Ihnen.
CommonsWare

47

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-Ressourcenoption ausgewählt haben.)

Geben Sie als Nächstes in die Datei file_paths ein

<?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 ist für einen externen Pfad, den Sie hier referieren können Weitere Optionen . 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();
    }

BEARBEITEN : Ich habe den Stammordner der SD-Karte in den Dateipfaden hinzugefügt. Ich habe diesen Code getestet und er funktioniert.


1
Danke dafür. Ich möchte Sie auch darüber informieren, dass es einen besseren Weg gibt, die Dateierweiterung zu erhalten. String extension = android.webkit.MimeTypeMap.getFileExtensionFromUrl(Uri.fromFile(file).toString()); Außerdem empfehle ich jedem, der nach Antworten sucht, zuerst FileProvider durchzulesen und zu verstehen, was Sie hier mit Dateiberechtigungen in Android N und höher tun. Es gibt Optionen für internen Speicher gegenüber externem Speicher sowie für regulären Dateipfad im Vergleich zu Cache-Pfaden.
Praneetloke

2
Ich bekam die folgende Ausnahme: java.lang.IllegalArgumentException: Failed to find configured root ...und das einzige, was funktionierte, war <files-path path="." name="files_root" />die XML-Datei anstelle von <external-path .... Meine Datei wurde im internen Speicher gespeichert.
Steliosf

26

Die Antwort von @palash k ist korrekt und funktioniert für interne Speicherdateien. In meinem Fall möchte ich jedoch auch Dateien aus dem externen Speicher öffnen. Meine App stürzte ab, wenn Dateien aus dem externen Speicher wie SD-Karte und USB geöffnet wurden. Ich kann das Problem jedoch durch Ändern lösen provider_paths.xml aus der akzeptierten Antwort

Ändern Sie die Datei 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 der 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. Ich hoffe, dies hilft jemandem, der das gleiche Problem wie ich hat :)


1
Wo haben Sie <root-pathbitte gefunden? Es funktioniert. <external-path path="Android/data/${applicationId}/" name="files_root" />hatte keine Auswirkung auf geöffnete Dateien aus dem externen Speicher.
bis

Ich finde dies aus verschiedenen Suchergebnissen, lassen Sie mich noch einmal überprüfen und so schnell wie möglich zu
Ihnen zurückkehren

Auch der von Ihnen erwähnte externe Speicher ist SD-Karte oder eingebauter Speicher?
Ramz

Entschuldigung für die Ungenauigkeit. Ich meinte Android/data/${applicationId}/in SD-Karte.
Bis zum

1
Sie müssen dies zur Absicht hinzufügen: intent.addFlags (Intent.FLAG_GRANT_READ_URI_PERMISSION);
S-Jäger

26

Meine Lösung bestand darin, den Dateipfad als Zeichenfolge 'Uri.parse' zu verwenden, anstatt 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, doesn't work in Android 10.
}
Intent viewFile = new Intent(Intent.ACTION_VIEW);
viewFile.setDataAndType(uri, "text/plain");
startActivity(viewFile);

Scheint, dass fromFile () einen Dateizeiger verwendet, der vermutlich 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 erfolgreich die Textdatei zur Bearbeitung in einer anderen App. Benötigt weder FileProvider noch die Android Support Library.


Ich wünschte, ich hätte das zuerst gesehen. Ich habe nicht bewiesen, dass es bei mir funktioniert, aber es ist so viel weniger umständlich als FileProvider.
Dale

Ein Hinweis, warum dies tatsächlich funktioniert: Es ist nicht der Dateizeiger, der das Problem verursacht, sondern die Tatsache, dass die Ausnahme nur auftritt, wenn Sie einen Pfad mit 'file: //' haben, dem automatisch fromFile, aber nicht parse vorangestellt wird .
Xmister

3
Dies stellt keine Ausnahme dar, kann die Datei jedoch auch nicht an die zugehörige App senden. Also nicht für mich gearbeitet.
Serdar Samancıoğlu

1
Dies schlägt unter Android 10 und höher fehl, da Sie nicht davon ausgehen können, dass die andere App über das Dateisystem Zugriff auf externen Speicher hat.
CommonsWare

24

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

Die URI-Exposition wird ignoriert


1
Dies ist eine der Lösungen, aber nicht die Standardlösung. Stil Leute, die die Antworten abgelehnt haben, sind falsch, da dies auch ein funktionierender Code mit der funktionierenden Lösung ist.
Saksham

23

Fügen Sie einfach den folgenden Code in die Aktivität ein onCreate().

StrictMode.VmPolicy.Builder builder = new StrictMode.VmPolicy.Builder(); 
StrictMode.setVmPolicy(builder.build());

Die URI-Exposition wird ignoriert.

Viel Spaß beim Codieren :-)


1
Was sind die Nachteile davon?
James F

1
Dies schlägt unter Android 10 und höher fehl, da Sie nicht davon ausgehen können, dass die andere App über das Dateisystem Zugriff auf externen Speicher hat.
CommonsWare

18

Die Verwendung des fileProviders ist der richtige Weg. Sie können jedoch diese einfache Problemumgehung verwenden:

WARNUNG : Es wird in der nächsten Android-Version behoben - https://issuetracker.google.com/issues/37122890#comment4

ersetzen:

startActivity(intent);

durch

startActivity(Intent.createChooser(intent, "Your title"));

7
Die Auswahl wird in Kürze von Google gepatcht, um denselben Scheck zu enthalten. Dies ist keine Lösung.
Zeiger Null

Dieser funktioniert, wird aber in zukünftigen Android-Versionen nicht funktionieren.
Diljeet

13

Ich habe die oben angegebene Antwort von Palash verwendet, aber sie war etwas unvollständig. Ich musste eine solche Erlaubnis erteilen

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

11

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

Die URI-Exposition wird ignoriert


Dadurch werden die Richtlinien für den strengen Modus entfernt. und ignoriert die Sicherheitswarnung. Keine gute Lösung.
Inspire_Coding

Es schlägt auch unter Android 10 und höher fehl, da Sie nicht davon ausgehen können, dass die andere App über das Dateisystem Zugriff auf externen Speicher hat.
CommonsWare

7

Fügen Sie diese beiden Zeilen in onCreate hinzu

StrictMode.VmPolicy.Builder builder = new StrictMode.VmPolicy.Builder();
    StrictMode.setVmPolicy(builder.build());

Freigabemethode

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..."));

Dies schlägt unter Android 10 und höher fehl, da Sie nicht davon ausgehen können, dass die andere App über das Dateisystem Zugriff auf externen Speicher hat.
CommonsWare

7

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 du brauchst.

Auch muss nicht erstellt werden

public class GenericFileProvider extends FileProvider {}

Ich teste auf Android 5.0, 6.0 und Android 9.0 und es funktioniert erfolgreich.


Ich habe diese Lösung getestet und sie funktioniert einwandfrei mit einer kleinen Änderung: intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK intent.putExtra (Intent.EXTRA_STREAM, myPhotoFileUri) intent.type = "image / png" startActivity (Intent.createChooser (intent, ") Bild teilen über ")) Es funktioniert fin auf Android 7 und 8.
inspir_coding

4

Fügen Sie zum Herunterladen von PDF 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, Ihrem Manifest Berechtigungen und Anbieter 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>

1
was ist @xml/provider_paths?
Adityasnl

1
@ Heisenberg beziehen sich bitte Rahul Upadhyay Beitrag von URL: stackoverflow.com/questions/38555301/…
Bhoomika Chauhan

3

Ich weiß nicht warum, ich habe alles genauso gemacht wie Pkosta ( https://stackoverflow.com/a/38858040 ), aber immer wieder 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)

intentwurde tatsächlich eingestellt, getIntent().addFlagsanstatt auf meinem neu deklarierten playIntent zu arbeiten.


2

Ich habe diese Methode so eingestellt, dass der Imageuri-Pfad leicht in den Inhalt gelangt.

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

2
As of Android N, in order to work around this issue, you need to use the FileProvider API

Hier gibt es 3 Hauptschritte, wie unten erwähnt

Schritt 1: Manifesteintrag

<manifest ...>
    <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>
</manifest>

Schritt 2: Erstellen Sie die XML-Datei 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>

Schritt 3: Codeänderungen

File file = ...;
Intent install = new Intent(Intent.ACTION_VIEW);
install.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_NEW_TASK);
// Old Approach
    install.setDataAndType(Uri.fromFile(file), mimeType);
// End Old approach
// New Approach
    Uri apkURI = FileProvider.getUriForFile(
                             context, 
                             context.getApplicationContext()
                             .getPackageName() + ".provider", file);
    install.setDataAndType(apkURI, mimeType);
    install.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
// End New Approach
    context.startActivity(install);

1

Ich weiß, dass dies eine ziemlich alte Frage ist, aber diese Antwort ist für zukünftige Zuschauer. 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 jedes andere Dateiformat wie PDF verwenden, und in meinem Fall hat es einwandfrei funktioniert


0

Ich habe fast einen Tag damit verbracht herauszufinden, warum ich diese Ausnahme bekam. Nach vielen Kämpfen funktionierte diese Konfiguration perfekt ( Kotlin ):

AndroidManifest.xml

<provider
  android:name="androidx.core.content.FileProvider"
  android:authorities="com.lomza.moviesroom.fileprovider"
  android:exported="false"
  android:grantUriPermissions="true">
  <meta-data
    android:name="android.support.FILE_PROVIDER_PATHS"
    android:resource="@xml/file_paths" />
</provider>

file_paths.xml

<?xml version="1.0" encoding="utf-8"?>
<paths>
  <files-path name="movies_csv_files" path="."/>
</paths>

Absicht selbst

fun goToFileIntent(context: Context, file: File): Intent {
    val intent = Intent(Intent.ACTION_VIEW)
    val contentUri = FileProvider.getUriForFile(context, "${context.packageName}.fileprovider", file)
    val mimeType = context.contentResolver.getType(contentUri)
    intent.setDataAndType(contentUri, mimeType)
    intent.flags = Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_GRANT_WRITE_URI_PERMISSION

    return intent
}

Ich erkläre den gesamten Prozess hier .


-1

https://stackoverflow.com/a/38858040/395097 Diese Antwort ist vollständig.

Diese Antwort ist für - Sie haben bereits eine App, deren Ziel unter 24 lag, und jetzt aktualisieren Sie auf targetSDKVersion> = 24.

In Android N wird nur die Datei uri geändert, die für Apps von Drittanbietern verfügbar ist. (Nicht so, wie wir es vorher benutzt haben). Ändern Sie also nur die Stellen, an denen Sie den Pfad mit der Drittanbieter-App teilen (in meinem Fall Kamera).

In unserer App haben wir uri an die Kamera-App gesendet. An diesem Ort erwarten wir, dass die Kamera-App das aufgenommene Bild speichert.

  1. Für Android N generieren wir eine neue URL auf Content: // uri-Basis, die auf eine Datei verweist.
  2. Wir generieren dafür den üblichen File-API-basierten Pfad (unter Verwendung einer älteren Methode).

Jetzt haben wir 2 verschiedene URLs für dieselbe Datei. # 1 wird mit der Kamera-App geteilt. Wenn die Absicht der Kamera erfolgreich ist, können wir von # 2 aus auf das Bild zugreifen.

Hoffe das hilft.


1
Sie verweisen auf eine Antwort, die bereits hier veröffentlicht wurde. Wenn Sie sie vervollständigen müssen, kommentieren Sie sie in der Antwort plz.
IgniteCoders

1
@IgniteCoders Wie ich in der Nachricht klar erwähnt habe, deckt meine Antwort den zugehörigen Anwendungsfall ab.
Aram

-1

Xamarin.Android

Hinweis: Der Pfad xml / provider_paths.xml (.axml) konnte nicht aufgelöst werden, selbst nachdem der XML- Ordner unter Ressourcen erstellt wurde (möglicherweise kann er an einem vorhandenen Speicherort wie Werte abgelegt werden , habe es nicht versucht), daher habe ich darauf zurückgegriffen das was jetzt funktioniert. Tests haben gezeigt, dass es nur einmal pro Anwendungslauf aufgerufen werden muss (was sinnvoll ist, da es den Betriebszustand der Host-VM ändert).

Hinweis: XML muss groß geschrieben werden, also Resources / Xml / provider_paths.xml

Java.Lang.ClassLoader cl = _this.Context.ClassLoader;
Java.Lang.Class strictMode = cl.LoadClass("android.os.StrictMode");                
System.IntPtr ptrStrictMode = JNIEnv.FindClass("android/os/StrictMode");
var method = JNIEnv.GetStaticMethodID(ptrStrictMode, "disableDeathOnFileUriExposure", "()V");                
JNIEnv.CallStaticVoidMethod(strictMode.Handle, method);

-1

Die Antwort von @Pkosta ist eine Möglichkeit, dies zu tun.

Neben der Verwendung FileProviderkönnen Sie die Datei auch in MediaStore(insbesondere für Bild- und Videodateien) einfügen , da auf Dateien im MediaStore für jede App zugegriffen werden kann:

Der MediaStore richtet sich in erster Linie an Video-, Audio- und Bild-MIME-Typen. Ab Android 3.0 (API-Level 11) können jedoch auch Nicht-Medientypen gespeichert werden (weitere Informationen finden Sie unter MediaStore.Files). Dateien können mit scanFile () in den MediaStore eingefügt werden. Anschließend wird ein für die Freigabe geeigneter Uri im Stil content: // an den bereitgestellten Rückruf onScanCompleted () übergeben. Beachten Sie, dass der Inhalt nach dem Hinzufügen zum MediaStore des Systems für jede App auf dem Gerät zugänglich ist.

Sie können beispielsweise eine Videodatei wie folgt in MediaStore einfügen:

ContentValues values = new ContentValues();
values.put(MediaStore.Video.Media.DATA, videoFilePath);
Uri contentUri = context.getContentResolver().insert(
      MediaStore.Video.Media.EXTERNAL_CONTENT_URI, values);

contentUriist wie content://media/external/video/media/183473, die direkt übergeben werden kann an Intent.putExtra:

intent.setType("video/*");
intent.putExtra(Intent.EXTRA_STREAM, contentUri);
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
activity.startActivity(intent);

Dies funktioniert für mich und erspart den Aufwand bei der Verwendung FileProvider.


-1

Lassen Sie es einfach die URI-Belichtung ignorieren ... Fügen Sie sie nach dem Erstellen hinzu

StrictMode.VmPolicy.Builder builder = new StrictMode.VmPolicy.Builder(); StrictMode.setVmPolicy(builder.build()); 

Dies schlägt unter Android 10 und höher fehl, da Sie nicht davon ausgehen können, dass die andere App über das Dateisystem Zugriff auf externen Speicher hat.
CommonsWare

Dies darf nicht in einer Produktions-App verwendet werden.
Jorgesys

-1

Versuchen Sie diese Lösung

STELLEN SIE DIESE ERLAUBNISSE IN MANIFEST

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

Absicht, Bilder aufzunehmen

Intent takePictureIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
                if (takePictureIntent.resolveActivity(getPackageManager()) != null) {
                    startActivityForResult(takePictureIntent, REQUEST_IMAGE_CAPTURE);
                }

ERHALTEN SIE EIN ERFASSTES BILD IN ONAKTIVITÄTSERGEBNIS

@Override
            protected void onActivityResult(int requestCode, int resultCode, Intent data) {
                super.onActivityResult(requestCode, resultCode, data);
                if (requestCode == REQUEST_IMAGE_CAPTURE && resultCode == RESULT_OK) {
                    Bundle extras = data.getExtras();
                    Bitmap imageBitmap = (Bitmap) extras.get("data");
                    // CALL THIS METHOD TO GET THE URI FROM THE BITMAP
                    Uri tempUri = getImageUri(getApplicationContext(), imageBitmap);
                    //DO SOMETHING WITH URI
                }
            } 

Methode zum Abrufen der Bild-URI

public Uri getImageUri(Context inContext, Bitmap inImage) {
        ByteArrayOutputStream bytes = new ByteArrayOutputStream();
        inImage.compress(Bitmap.CompressFormat.JPEG, 100, bytes);
        String path = MediaStore.Images.Media.insertImage(inContext.getContentResolver(), inImage, "Title", null);
        return Uri.parse(path);
    }

kann mir jemand sagen warum runter abstimmen. Dies ist eine 100% ige Arbeitslösung.
Abdul Basit Rishi

Dadurch erhalten Sie nur das Miniaturbild und nicht das vollständige Bild.
Build3r

-2

In meinem Fall habe ich die Ausnahme durch Ersetzen SetDataAndTypedurch just beseitigt SetData.

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.