android.os.FileUriExposedException: file: ///storage/emulated/0/test.txt esposto oltre l'app tramite Intent.getData ()


738

L'app si arresta in modo anomalo quando provo ad aprire un file. Funziona sotto Android Nougat, ma su Android Nougat si arresta in modo anomalo. Si arresta in modo anomalo solo quando provo ad aprire un file dalla scheda SD, non dalla partizione di sistema. Qualche problema di autorizzazione?

Codice di esempio:

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: file: ///storage/emulated/0/test.txt esposto oltre l'app tramite Intent.getData ()

Modificare:

Quando si sceglie come target Android Nougat, gli file://URI non sono più consentiti. Dovremmo usare content://invece gli URI. Tuttavia, la mia app deve aprire i file nelle directory principali. Qualche idea?


20
Sento che questo è stato un errore che rende la vita inutilmente difficile per gli sviluppatori di app. Dover raggruppare un "FileProvider" e "autorità" con ogni app, sembra una piastra di comando di Enterprisey. Dover aggiungere un flag ad ogni intento del file sembra imbarazzante e forse non necessario. Rompere l'elegante concetto di "percorsi" è spiacevole. E qual è il vantaggio? Garantire selettivamente l'accesso allo spazio di archiviazione delle app (mentre la maggior parte delle app dispone dell'accesso sdcard completo, in particolare di quelle che funzionano sui file)?
nyanpasu64,

2
provare questo, piccolo e perfetto codice stackoverflow.com/a/52695444/4997704
Binesh Kumar

Risposte:


1316

Se il tuo targetSdkVersion >= 24, allora dobbiamo usare la FileProviderclasse per dare accesso a un determinato file o cartella per renderli accessibili per altre app. Creiamo la nostra eredità di classe FileProviderper assicurarci che il nostro FileProvider non sia in conflitto con FileProviders dichiarati nelle dipendenze importate come descritto qui .

Passaggi per sostituire file://URI con content://URI:

  • Aggiungi una classe che si estende FileProvider

    public class GenericFileProvider extends FileProvider {}
  • Aggiungi un <provider>tag FileProvider AndroidManifest.xmlsotto il <application>tag. Specificare un'autorità unica per l' android:authoritiesattributo per evitare conflitti, le dipendenze importate potrebbero specificare ${applicationId}.providere altre autorità comunemente utilizzate.

<?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>
  • Quindi creare un provider_paths.xmlfile nella res/xmlcartella. La cartella potrebbe essere necessaria per la creazione se non esiste. Il contenuto del file è mostrato di seguito. Descrive che vorremmo condividere l'accesso alla memoria esterna nella cartella principale (path=".")con il nome 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>
  • Il passaggio finale consiste nel modificare la riga di codice di seguito in

    Uri photoURI = Uri.fromFile(createImageFile());

    per

    Uri photoURI = FileProvider.getUriForFile(context, context.getApplicationContext().getPackageName() + ".provider", createImageFile());
  • Modifica: se stai usando un intento per rendere il sistema aperto il tuo file, potrebbe essere necessario aggiungere la seguente riga di codice:

    intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);

Si prega di fare riferimento, il codice completo e la soluzione sono stati spiegati qui.


62
Ho solo bisogno di aggiungere intent.setFlags (Intent.FLAG_GRANT_READ_URI_PERMISSION);
alorma,

24
Funzionerà con tutte le versioni di Android o solo da API 24?
sviluppatore Android


11
@rockhammer Ho appena provato questo con Android 5.0, 6.0, 7.1 e 8.1, funziona in tutti i casi. Quindi la (Build.VERSION.SDK_INT > M)condizione è inutile.
Sébastien,

66
FileProviderdovrebbe essere esteso solo se si desidera sovrascrivere uno dei comportamenti predefiniti, altrimenti utilizzare android:name="android.support.v4.content.FileProvider". Vedi developer.android.com/reference/android/support/v4/content/…
JP Ventura,

313

Oltre alla soluzione che utilizza il FileProvider, esiste un altro modo per aggirare questo. In poche parole

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

in Application.onCreate(). In questo modo la VM ignora l' URIesposizione dei file .

Metodo

builder.detectFileUriExposure()

abilita il controllo dell'esposizione dei file, che è anche il comportamento predefinito se non configuriamo un VmPolicy.

Ho riscontrato un problema che, se utilizzo a content:// URIper inviare qualcosa, alcune app non riescono a capirlo. E il downgrade della target SDKversione non è consentito. In questo caso la mia soluzione è utile.

Aggiornare:

Come menzionato nel commento, StrictMode è uno strumento diagnostico e non dovrebbe essere utilizzato per questo problema. Quando ho pubblicato questa risposta un anno fa, molte app possono ricevere solo file uris. Si arrestano in modo anomalo quando provo a inviare loro un uri FileProvider. Questo problema è stato risolto nella maggior parte delle app ora, quindi dovremmo andare con la soluzione FileProvider.


1
@LaurynasG Dalle API 18 a 23, Android non controlla per impostazione predefinita l'esposizione dei file uri. La chiamata a questo metodo abilita questo controllo. Da API 24, Android esegue questo controllo per impostazione predefinita. Ma possiamo disabilitarlo impostando un nuovo VmPolicy.
hqzxzwb,

C'è qualche altro passo necessario affinché questo funzioni? Non funziona come sta per la mia Moto G con Android 7.0.
CKP78,

3
Tuttavia, come risolvere questo problema, StrictMode è uno strumento diagnostico che dovrebbe essere abilitato in modalità sviluppatore e non in modalità di rilascio ???
Imene Noomene,

1
@ImeneNoomene In realtà stiamo disabilitando StrictMode qui. Sembra ragionevole che StrictMode non debba essere abilitato in modalità di rilascio, ma in realtà Android abilita alcune opzioni di StrictMode per impostazione predefinita, indipendentemente dalla modalità di debug o dalla modalità di rilascio. Ma in un modo o nell'altro, questa risposta doveva essere solo una soluzione alternativa quando alcune app di destinazione non erano preparate per la ricezione di uris di contenuto. Ora che la maggior parte delle app ha aggiunto il supporto per l'uris del contenuto, dovremmo usare il modello FileProvider.
hqzxzwb,

3
@ImeneNoomene Sono totalmente con te nel tuo oltraggio. Hai ragione, questo è uno strumento diagnostico, o almeno era secoli fa quando l'ho aggiunto ai miei progetti. Questo è super frustrante! StrictMode.enableDefaults();, che eseguo solo sulle build di sviluppo, impedisce che questo arresto si verifichi, quindi ora ho un'applicazione di produzione che si arresta in modo anomalo ma non si arresta durante lo sviluppo. Quindi, fondamentalmente, abilitare uno strumento diagnostico qui nasconde un problema serio. Grazie @hqzxzwb per avermi aiutato a demistificare questo.
Jon

174

Se targetSdkVersionè maggiore di 24 , FileProvider viene utilizzato per concedere l'accesso.

Creare un file xml (Path: 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>


Aggiungi un provider in 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>

Se stai usando androidx , il percorso di FileProvider dovrebbe essere:

 android:name="androidx.core.content.FileProvider"

e sostituisci

Uri uri = Uri.fromFile(fileImagePath);

per

Uri uri = FileProvider.getUriForFile(MainActivity.this, BuildConfig.APPLICATION_ID + ".provider",fileImagePath);

Modifica: mentre includi l'URI con una Intentassicurati di aggiungere la riga sotto:

intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);

e sei a posto. Spero che sia d'aiuto.


2
@MaksimKniazev Puoi descrivere brevemente il tuo errore? In modo che io possa aiutarti.
Pankaj Lilan,

1
@PankajLilan, ho fatto esattamente quello che hai detto. Ma ogni volta che apro il mio pdf nell'altra applicazione appare vuoto (il suo salvataggio è corretto). Devo modificare il file XML? Ho già aggiunto anche FLAG_GRANT_READ_URI_PERMISSION;
Felipe Castilhos,

1
Errore mio, stavo aggiungendo il permesso all'intento sbagliato. Questa è la risposta giusta migliore e più semplice. Grazie!
Felipe Castilhos,

2
Genera un'eccezione java.lang.IllegalArgumentException: impossibile trovare la root configurata che contiene / Il mio percorso del file è /storage/emulated/0/GSMManage_DCIM/Documents/Device7298file_74.pdf. puoi per favore aiutare?
Jagdish Bhavsar

1
Non so perché, ma ho dovuto aggiungere entrambe le autorizzazioni READ e WRITE: target.addFlags (Intent.FLAG_GRANT_READ_URI_PERMISSION) target.addFlags (Intent.FLAG_GRANT_WRITE_URI_PERMISSION)
casella

160

Se la tua app è indirizzata all'API 24+ e desideri / hai ancora bisogno di utilizzare intenti file: //, puoi utilizzare il modo hacky per disabilitare il controllo di runtime:

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

Il metodo StrictMode.disableDeathOnFileUriExposureè nascosto e documentato come:

/**
* Used by lame internal apps that haven't done the hard work to get
* themselves off file:// Uris yet.
*/

Il problema è che la mia app non è zoppa, ma piuttosto non vuole essere paralizzata usando content: // intenti che non sono compresi da molte app là fuori. Ad esempio, l'apertura di un file mp3 con contenuto: // schema offre molte meno app rispetto all'apertura dello stesso su file: // schema. Non voglio pagare per i difetti di progettazione di Google limitando la funzionalità della mia app.

Google vuole che gli sviluppatori utilizzino lo schema dei contenuti, ma il sistema non è preparato per questo, per anni le app sono state create per utilizzare File non "contenuti", i file possono essere modificati e salvati, mentre i file pubblicati sullo schema dei contenuti non possono essere (può essi?).


3
"mentre i file pubblicati sullo schema di contenuto non possono essere (vero?)." - certo, se hai accesso in scrittura al contenuto. ContentResolverha entrambi openInputStream()e openOutputStream(). Un modo meno complicato per farlo è semplicemente configurare le regole della macchina virtuale e non abilitare la file Uriregola.
Commons War il

1
Esattamente. È un duro lavoro quando hai creato l'intera app, quindi scoprilo dopo aver scelto come target 25 interruzioni di tutti i metodi della fotocamera. Questo funziona per me fino a quando non ho tempo per farlo nel modo giusto.
Matt W,

5
Funziona su Android 7. Grazie
Anton Kizema

4
Funziona anche su Android 8, testato su Huawei Nexus 6P.
Gonzalo Ledezma Torres,

4
Confermo che funziona sulla produzione (ho più di 500.000 utenti), attualmente la versione 8.1 è la versione più alta e funziona su di essa.
Eli,

90

Se hai targetSdkVersion24 anni o più, non puoi utilizzare i file: Urivalori Intentssui dispositivi Android 7.0+ .

Le tue scelte sono:

  1. Rilascia il numero targetSdkVersiona 23 o inferiore, oppure

  2. Metti i tuoi contenuti nella memoria interna, quindi usaliFileProvider per renderli disponibili in modo selettivo ad altre app

Per esempio:

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

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

(da questo progetto di esempio )


Grazie per la risposta. Cosa succede quando lo uso con i file sulla /systempartizione? Ogni app dovrebbe essere in grado di accedere a questa partizione senza root.
Thomas Vos,

2
@SuperThomasLab: non vorrei contare su tutto per /systemessere leggibile dal mondo. Detto questo, la mia ipotesi è che otterrai ancora questa eccezione. Sospetto che stiano solo controllando lo schema e non stiano cercando di determinare se il file è veramente leggibile dal mondo. Tuttavia, FileProvidernon ti aiuterà, poiché non puoi insegnare a servirti /system. Potresti creare una strategia personalizzata per il mioStreamProvider , o crearne una tua ContentProviderper superare il problema.
Commons War

Sto ancora pensando a come risolverlo. L'app che sto aggiornando con il supporto di Android N è un browser di root. Ma ora non è più possibile aprire alcun file nelle directory principali. ( /data, /system), a causa di questo "buon cambiamento".
Thomas Vos,

1
quali sono gli svantaggi più importanti nel far cadere targetSdkVersion su 23? thnx
rommex,

2
@Rommex: non so cosa si qualifichi come "più importante". Ad esempio, agli utenti che lavorano in modalità schermo diviso o su dispositivi multi-finestra a forma libera (Chromebook, Samsung DeX) verrà comunicato che l'app potrebbe non funzionare con più finestre. Se questo è importante o no dipende da te.
CommonsWare,

47

Per prima cosa devi aggiungere un provider al tuo AndroidManifest

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

ora crea un file nella cartella delle risorse xml (se usi Android Studio puoi premere Alt + Invio dopo aver evidenziato i percorsi_file e selezionare l'opzione Crea risorsa xml)

Successivamente, immettere il file file_paths

<?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>

Questo esempio è per percorso esterno che puoi consultare qui per ulteriori opzioni. Questo ti permetterà di condividere i file che si trovano in quella cartella e nella sua sottocartella.

Ora non resta che creare l'intento come segue:

    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 : ho aggiunto la cartella principale della scheda SD nei file_paths. Ho testato questo codice e funziona.


1
Grazie per questo. Voglio anche farti sapere che esiste un modo migliore per ottenere l'estensione del file. String extension = android.webkit.MimeTypeMap.getFileExtensionFromUrl(Uri.fromFile(file).toString()); Inoltre, consiglio a tutti coloro che cercano risposte di leggere prima FileProvider e di capire di cosa si tratta qui con le autorizzazioni per i file in Android N e versioni successive. Esistono opzioni per la memoria interna rispetto alla memoria esterna e anche per i normali percorsi di file e percorsi di cache.
praneetloke,

2
Stavo ottenendo la seguente eccezione: java.lang.IllegalArgumentException: Failed to find configured root ...e l'unica cosa che funzionava era <files-path path="." name="files_root" />sul file xml anziché <external-path .... Il mio file è stato salvato nella memoria interna.
steliosf,

26

La risposta di @palash k è corretta e ha funzionato per i file di memoria interna, ma nel mio caso voglio aprire anche i file da memoria esterna, la mia app si è arrestata in modo anomalo quando il file aperto da memoria esterna come sdcard e usb, ma riesco a risolvere il problema modificando provider_paths.xml dalla risposta accettata

cambia provider_paths.xml come di seguito

<?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>

e in classe java (nessuna modifica come risposta accettata solo una piccola modifica)

Uri uri=FileProvider.getUriForFile(getActivity(), BuildConfig.APPLICATION_ID+".provider", File)

Questo mi aiuta a risolvere il crash dei file da archivi esterni, spero che questo possa aiutare qualcuno che ha lo stesso problema del mio :)


1
Dove hai trovato per <root-pathfavore? Sta funzionando. <external-path path="Android/data/${applicationId}/" name="files_root" />non ha avuto alcun effetto per i file aperti da memoria esterna.
t0m

lo trovo da vari risultati di ricerca, fammi controllare di nuovo e tornare a te al più presto
Ramz

anche la memoria esterna di cui parli è la scheda SD o la memoria integrata?
Ramz,

Ci scusiamo per l'imprecisione. Intendevo Android/data/${applicationId}/in SDcard.
t0m

1
È necessario aggiungere questo all'intento: intent.addFlags (Intent.FLAG_GRANT_READ_URI_PERMISSION);
s-hunter,

26

La mia soluzione era "Uri.parse" il percorso del file come stringa, invece di utilizzare 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, doesn't work in Android 10.
}
Intent viewFile = new Intent(Intent.ACTION_VIEW);
viewFile.setDataAndType(uri, "text/plain");
startActivity(viewFile);

Sembra che fromFile () utilizzi un puntatore a file, che suppongo potrebbe essere insicuro quando gli indirizzi di memoria sono esposti a tutte le app. Ma un percorso di file String non fa mai male a nessuno, quindi funziona senza lanciare FileUriExposedException.

Testato sui livelli API da 9 a 27! Apre correttamente il file di testo per la modifica in un'altra app. Non richiede FileProvider, né la libreria di supporto Android.


Vorrei averlo visto prima. Non ho dimostrato che funzionasse per me, ma è molto meno ingombrante di FileProvider.
Dale,

Una nota sul perché questo funziona davvero: non è il puntatore a File che causa il problema, ma il fatto che l'eccezione si verifica solo se si dispone di un percorso con 'file: //', che viene automaticamente anteposto da fromFile, ma non con parse .
Xmister,

3
Ciò non fa eccezione, ma non può nemmeno inviare il file all'app correlata. Quindi, non ha funzionato per me.
Serdar Samancıoğlu,

1
Questo fallirà su Android 10 e versioni successive, poiché non puoi supporre che l'altra app abbia accesso alla memoria esterna tramite il filesystem.
CommonsWare

24

Basta incollare il codice seguente in attività onCreate ()

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

Ignorerà l'esposizione all'URI


1
questa è una delle soluzioni ma non quella standard. Stilare le persone che hanno declassato le risposte è sbagliato in quanto questo è anche un codice funzionante con la soluzione funzionante.
Saksham,

23

Basta incollare il codice qui sotto in attività onCreate().

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

Ignorerà l'esposizione all'URI.

Buona codifica :-)


1
Quali sono gli svantaggi di questo?
James F,

1
Questo fallirà su Android 10 e versioni successive, poiché non puoi supporre che l'altra app abbia accesso alla memoria esterna tramite il filesystem.
CommonsWare

18

L'uso di fileProvider è la strada da percorrere. Ma puoi usare questa semplice soluzione alternativa:

ATTENZIONE : verrà risolto nella prossima versione di Android - https://issuetracker.google.com/issues/37122890#comment4

sostituire:

startActivity(intent);

di

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

7
Google sceglierà presto una patch per contenere lo stesso controllo. Questa non è una soluzione.
Puntatore Null,

Questo funziona ma non funzionerà nelle future versioni di Android.
Diljeet

13

Ho usato la risposta di Palash di cui sopra, ma era in qualche modo incompleta, ho dovuto fornire un permesso come questo

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

Basta incollare il codice seguente in attività onCreate ()

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

Ignorerà l'esposizione all'URI


Questo, questo rimuoverà i criteri di modalità rigorosa. e ignorerà l'avviso di sicurezza. Non è una buona soluzione.
inspire_coding

Inoltre non funzionerà su Android 10 e versioni successive, poiché non puoi supporre che l'altra app abbia accesso alla memoria esterna tramite il filesystem.
CommonsWare

7

aggiungi queste due righe in onCreate

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

Condividi metodo

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

Questo fallirà su Android 10 e versioni successive, poiché non puoi supporre che l'altra app abbia accesso alla memoria esterna tramite il filesystem.
CommonsWare

7

Ecco la mia soluzione:

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>

nel mio frammento ho il seguente codice:

 Uri myPhotoFileUri = FileProvider.getUriForFile(getActivity(), getActivity().getApplicationContext().getPackageName() + ".provider", myPhotoFile);               
    intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
    intent.putExtra(MediaStore.EXTRA_OUTPUT, myPhotoFileUri);

È tutto ciò di cui hai bisogno.

Inoltre non è necessario creare

public class GenericFileProvider extends FileProvider {}

Ho testato su Android 5.0, 6.0 e Android 9.0 ed è successo.


Ho testato questa soluzione e funziona bene con una piccola modifica: intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK intent.putExtra (Intent.EXTRA_STREAM, myPhotoFileUri) intent.type = "image / png" startActivity (Intent.createChooser (intent, " Condividi immagine tramite ")) Funziona su Android 7 e 8.
inspire_coding il

4

Per scaricare pdf dal server, aggiungi il codice seguente nella tua classe di servizio. Spero che questo ti sia 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");
    }

E sì, non dimenticare di aggiungere permessi e provider nel tuo manifest.

<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
che cos'è @xml/provider_paths?
adityasnl,

1
@Heisenberg consultare Rahul Upadhyay post dal url: stackoverflow.com/questions/38555301/...
Bhoomika Chauhan

3

Non so perché, ho fatto tutto esattamente come Pkosta ( https://stackoverflow.com/a/38858040 ) ma continuavo a ricevere errori:

java.lang.SecurityException: Permission Denial: opening provider redacted from ProcessRecord{redacted} (redacted) that is not exported from uid redacted

Ho perso ore su questo problema. Il colpevole? Kotlin.

val playIntent = Intent(Intent.ACTION_VIEW, uri)
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)

intentstava effettivamente impostando getIntent().addFlagsinvece di operare sul mio playIntent appena dichiarato.


2

ho inserito questo metodo in modo che il percorso di imageuri ottenga facilmente contenuti.

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

Ci sono 3 passaggi principali qui come indicato di seguito

Passaggio 1: inserimento manifest

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

Passaggio 2: creare il file XML 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>

Passaggio 3: modifiche al codice

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

So che questa è una domanda piuttosto vecchia ma questa risposta è per i futuri spettatori. Quindi ho riscontrato un problema simile e dopo le ricerche, ho trovato un'alternativa a questo approccio.

Il tuo intento qui per esempio: per visualizzare la tua immagine dal tuo percorso in 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)

Funzione principale di seguito

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

Allo stesso modo, invece di un'immagine, puoi usare qualsiasi altro formato di file come pdf e nel mio caso, ha funzionato bene


0

Ho trascorso quasi un giorno a cercare di capire perché stavo ottenendo questa eccezione. Dopo molte lotte, questa configurazione ha funzionato perfettamente ( 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>

Intento stesso

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
}

Spiego l'intero processo qui .


-1

https://stackoverflow.com/a/38858040/395097 questa risposta è completa.

Questa risposta è per: hai già un'app con targeting inferiore a 24 e ora stai eseguendo l'upgrade a targetSDKVersion> = 24.

In Android N, viene modificato solo il file uri esposto all'app di terze parti. (Non come lo usavamo prima). Quindi cambia solo i luoghi in cui condividi il percorso con un'app di terze parti (Fotocamera nel mio caso)

Nella nostra app stavamo inviando uri all'app Fotocamera, in quella posizione ci aspettiamo che l'app Fotocamera memorizzi l'immagine catturata.

  1. Per Android N, generiamo un nuovo URL basato su Content: // uri che punta al file.
  2. Generiamo il solito percorso basato su File API per lo stesso (usando il metodo più vecchio).

Ora abbiamo 2 diversi uri per lo stesso file. Il numero 1 è condiviso con l'app Fotocamera. Se l'intento della fotocamera ha esito positivo, possiamo accedere all'immagine dal numero 2.

Spero che sia di aiuto.


1
Stai facendo riferimento a una risposta già pubblicata qui, se devi completarla, commenta nella risposta per favore.
IgniteCoders

1
@IgniteCoders Come ho chiaramente indicato nel messaggio, la mia risposta copre il caso d'uso correlato.
Aram,

-1

Xamarin.Android

Nota: il percorso xml / provider_paths.xml (.axml) non può essere risolto, anche dopo aver creato la cartella xml in Risorse (forse può essere inserita in una posizione esistente come Valori , non ho provato), quindi ho fatto ricorso a questo che funziona per ora. I test hanno dimostrato che deve essere chiamato solo una volta per ogni esecuzione dell'applicazione (il che ha senso in quanto modifica lo stato operativo della VM host).

Nota: xml deve essere in maiuscolo, quindi Risorse / 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

La risposta di @Pkosta è un modo per farlo.

Oltre all'utilizzo FileProvider, puoi anche inserire il file in MediaStore(specialmente per file di immagini e video), poiché i file in MediaStore sono accessibili a tutte le app:

MediaStore si rivolge principalmente ai tipi MIME di video, audio e immagini, tuttavia a partire da Android 3.0 (livello API 11) può anche memorizzare tipi non multimediali (vedere File MediaStore.File per ulteriori informazioni). I file possono essere inseriti nel MediaStore usando scanFile () dopo di che un Uri di tipo: // style adatto alla condivisione viene passato al callback onScanCompleted () fornito. Nota che una volta aggiunto al sistema MediaStore il contenuto è accessibile a qualsiasi app sul dispositivo.

Ad esempio, è possibile inserire un file video in MediaStore in questo modo:

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

contentUriè come content://media/external/video/media/183473, che può essere passato direttamente a Intent.putExtra:

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

Questo funziona per me e salva le seccature dell'uso FileProvider.


-1

Lascia che ignori l'esposizione URI ... Aggiungilo dopo aver creato

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

Questo fallirà su Android 10 e versioni successive, poiché non puoi supporre che l'altra app abbia accesso alla memoria esterna tramite il filesystem.
CommonsWare

Questo non deve essere utilizzato in un'app di produzione.
Jorgesys,

-1

Prova questa soluzione

METTERE QUESTE AUTORIZZAZIONI AL MANIFESTO

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

INTENSO PER CATTURARE IMMAGINE

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

OTTIENI IMMAGINI CATTURATE IN RISULTATO ONATTIVITÀ

@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
                }
            } 

METODO PER OTTENERE L'URI DI IMMAGINE

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

qualcuno può dirmi perché votare in basso. questa è una soluzione funzionante al 100%.
Abdul Basit Rishi,

Questo ti dà solo la miniatura, non il quadro completo.
Build3r

-2

Nel mio caso mi sono sbarazzato dell'eccezione sostituendolo SetDataAndTypecon just SetData.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.