it-swarm.dev

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

L'app si arresta in modo anomalo quando sto tentando di aprire un file. Funziona sotto Android Nougat, ma su Android Nougat si blocca. Si blocca 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 Android Nougat, gli URI file:// non sono più consentiti. Dovremmo invece utilizzare gli URI content://. Tuttavia, la mia app deve aprire i file nelle directory principali. Qualche idea?

603
Thomas Vos

Se il tuo targetSdkVersion >= 24, allora dobbiamo usare la classe FileProvider per dare accesso a quel particolare file o cartella per renderli accessibili ad altre app. Creiamo la nostra classe ereditando FileProvider per assicurarci che il nostro FileProvider non sia in conflitto con FileProvider dichiarato nelle dipendenze importate come descritto in here .

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

  • Aggiungi una classe che estende FileProvider

    public class GenericFileProvider extends FileProvider {}
    
  • Aggiungi un tag FileProvider <provider> in AndroidManifest.xml sotto il tag <application>. Specificare un'autorità unica per l'attributo Android:authorities per evitare conflitti, le dipendenze importate potrebbero specificare ${applicationId}.provider e 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}.fileprovider"
            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 file provider_paths.xml nella cartella res/xml. Potrebbe essere necessaria la cartella da creare se non esiste. Il contenuto del file è mostrato sotto. Descrive che vorremmo condividere l'accesso allo Storage esterno 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());
    

    a

    Uri photoURI = FileProvider.getUriForFile(context, context.getApplicationContext().getPackageName() + ".my.package.name.provider", createImageFile());
    
  • Modifica: Se stai usando un intento per far aprire il tuo file, potresti dover aggiungere la seguente riga di codice:

    intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
    

Si prega di fare riferimento, codice completo e soluzione è stato spiegato qui.

1090
Pkosta

Oltre alla soluzione che utilizza FileProvider, esiste un altro modo per ovviare a questo problema. In poche parole

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

in Application.onCreate(). In questo modo il VM ignora l'esposizione del file URI.

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 uso un content://URI per inviare qualcosa, alcune app non riescono a capirlo. E il downgrade della versione target SDK non è permesso. In questo caso la mia soluzione è utile.

Aggiornare:

Come menzionato nel commento, StrictMode è uno strumento diagnostico e non dovrebbe essere usato per questo problema. Quando ho postato questa risposta un anno fa, molte app possono ricevere solo uris di file. Si bloccano appena quando ho provato a inviare un URL di FileProvider a loro. Questo è stato risolto nella maggior parte delle app ora, quindi dovremmo utilizzare la soluzione FileProvider.

274
hqzxzwb

Se la tua app ha come target l'API 24+ e desideri comunque/utilizzare il file: //intents, puoi utilizzare la modalità hacky per disabilitare il controllo del 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 che non vuole essere menomata usando contenuti: // intenti che non sono compresi da molte app là fuori. Ad esempio, l'apertura del file mp3 con contenuto: // schema offre un numero di app molto inferiore rispetto all'apertura dello stesso file: // schema. Non voglio pagare gli errori 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 sono state create app per usare File non "contenuti", i file possono essere modificati e salvati, mentre i file serviti su schema di contenuti non possono essere essi?).

142
Pointer Null

Se targetSdkVersion è superiore a 24 , quindi FileProvider viene utilizzato per concedere l'accesso.

Creare un file xml (percorso: 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 inAndroidManifest.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 FileProvider dovrebbe essere:

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

e replace

Uri uri = Uri.fromFile(fileImagePath);

a

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

Modifica: Mentre includi l'URI con un Intent assicurati di aggiungere la riga seguente:

intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);

e tu sei bello andare. Spero che sia d'aiuto.

120
Pankaj Lilan

Se targetSdkVersion è 24 o superiore, non puoi usare file:Uri valori in Intents su dispositivi Android 7.0+ .

Le tue scelte sono:

  1. Rilasciare targetSdkVersion a 23 o inferiore, o

  2. Metti i tuoi contenuti nella memoria interna, quindi usa FileProvider per renderlo disponibile 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 )

80
CommonsWare

Per prima cosa devi aggiungere un fornitore 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 file_paths e selezionare creare un'opzione risorsa xml)

Avanti nel file file_paths entra

<?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 refere qui per più opzioni. Questo ti permetterà di condividere 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 radice della scheda SD nei percorsi file. Ho testato questo codice e funziona.

44
Karn Patel

@palash k answer è corretto e ha funzionato per i file di archiviazione interni, ma nel mio caso voglio aprire i file da una memoria esterna, la mia app si è bloccata quando si apre il file da una memoria esterna come sdcard e usb, ma riesco a risolvere il problema modificando provider_paths.xml dalla risposta accettata

cambia provider_paths.xml like 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 di file da archivi esterni, spero che questo possa aiutare qualcuno che ha lo stesso problema del mio :)

25
Ramz

Basta incollare il codice qui sotto nell'attività onCreate ()

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

Ignorerà l'esposizione URI

20
Kaushal Sachan

Usare il fileProvider è la strada da percorrere. Ma puoi usare questa soluzione semplice:

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

sostituire:

startActivity(intent);

di

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

La mia soluzione era quella di 'Uri.parse' il percorso del file come String, invece di usare Uri.fromFile ().

String storage = Environment.getExternalStorageDirectory().toString() + "/test.txt";
File file = new File(storage);
Uri uri;
if (Build.VERSION.SDK_INT < 24) {
    uri = Uri.fromFile(file);
} else {
    uri = Uri.parse(file.getPath()); // My work-around for new SDKs, causes ActivityNotFoundException in API 10.
}
Intent viewFile = new Intent(Intent.ACTION_VIEW);
viewFile.setDataAndType(uri, "text/plain");
startActivity(viewFile);

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

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

18
CrazyJ36

Basta incollare il codice seguente nell'attività onCreate().

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

Ignorerà l'esposizione URI.

Felice codifica :-)

14
Ripdaman Singh

Ho usato la risposta di Palash di cui sopra ma era alquanto incompleta, dovevo 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);
12
Max

Per scaricare il pdf dal server, aggiungi sotto il codice nella tua classe di servizio. Spero che questo sia utile per te.

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>
4
Bhoomika Chauhan

Non so perché, ho fatto tutto esattamente come Pkosta ( https://stackoverflow.com/a/38858040 ) ma ho continuato 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)

intent stava in realtà impostando getIntent().addFlags invece di operare sul mio playIntent appena dichiarato.

3
jimbo1qaz

aggiungi questa linea in onCreate

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

Metodo di condivisione

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

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

Il tuo intento qui per es .: per vedere 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 il pdf e nel mio caso ha funzionato bene

1
S.Sho

ho messo questo metodo in modo che il percorso di immagine facilmente ottenere nel contenuto.

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);
}
0
Jitu Batiya