android.os.FileUriExposedException: file: ///storage/emulated/0/test.txt udostępniony poza aplikacją poprzez Intent.getData ()

738

Aplikacja ulega awarii, gdy próbuję otworzyć plik. Działa poniżej Androida Nougat, ale na Androidzie Nougat ulega awarii. Występuje awaria tylko wtedy, gdy próbuję otworzyć plik z karty SD, a nie z partycji systemowej. Masz 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: file: ///storage/emulated/0/test.txt udostępniony poza aplikacją poprzez Intent.getData ()

Edytować:

Podczas kierowania na Android Nougat file://identyfikatory URI nie są już dozwolone. content://Zamiast tego powinniśmy używać identyfikatorów URI. Jednak moja aplikacja musi otwierać pliki w katalogach głównych. Jakieś pomysły?

Thomas Vos
źródło
20
Wydaje mi się, że był to błąd, który sprawia, że ​​życie jest niepotrzebnie trudne dla twórców aplikacji. Konieczność dołączenia „FileProvider” i „uprawnień” do każdej aplikacji wydaje się przypominać płytę Enterprisey. Konieczność dodania flagi do każdej intencji pliku wydaje się niewygodna i być może niepotrzebna. Złamanie eleganckiej koncepcji „ścieżek” jest nieprzyjemne. A jaka jest korzyść? Selektywnie udzielając dostępu do pamięci dla aplikacji (podczas gdy większość aplikacji ma pełny dostęp do sdcard, szczególnie te, które działają na plikach)?
nyanpasu64
2
spróbuj tego, małego i idealnego kodu stackoverflow.com/a/52695444/4997704
Binesh Kumar

Odpowiedzi:

1316

Jeśli tak targetSdkVersion >= 24, musimy użyć FileProviderklasy, aby dać dostęp do określonego pliku lub folderu, aby były dostępne dla innych aplikacji. Tworzymy własne dziedziczenie klas FileProvider, aby nasz FileProvider nie powodował konfliktu z FileProvider zadeklarowanymi w importowanych zależnościach, jak opisano tutaj .

Kroki zastępowania file://identyfikatora content://URI identyfikatorem URI:

  • Dodaj rozszerzenie klasy FileProvider

    public class GenericFileProvider extends FileProvider {}
  • Dodaj <provider>tag FileProvider AndroidManifest.xmlpod <application>tagiem. Podaj unikatowe uprawnienia do android:authoritiesatrybutu, aby uniknąć konfliktów, można określić importowane zależności ${applicationId}.provideri inne często używane uprawnienia.

<?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>
  • Następnie utwórz provider_paths.xmlplik w res/xmlfolderze. Folder może być potrzebny do utworzenia, jeśli nie istnieje. Zawartość pliku pokazano poniżej. Opisuje, że chcielibyśmy udostępnić dostęp do pamięci zewnętrznej w folderze głównym (path=".")o nazwie pliki_zewnętrzne .
<?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() + ".provider", createImageFile());
  • Edycja: jeśli używasz zamiaru, aby system otworzył plik, może być konieczne dodanie następującego wiersza kodu:

    intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);

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

Pkosta
źródło
62
Potrzebowałem tylko dodać intent.setFlags (Intent.FLAG_GRANT_READ_URI_PERMISSION);
alorma
24
Czy będzie działać dla wszystkich wersji Androida, czy tylko z API 24?
programista Androida
9
ten artykuł pomógł mi medium.com/@ali.muzaffar/...
AbdulMomen عبدالمؤمن
11
@rockhammer Właśnie przetestowałem to z Androidem 5.0, 6.0, 7.1 i 8.1, działa we wszystkich przypadkach. Więc (Build.VERSION.SDK_INT > M)warunek jest bezużyteczny.
Sébastien
66
FileProvidernależy rozszerzyć tylko wtedy, gdy chcesz zastąpić domyślne zachowanie, w przeciwnym razie użyj android:name="android.support.v4.content.FileProvider". Zobacz developer.android.com/reference/android/support/v4/content/…
JP Ventura
313

Oprócz rozwiązania wykorzystującego FileProvider, istnieje inny sposób obejścia tego. Po prostu

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

w Application.onCreate(). W ten sposób maszyna wirtualna ignoruje URIekspozycję pliku .

metoda

builder.detectFileUriExposure()

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

Napotkałem problem polegający na tym, że jeśli używam content:// URIdo przesłania czegoś, niektóre aplikacje po prostu go nie rozumieją. Obniżenie target SDKwersji nie jest dozwolone. W takim przypadku moje rozwiązanie jest przydatne.

Aktualizacja:

Jak wspomniano w komentarzu, StrictMode jest narzędziem diagnostycznym i nie powinno być używane w przypadku tego problemu. Kiedy opublikowałem tę odpowiedź rok temu, wiele aplikacji może odbierać tylko pliki uris. Po prostu zawieszają się, gdy próbuję wysłać im identyfikator URI FileProvider. Zostało to już naprawione w większości aplikacji, dlatego powinniśmy przejść do rozwiązania FileProvider.

hqzxzwb
źródło
1
@LaurynasG Od API 18 do 23, Android domyślnie nie sprawdza ekspozycji pliku URI. Wywołanie tej metody umożliwia tę kontrolę. Od API 24, Android domyślnie wykonuje to sprawdzenie. Ale możemy to wyłączyć, ustawiając nowy VmPolicy.
hqzxzwb
Czy jest jakiś inny krok, aby to zadziałało? Nie działa tak jak w przypadku mojej Moto G z systemem Android 7.0.
CKP78,
3
Jak to jednak rozwiązać, StrictMode to narzędzia diagnostyczne, które powinny być włączone w trybie programisty, a nie w trybie zwolnienia ???
Imene Noomene
1
@ImeneNoomene Właściwie wyłączamy tutaj StrictMode. Wydaje się uzasadnione, że StrictMode nie powinien być włączony w trybie wydania, ale w rzeczywistości Android domyślnie włącza niektóre opcje StrictMode, niezależnie od trybu debugowania lub trybu wydania. Ale w ten czy inny sposób odpowiedź ta miała być obejściem wstecz, gdy niektóre aplikacje docelowe nie były przygotowane do odbioru treści z uris. Teraz, gdy większość aplikacji dodała obsługę uris treści, powinniśmy użyć wzorca FileProvider.
hqzxzwb
3
@ImeneNoomene Jestem całkowicie z tobą w oburzeniu. Masz rację, to narzędzie diagnostyczne, a przynajmniej przed wiekami, kiedy dodałem je do moich projektów. To jest bardzo frustrujące! StrictMode.enableDefaults();, który uruchamiam tylko na moich kompilacjach programistycznych, nie dochodzi do awarii - więc mam teraz aplikację produkcyjną, która ulega awarii, ale nie ulega awarii podczas programowania. Zasadniczo włączenie narzędzia diagnostycznego ukrywa tutaj poważny problem. Dzięki @hqzxzwb za pomoc w wyjaśnieniu tego.
Jon
174

Jeśli targetSdkVersionjest wyższa niż 24 , wówczas do udzielenia dostępu służy FileProvider .

Utwórz plik xml (ścieżka: res \ xml) ścieżka_ dostawcy. 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 dostawcę 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>

Jeśli używasz Androidax , ścieżka FileProvider powinna być:

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

i zamień

Uri uri = Uri.fromFile(fileImagePath);

do

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

Edycja: Podczas Intentdołączania identyfikatora URI pamiętaj, aby dodać poniższy wiersz:

intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);

i jesteś gotowy iść. Mam nadzieję, że to pomoże.

Pankaj Lilan
źródło
2
@MaksimKniazev Czy możesz krótko opisać swój błąd? Abym mógł ci pomóc.
Pankaj Lilan
1
@PankajLilan, zrobiłem dokładnie to, co powiedziałeś. Ale za każdym razem, gdy otwieram mój plik pdf w innej aplikacji, wydaje się on pusty (jego prawidłowe zapisywanie). Czy powinienem edytować plik XML? Dodałem już również FLAG_GRANT_READ_URI_PERMISSION;
Felipe Castilhos
1
Mój błąd, dodałem pozwolenie do złych zamiarów. To najlepsza i najprostsza właściwa odpowiedź. Dziękuję Ci!
Felipe Castilhos
2
Zgłasza wyjątek java.lang.IllegalArgumentException: Nie można znaleźć skonfigurowanego katalogu głównego zawierającego / My ścieżka do pliku to /storage/emulated/0/GSMManage_DCIM/Documents/Device7298file_74.pdf. czy możesz mi pomóc
Jagdish Bhavsar
1
Nie wiem dlaczego, ale musiałem dodać zarówno uprawnienia DO CZYTANIA, jak i PISANIA: target.addFlags (Intent.FLAG_GRANT_READ_URI_PERMISSION) target.addFlags (Intent.FLAG_GRANT_WRITE_URI_PERMISSION)
pole
160

Jeśli twoja aplikacja jest ukierunkowana na API 24+ i nadal chcesz / musisz użyć file: // intents, możesz użyć zhackowanego sposobu, aby wyłączyć sprawdzanie czasu wykonywania:

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

Metoda StrictMode.disableDeathOnFileUriExposurejest 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 kiepska, ale raczej nie chce zostać kaleką za pomocą content: // intencji, które nie są zrozumiałe dla wielu aplikacji. Na przykład otwarcie pliku mp3 ze schematem content: // oferuje znacznie mniej aplikacji niż przy otwieraniu tego samego schematu over file: //. Nie chcę płacić za błędy projektowe Google, ograniczając funkcjonalność mojej aplikacji.

Google chce, aby programiści korzystali ze schematu treści, ale system nie jest na to przygotowany, przez lata aplikacje były tworzone z wykorzystaniem plików, które nie są „treściami”, pliki można edytować i zapisywać z powrotem, a plików obsługiwanych przez schemat treści nie można (można one?).

Wskaźnik Null
źródło
3
„podczas gdy pliki obsługiwane w ramach schematu treści nie mogą być (czy mogą?)”. - jasne, jeśli masz dostęp do zapisu do treści. ContentResolverma zarówno openInputStream()i openOutputStream(). Mniej hackującym sposobem jest po prostu samodzielne skonfigurowanie reguł maszyn wirtualnych i nie włączanie file Urireguły.
CommonsWare,
1
Dokładnie. To ciężka praca, gdy zbudujesz całą aplikację, a potem dowiesz się po nakierowaniu na 25 wszystkich metod aparatu. Działa to dla mnie, dopóki nie zdążę zrobić tego we właściwy sposób.
Matt W
5
Działa na Androidzie 7. Dzięki
Anton Kizema
4
Działa również na Androidzie 8, testowany na Huawei Nexus 6P.
Gonzalo Ledezma Torres
4
Potwierdzam, że działa na produkcji (mam ponad 500 000 użytkowników), obecnie wersja 8.1 jest najwyższą wersją i działa na niej.
Eli
90

Jeśli masz targetSdkVersion24 lata lub więcej, nie możesz używać file: Uriwartości Intentsna urządzeniach z Androidem 7.0+ .

Do wyboru są:

  1. Upuść swój targetSdkVersiondo 23 lub mniej, lub

  2. Umieść swoje treści w pamięci wewnętrznej, a następnie użyj,FileProvider aby je selektywnie udostępnić 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 )

CommonsWare
źródło
Dziękuję za odpowiedź. Co się stanie, gdy użyję tego z plikami na /systempartycji? Każda aplikacja powinna mieć dostęp do tej partycji bez rootowania.
Thomas Vos
2
@SuperThomasLab: Nie liczyłbym na wszystko /system będzie czytelne dla świata. Biorąc to pod uwagę, domyślam się, że nadal będziesz otrzymywać ten wyjątek. Podejrzewam, że sprawdzają tylko schemat i nie próbują ustalić, czy plik jest naprawdę czytelny dla całego świata. Jednak ci FileProviderto nie pomoże, ponieważ nie możesz nauczyć go służyć /system. Możesz stworzyć niestandardową strategię dla mnieStreamProvider lub rzucić własną ContentProvider, aby obejść problem.
CommonsWare,
Nadal zastanawiam się, jak to rozwiązać. Aplikacja, którą aktualizuję z obsługą Androida N, jest przeglądarką root. Ale teraz nie możesz już otwierać żadnych plików w katalogach głównych. ( /data, /system), z powodu tej „dobrej zmiany”.
Thomas Vos,
1
jakie są najważniejsze wady upuszczania targetSdkVersion na 23? thnx
rommex
2
@rommex: Nie wiem, co kwalifikuje się jako „najważniejsze”. Na przykład użytkownicy pracujący w trybie podzielonego ekranu lub na dowolnych urządzeniach z wieloma oknami (Chromebooki, Samsung DeX) zostaną poinformowani, że Twoja aplikacja może nie działać z wieloma oknami. To, czy jest to ważne, czy nie, zależy od Ciebie.
CommonsWare
47

Najpierw musisz dodać dostawcę do swojego 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żki_pliku i wybrać opcję utworzenia zasobu 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 ścieżki zewnętrznej, do której możesz się odwoływać tutaj więcej opcji. Umożliwi to udostępnianie plików znajdujących się w tym folderze i jego podfolderze.

Teraz pozostało tylko stworzyć zamiar 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();
    }

EDYTOWAĆ : Dodałem folder główny karty SD w ścieżkach do pliku. Przetestowałem ten kod i działa.

Karn Patel
źródło
1
Dziękuję Ci za to. Chcę również poinformować, że istnieje lepszy sposób na uzyskanie rozszerzenia pliku. String extension = android.webkit.MimeTypeMap.getFileExtensionFromUrl(Uri.fromFile(file).toString()); Polecam również każdemu, kto szuka odpowiedzi, aby najpierw przeczytać przez FileProvider i zrozumieć, co masz do czynienia z uprawnieniami do plików w systemie Android N i nowszym. Dostępne są opcje pamięci wewnętrznej vs. pamięci zewnętrznej, a także zwykłej ścieżki plików vs. ścieżek pamięci podręcznej.
praneetloke
2
Otrzymałem następujący wyjątek: java.lang.IllegalArgumentException: Failed to find configured root ...jedyną rzeczą, która działała, był <files-path path="." name="files_root" />plik xml zamiast <external-path .... Mój plik został zapisany w pamięci wewnętrznej.
steliosf
26

@palash k odpowiedź jest poprawna i działała w przypadku plików pamięci wewnętrznej, ale w moim przypadku chcę również otwierać pliki z pamięci zewnętrznej, moja aplikacja uległa awarii podczas otwierania pliku z pamięci zewnętrznej, takiej jak sdcard i usb, ale udało mi się rozwiązać problem, modyfikując dostawca_paths.xml z zaakceptowanej odpowiedzi

zmień ścieżkę dostawca.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 niewielka edycja)

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

Pomoże mi to naprawić awarię plików z zewnętrznych pamięci. Mam nadzieję, że pomoże to komuś, kto ma taki sam problem jak mój :)

Ramz
źródło
1
Gdzie znalazłeś <root-pathproszę? To działa. <external-path path="Android/data/${applicationId}/" name="files_root" />nie miało wpływu na otwarte pliki z pamięci zewnętrznej.
t0m
znajduję to w różnych wynikach wyszukiwania, pozwól mi sprawdzić ponownie i wrócić do ciebie jak najszybciej
Ramz
także pamięć zewnętrzna, o której wspominasz, to karta SD lub wbudowana pamięć?
Ramz
Przepraszamy za niedokładność. Miałem na myśli Android/data/${applicationId}/w karcie SD.
t0m
1
Musisz dodać to do intencji: intent.addFlags (Intent.FLAG_GRANT_READ_URI_PERMISSION);
s-hunter
26

Moim rozwiązaniem było użycie „Uri.parse” ścieżki pliku jako ciągu, zamiast używania 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);

Wydaje się, że fromFile () używa wskaźnika pliku, który, jak przypuszczam, może być niepewny, gdy adresy pamięci są widoczne dla wszystkich aplikacji. Ale Ciąg ścieżki do pliku nigdy nikomu nie zaszkodzi, więc działa bez zgłaszania wyjątku FileUriExposedException.

Testowane na poziomach API od 9 do 27! Pomyślnie otwiera plik tekstowy do edycji w innej aplikacji. W ogóle nie wymaga FileProvider ani biblioteki obsługi systemu Android.

CrazyJ36
źródło
Chciałbym zobaczyć to pierwszy. Nie udowodniłem, że to działa dla mnie, ale jest o wiele mniej kłopotliwe niż FileProvider.
Dale
Uwaga, dlaczego tak naprawdę działa: to nie wskaźnik pliku rozwiązuje problem, ale fakt, że wyjątek występuje tylko wtedy, gdy masz ścieżkę z „file: //”, która jest automatycznie dodawana z pliku, ale nie z analizą .
Xmister
3
Nie otrzymuje to wyjątku, ale nie może również wysłać pliku do powiązanej aplikacji. Więc nie działało dla mnie.
Serdar Samancıoğlu
1
Nie powiedzie się to w systemie Android 10 i nowszych, ponieważ nie można zakładać, że druga aplikacja ma dostęp do pamięci zewnętrznej za pośrednictwem systemu plików.
CommonsWare
24

Po prostu wklej poniższy kod w działaniu onCreate ()

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

Zignoruje ekspozycję URI

Kaushal Sachan
źródło
1
jest to jedno z rozwiązań, ale nie standardowe. Stil osoby, które zanegowały odpowiedzi, są w błędzie, ponieważ jest to również działający kod z działającym rozwiązaniem.
saksham
23

Po prostu wklej poniższy kod w aktywności onCreate().

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

Zignoruje ekspozycję URI.

Miłego kodowania :-)

Ripdaman Singh
źródło
1
Jakie są wady tego?
James F
1
Nie powiedzie się to w systemie Android 10 i nowszych, ponieważ nie można zakładać, że druga aplikacja ma dostęp do pamięci zewnętrznej za pośrednictwem systemu plików.
CommonsWare
18

Korzystanie z fileProvider jest właściwą drogą. Możesz jednak użyć tego prostego obejścia:

OSTRZEŻENIE : Zostanie to naprawione w następnej wersji Androida - https://issuetracker.google.com/issues/37122890#comment4

zastąpić:

startActivity(intent);

przez

startActivity(Intent.createChooser(intent, "Your title"));
Szymon
źródło
7
Wybieracz zostanie wkrótce załatany przez Google, aby zawierał ten sam czek. To nie jest rozwiązanie.
Wskaźnik Null
Ten działa, ale nie będzie działać w przyszłych wersjach Androida.
Diljeet,
13

Użyłem odpowiedzi Palasha podanej powyżej, ale była ona nieco niekompletna, musiałem udzielić takiego pozwolenia

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);
Max
źródło
11

Po prostu wklej poniższy kod w działaniu onCreate ()

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

Zignoruje ekspozycję URI

AnilS
źródło
Spowoduje to usunięcie zasad trybu ścisłego. i zignoruje ostrzeżenie dotyczące bezpieczeństwa. Nie jest to dobre rozwiązanie.
inspire_coding
Nie powiedzie się również w systemie Android 10 i nowszych, ponieważ nie można zakładać, że druga aplikacja ma dostęp do pamięci zewnętrznej za pośrednictwem systemu plików.
CommonsWare
7

dodaj tę linię w onCreate

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

Metoda udostępniania

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..."));
DEVSHK
źródło
Nie powiedzie się to w systemie Android 10 i nowszych, ponieważ nie można zakładać, że druga aplikacja ma dostęp do pamięci zewnętrznej za pośrednictwem systemu plików.
CommonsWare
7

Oto moje rozwiązanie:

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

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

w moim fragmencie mam następny kod:

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

To wszystko, czego potrzebujesz.

Również nie trzeba tworzyć

public class GenericFileProvider extends FileProvider {}

Testuję na Androidzie 5.0, 6.0 i Androidzie 9.0 i to się udało.

Alex
źródło
Przetestowałem to rozwiązanie i działa ono dobrze z niewielką zmianą: intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK intent.putExtra (Intent.EXTRA_STREAM, myPhotoFileUri) intent.type = "image / png" startActivity (Intent.createChooser (intent, " Udostępnij obraz przez „)) Działa fin na Androidzie 7 i 8.
inspire_coding
4

Aby pobrać pdf z serwera, dodaj poniższy kod w swojej klasie usług. Mam nadzieję, że jest to dla ciebie pomocne.

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 swoim 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>
Bhoomika Chauhan
źródło
1
co jest @xml/provider_paths?
adityasnl
1
@Heisenberg proszę kierować post Rahul Upadhyay z adresu URL: stackoverflow.com/questions/38555301/...
Bhoomika Chauhan
3

Nie wiem dlaczego, zrobiłem wszystko dokładnie tak samo jak Pkosta ( https://stackoverflow.com/a/38858040 ), ale ciągle pojawiał się 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)

intentfaktycznie ustawienie getIntent().addFlagszamiast działa na moim nowo zadeklarowanej playIntent.

nyanpasu64
źródło
2

Umieściłem tę metodę, aby ścieżka imageuri łatwo wchodziła w treść.

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);
}
Jitu Batiya
źródło
2
As of Android N, in order to work around this issue, you need to use the FileProvider API

Istnieją 3 główne kroki tutaj, jak wspomniano poniżej

Krok 1: Wpis manifestacyjny

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

Krok 2: Utwórz plik 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>

Krok 3: Zmiany kodu

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);
Suraj Bahadur
źródło
1

Wiem, że to dość stare pytanie, ale ta odpowiedź jest dla przyszłych widzów. Zetknąłem się z podobnym problemem i po przeprowadzeniu badań znalazłem alternatywę dla tego podejścia.

Twój zamiar tutaj na przykład: Aby wyświetlić obraz ze swojej ścieżki w 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)

Główna funkcja poniżej

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

Podobnie, zamiast obrazu, możesz użyć dowolnego innego formatu pliku, takiego jak pdf, aw moim przypadku działało dobrze


źródło
0

Spędziłem prawie dzień, próbując dowiedzieć się, dlaczego dostaję ten wyjątek. Po wielu zmaganiach ta konfiguracja działała idealnie ( 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>

ścieżka_pliku.xml

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

Sama intencja

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
}

Tutaj wyjaśniam cały proces .

Łomża
źródło
-1

https://stackoverflow.com/a/38858040/395097 ta odpowiedź jest kompletna.

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

W Androidzie N zmieniany jest tylko plik uri udostępniony aplikacji innej firmy. (Nie tak jak wcześniej go używaliśmy). Dlatego zmieniaj tylko miejsca, w których udostępniasz ścieżkę za pomocą aplikacji innej firmy (w moim przypadku Aparat)

W naszej aplikacji wysyłaliśmy uri do aplikacji Camera, w tym miejscu spodziewamy się, że aplikacja camera zapisze przechwycony obraz.

  1. Dla Androida N generujemy nowy adres URL oparty na treści: // uri wskazujący plik.
  2. Generujemy dla tego samego zwykłą ścieżkę opartą na interfejsie API plików (przy użyciu starszej metody).

Teraz mamy 2 różne URI dla tego samego pliku. # 1 jest współdzielony z aplikacją Camera. Jeśli zamierzeniem kamery jest sukces, możemy uzyskać dostęp do obrazu z punktu 2.

Mam nadzieję że to pomoże.

Aram
źródło
1
Odwołujesz się do już opublikowanej odpowiedzi, jeśli musisz ją wypełnić, skomentuj w odpowiedzi plz.
IgniteCoders
1
@IgniteCoders Jak wyraźnie wspomniałem w wiadomości, moja odpowiedź dotyczy odpowiedniego przypadku użycia.
Aram
-1

Xamarin.Android

Uwaga: Ścieżka xml / dostawca_paths.xml (.axml) nie mogła zostać rozwiązana, nawet po utworzeniu folderu xml w obszarze Zasoby (być może można go umieścić w istniejącej lokalizacji, takiej jak Wartości , nie próbowałem), więc uciekłem się to działa na teraz. Testy wykazały, że należy go wywoływać tylko raz na uruchomienie aplikacji (co ma sens, ponieważ zmienia stan operacyjny maszyny wirtualnej hosta).

Uwaga: xml musi 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);
Sam jest
źródło
-1

Odpowiedź @Pkosta jest jednym ze sposobów na zrobienie tego.

Oprócz używania FileProvidermożesz także wstawić plik MediaStore(szczególnie w przypadku plików obrazów i wideo), ponieważ pliki w MediaStore są dostępne dla każdej aplikacji:

MediaStore jest skierowany przede wszystkim do typów MIME wideo, audio i obrazów, jednak począwszy od Androida 3.0 (API poziom 11) może także przechowywać typy inne niż media (więcej informacji znajduje się w MediaStore.Files). Pliki można wstawić do MediaStore za pomocą scanFile (), po czym identyfikator Uri content: // odpowiedni do udostępniania jest przekazywany do dostarczonego wywołania zwrotnego onScanCompleted (). Pamiętaj, że po dodaniu do systemu MediaStore zawartość jest dostępna dla dowolnej aplikacji na urządzeniu.

Na przykład możesz wstawić plik wideo do MediaStore w następujący sposób:

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

contentUrijest jak content://media/external/video/media/183473, który można przekazać bezpośrednio do Intent.putExtra:

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

To działa dla mnie i oszczędza kłopotów z używaniem FileProvider.

NeoWang
źródło
-1

Po prostu pozwól mu zignorować ekspozycję URI ... Dodaj go po utworzeniu

StrictMode.VmPolicy.Builder builder = new StrictMode.VmPolicy.Builder(); StrictMode.setVmPolicy(builder.build()); 
Anurag Bhalekar
źródło
Nie powiedzie się to w systemie Android 10 i nowszych, ponieważ nie można zakładać, że druga aplikacja ma dostęp do pamięci zewnętrznej za pośrednictwem systemu plików.
CommonsWare
Nie należy tego używać w aplikacji produkcyjnej.
Jorgesys,
-1

Wypróbuj to rozwiązanie

PRZEKAZAJ TE ZEZWOLENIA NA MANIFEST

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

ZAMIERZANIE ZDJĘĆ

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

UZYSKAJ UCHWYT OBRAZU W WYNIKU NIEAKTYWNOŚCI

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

METODA UZYSKANIA URI OBRAZU

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);
    }
Abdul Basit Rishi
źródło
czy ktoś może mi powiedzieć, dlaczego głosować w dół? jest to w 100% działające rozwiązanie.
Abdul Basit Rishi
To daje tylko miniaturę, a nie pełny obraz.
Build3r
-2

W moim przypadku pozbyłem się wyjątku, zastępując SetDataAndTypego just SetData.

Thomiel
źródło