android.os.FileUriExposedException: file: / / / storage/emulated/0 / test.txt ujawnione poza app poprzez Intent.getData()

Aplikacja zawiesza się, gdy próbuję otworzyć plik. Działa pod Androidem Nougat, ale na Androidzie Nougat się zawiesza. To tylko zawiesza się, gdy próbuję otworzyć plik z karty SD, a nie z partycji systemowej. Jakiś problem z pozwoleniem?

Przykładowy kod:

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: plik:///storage/emulated/0/test.JPG / txt exposed beyond app through Intencja.getData ()

Edit:

Podczas celowania w Androida Nugat, file:// URI nie są już dozwolone. Zamiast tego powinniśmy użyć content:// Uri. Jednak moja aplikacja musi otwierać pliki w katalogach głównych. Jakieś pomysły?

Author: Thomas Vos, 2016-07-05

16 answers

Jeśli twoja targetSdkVersion >= 24, to musimy użyć klasy FileProvider, aby dać dostęp do określonego pliku lub folderu, aby uczynić je dostępnymi dla innych aplikacji. Tworzymy własną klasę dziedziczącą FileProvider, aby upewnić się, że nasz FileProvider nie koliduje z fileproviderami zadeklarowanymi w importowanych zależnościach, jak opisano tutaj .

Kroki do zastąpienia file:// URI przez content:// URI:

  • Dodaj klasę rozszerzającą FileProvider

    public class GenericFileProvider extends FileProvider {}
    
  • Dodaj tag FileProvider w AndroidManifest.xml pod tagiem. Określ unikalny atrybut android:authorities aby uniknąć konfliktów, zaimportowane zależności mogą określać ${applicationId}.provider i inne powszechnie używane atrybuty.

<?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}.my.package.name.provider"
            android:exported="false"
            android:grantUriPermissions="true">
            <meta-data
                android:name="android.support.FILE_PROVIDER_PATHS"
                android:resource="@xml/provider_paths"/>
        </provider>
    </application>
</manifest>
  • następnie utwórz plik provider_paths.xml w folderze res/xml. Folder może być potrzebny do utworzenia, jeśli nie istnieje. Zawartość pliku pokazana jest poniżej. Opisuje, że chcielibyśmy udostępnić dostęp do zewnętrznej pamięci w folderze głównym (path=".") o nazwie 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>
  • Ostatnim krokiem jest zmiana linii kodu poniżej w

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

    Do

    Uri photoURI = FileProvider.getUriForFile(context, context.getApplicationContext().getPackageName() + ".my.package.name.provider", createImageFile());
    
  • Edit: jeśli używasz intencji, aby system otworzył plik, może być konieczne dodanie następującej linii kodu:

    intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
    

Proszę odnieść się, Pełny kod i rozwiązanie zostało wyjaśnione tutaj.

 912
Author: Pkosta,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-01-29 13:04:51

Oprócz rozwiązania za pomocą FileProvider, istnieje inny sposób obejścia tego problemu. Po prostu

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

W Application.onCreate(). W ten sposób maszyna wirtualna ignoruje ekspozycję pliku URI.

Metoda

builder.detectFileUriExposure()

Włącza sprawdzanie ekspozycji pliku, co jest również domyślnym zachowaniem, jeśli nie ustawiamy VmPolicy.

Napotkałem problem, że jeśli używam content:// URI aby coś wysłać, niektóre aplikacje po prostu nie mogą tego zrozumieć. I obniżanie wersji target SDK jest niedozwolone. W w tym przypadku moje rozwiązanie jest przydatne.

Update:

Jak wspomniano w komentarzu, StrictMode jest narzędziem diagnostycznym i nie powinien być używany do tego problemu. Kiedy opublikowałem tę odpowiedź rok temu, wiele aplikacji może odbierać tylko Uri plików. Po prostu się rozbijają, gdy próbowałem wysłać do nich Uri Fileprovidera. Jest to naprawione w większości aplikacji teraz, więc powinniśmy przejść z rozwiązaniem FileProvider.

 235
Author: hqzxzwb,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-01-06 10:56:40

Jeśli Twoja aplikacja obsługuje API 24+, a nadal chcesz / musisz użyć file: / / intents, możesz użyć hacky way, aby wyłączyć sprawdzenie runtime:

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

Metoda StrictMode.disableDeathOnFileUriExposure jest ukryta i udokumentowana jako:

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

Problem polega na tym, że moja aplikacja nie jest lame, ale raczej nie chce być kaleką za pomocą treści: / / intencje, które nie są rozumiane przez wiele aplikacji tam. Na przykład, otwarcie pliku mp3 z content: / / scheme oferuje znacznie mniej aplikacji niż przy otwieraniu samego pliku: / / scheme. Nie chcę aby zapłacić za błędy projektowe Google poprzez ograniczenie funkcjonalności mojej aplikacji.

Google chce, aby Programiści korzystali z schematu zawartości, ale system nie jest na to przygotowany, przez lata aplikacje były tworzone do korzystania z Plików Nie "zawartości", pliki można edytować i zapisywać z powrotem, podczas gdy pliki serwowane nad schematem zawartości nie mogą być (prawda?).

 118
Author: Pointer Null,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-02-24 11:29:25

Jeśli twój targetSdkVersion jest 24 lub wyższy, nie możesz używać file: Uri wartości w Intents na urządzeniach z Androidem 7.0 + .

Twoje wybory to:

  1. Upuść targetSdkVersion do 23 lub niżej, lub

  2. Umieść zawartość w pamięci wewnętrznej, a następnie użyj FileProvider Aby udostępnić go selektywnie innym aplikacjom

Na przykład:

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

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

(z tego przykładowego projektu )

 77
Author: CommonsWare,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-07-05 12:42:59

Jeśli targetSdkVersion jest wyższa niż 24, Następnie FileProvider jest używany do przyznania dostępu.

Utwórz plik xml(ścieżka: 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>


Dodaj Provider w 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>

I zastąp

Uri uri = Uri.fromFile(fileImagePath);

Do

Uri uri = FileProvider.getUriForFile(MainActivity.this, BuildConfig.APPLICATION_ID + ".provider",fileImagePath);
I możesz iść. Mam nadzieję, że to pomoże.
 77
Author: Pankaj Lilan,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-11-01 05:13:34

Najpierw musisz dodać dostawcę do 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>

Teraz Utwórz plik w folderze zasobów XML (jeśli używasz android studio, możesz nacisnąć Alt + Enter po podświetleniu ścieżek plików i wybrać opcję Utwórz zasób XML)

Następnie w pliku file_paths wpisz

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

Ten przykład dotyczy zewnętrznej ścieżki, którą możesz referować tutaj , aby uzyskać więcej opcji. Pozwoli to na udostępnianie plików znajdujących się w tym folderze i jego podfolderze.

Teraz wszystko, co jest po lewej jest utworzenie intencji w następujący sposób:

    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 : dodałem folder główny karty sd w pliku_paths. Przetestowałem ten kod i działa.

 43
Author: Karn Patel,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-08-12 19:59:46

@palash K odpowiedź jest poprawna i działa na pliki pamięci wewnętrznej, ale w moim przypadku chcę otwierać pliki z pamięci zewnętrznej również moja aplikacja zawiesiła się podczas otwierania pliku z pamięci zewnętrznej, takiego jak sdcard i usb, ale udaje mi się rozwiązać problem modyfikując provider_paths.xml z zaakceptowanej odpowiedzi

Zmień provider_paths.xml Jak poniżej

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

I w klasie java (bez zmian jako zaakceptowana odpowiedź tylko mała edycja)

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

To pomoże mi naprawić awaria plików z zewnętrznych magazynów, mam nadzieję, że pomoże to komuś, kto ma ten sam problem co mój :)

 24
Author: Ramz,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-02-20 21:24:34

Używanie fileprovidera jest dobrym rozwiązaniem. Ale możesz użyć tego prostego obejścia:

WARNING: zostanie to naprawione w następnym wydaniu Androida - https://issuetracker.google.com/issues/37122890#comment4

Zastąpić:

startActivity(intent);

By

startActivity(Intent.createChooser(intent, "Your title"));
 17
Author: Simon,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-04-14 07:59:59

Użyłem powyższej odpowiedzi Palasha, ale była ona nieco niekompletna, musiałem udzielić takiej zgody

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
Author: Max,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-12-11 16:05:31

Po prostu wklej poniższy kod w activity onCreate ()

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

Zignoruje ekspozycję URI

 8
Author: Kaushal Sachan,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-01-16 06:50:25

Nie wiem dlaczego, zrobiłem wszystko dokładnie tak samo jak Pkosta ( https://stackoverflow.com/a/38858040 ) ale ciągle dostaję błąd:

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

Zmarnowałem godziny na ten temat. Sprawca? Kotlin.
val playIntent = Intent(Intent.ACTION_VIEW, uri)
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)

intent w rzeczywistości ustawiał getIntent().addFlags zamiast operować na moim nowo zadeklarowanym playIntent.

 2
Author: jimbo1qaz,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-09-17 12:59:37

Aby pobrać plik pdf z serwera, dodaj poniższy kod w swojej klasie usług. Mam nadzieję, że to ci pomoże.

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

I tak, nie zapomnij dodać uprawnień i dostawcy w manifeście.

<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
Author: Bhoomika Chauhan,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-07-28 12:23:57

Po prostu wklej poniższy kod w activity onCreate ()

StrictMode.VmPolicy.Builder builder = new StrictMode.VmPolicy.Builder (); StrictMode.setVmPolicy(konstruktor.zbuduj());

Zignoruje ekspozycję URI

Happy coding: -)

 0
Author: Ripdaman Singh,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-09-20 09:15:48

Https://stackoverflow.com/a/38858040/395097 Ta odpowiedź jest kompletna.

Ta odpowiedź jest dla - masz już aplikację, która była kierowana poniżej 24, a teraz uaktualniasz się do targetSDKVersion >= 24.

W Androidzie N zmieniany jest tylko uri pliku wystawionego na aplikację innej firmy. (Nie tak jak wcześniej). Więc zmień tylko miejsca, w których udostępniasz ścieżkę aplikacji 3rd party (aparat w moim przypadku)

W naszej aplikacji wysyłaliśmy uri do aplikacji Camera, w tym miejscu spodziewamy się, że aplikacja aparatu będzie przechowywać przechwycony obraz.

  1. dla Androida N generujemy nową zawartość: / / URL oparty na uri wskazujący na plik.
  2. generujemy zwykłą ścieżkę opartą na API plików dla tej samej (przy użyciu starszej metody).

Teraz mamy 2 różne uri dla tego samego pliku. #1 jest współdzielony z aplikacją Camera. Jeśli intencja aparatu się powiedzie, możemy uzyskać dostęp do obrazu z #2.

Mam nadzieję, że to pomoże.

 -1
Author: Aram,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-04-30 07:20:22

W moim przypadku pozbyłem się wyjątku, zastępując {[0] } tylko SetData.

 -1
Author: thomiel,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-08-29 00:45:19

Xamarin.Android

Uwaga: ścieżka xml / provider_paths.xml (.axml) nie dało się rozwiązać, nawet po stworzeniu folderu xmlpod Resources (może można go umieścić w istniejącym miejscu jak Values, nie próbowałem), więc uciekłem się do tego, co na razie działa. Testy wykazały, że musi być wywołana tylko raz na każde uruchomienie aplikacji (co ma sens, ponieważ zmienia stan operacyjny hosta maszyny wirtualnej).

Uwaga: xmlmusi być pisany wielkimi literami, więc 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
Author: samis,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-09-26 14:48:15