Dostęp do dokumentów i innych plików z pamięci współdzielonej

Na urządzeniach z Androidem 4.4 (poziom interfejsu API 19) lub nowszym aplikacja może wchodzić w interakcje z dostawcą dokumentów, w tym z zewnętrznymi woluminami pamięci masowej i pamięcią w chmurze, z wykorzystaniem platformy Storage Access Framework. Ta platforma umożliwia użytkownikom korzystanie z selektora systemowego, aby wybrać dostawcę dokumentów i wybrać określone dokumenty i inne pliki do utworzenia, otwarcia lub modyfikacji w aplikacji.

To użytkownik jest zaangażowany w wybór plików lub katalogów, do których ma dostęp Twoja aplikacja, dlatego ten mechanizm nie wymaga żadnych uprawnień systemowych, a także zapewnia lepszą kontrolę użytkownika i większą prywatność. Poza tym pliki te, które są przechowywane poza katalogiem danej aplikacji i poza sklepem z multimediami, pozostają na urządzeniu po odinstalowaniu aplikacji.

Korzystanie ze struktury wymaga wykonania następujących czynności:

  1. Aplikacja wywołuje intencję zawierającą działanie związane z pamięcią masową. Odpowiada to konkretnemu przypadkowi użycia udostępnionemu przez platformę.
  2. Użytkownik widzi selektor systemowy, który umożliwia przeglądanie dostawcy dokumentów i wybieranie lokalizacji lub dokumentu, w którym ma miejsce działanie związane z przechowywaniem danych.
  3. Aplikacja uzyskuje uprawnienia do zapisu i odczytu identyfikatora URI, który reprezentuje wybraną lokalizację lub dokument użytkownika. Dzięki niemu aplikacja może wykonywać operacje w wybranej lokalizacji.

Aby obsługiwać dostęp do plików multimedialnych na urządzeniach z Androidem 9 (poziom interfejsu API 28) lub niższym, zadeklaruj uprawnienie READ_EXTERNAL_STORAGE i ustaw w maxSdkVersion wartość 28.

W tym przewodniku opisujemy różne przypadki użycia platformy umożliwiające pracę z plikami i innymi dokumentami. Wyjaśniono też, jak wykonywać działania na wybranej przez użytkownika lokalizacji.

Przypadki użycia dostępu do dokumentów i innych plików

Platforma Storage Access Framework obsługuje te przypadki użycia dostępu do plików i innych dokumentów.

Tworzenie nowego pliku
Działanie intencji ACTION_CREATE_DOCUMENT pozwala użytkownikom zapisać plik w określonej lokalizacji.
Otwieranie dokumentu lub pliku
Zamiar ACTION_OPEN_DOCUMENT pozwala użytkownikom wybrać konkretny dokument lub plik do otwarcia.
Przyznawanie dostępu do zawartości katalogu
Zamiar ACTION_OPEN_DOCUMENT_TREE, który jest dostępny w Androidzie 5.0 (poziom interfejsu API 21) i nowszych, pozwala użytkownikom wybrać określony katalog, zapewniając aplikacji dostęp do wszystkich plików i podkatalogów w tym katalogu.

W sekcjach poniżej znajdziesz wskazówki dotyczące konfiguracji poszczególnych przypadków użycia.

Tworzenie nowego pliku

Użyj intencji ACTION_CREATE_DOCUMENT, aby wczytać selektor plików systemowych i umożliwić użytkownikowi wybór lokalizacji, w której zapisać zawartość pliku. Ten proces jest podobny do procesu używanego w oknach „zapisz jako” w innych systemach operacyjnych.

Uwaga: ACTION_CREATE_DOCUMENT nie może zastąpić istniejącego pliku. Jeśli aplikacja spróbuje zapisać plik o tej samej nazwie, na końcu nazwy pliku zostanie dodana liczba w nawiasie.

Jeśli na przykład aplikacja spróbuje zapisać plik o nazwie confirmation.pdf w katalogu, który ma już plik o tej nazwie, system zapisze nowy plik pod nazwą confirmation(1).pdf.

Konfigurując intencję, podaj nazwę pliku i typ MIME oraz opcjonalnie podaj identyfikator URI pliku lub katalogu, który selektor plików powinien wyświetlać przy pierwszym wczytywaniu za pomocą dodatkowej intencji EXTRA_INITIAL_URI.

Poniższy fragment kodu pokazuje, jak utworzyć i wywołać intencję utworzenia pliku:

Kotlin

// Request code for creating a PDF document.
const val CREATE_FILE = 1

private fun createFile(pickerInitialUri: Uri) {
    val intent = Intent(Intent.ACTION_CREATE_DOCUMENT).apply {
        addCategory(Intent.CATEGORY_OPENABLE)
        type = "application/pdf"
        putExtra(Intent.EXTRA_TITLE, "invoice.pdf")

        // Optionally, specify a URI for the directory that should be opened in
        // the system file picker before your app creates the document.
        putExtra(DocumentsContract.EXTRA_INITIAL_URI, pickerInitialUri)
    }
    startActivityForResult(intent, CREATE_FILE)
}

Java

// Request code for creating a PDF document.
private static final int CREATE_FILE = 1;

private void createFile(Uri pickerInitialUri) {
    Intent intent = new Intent(Intent.ACTION_CREATE_DOCUMENT);
    intent.addCategory(Intent.CATEGORY_OPENABLE);
    intent.setType("application/pdf");
    intent.putExtra(Intent.EXTRA_TITLE, "invoice.pdf");

    // Optionally, specify a URI for the directory that should be opened in
    // the system file picker when your app creates the document.
    intent.putExtra(DocumentsContract.EXTRA_INITIAL_URI, pickerInitialUri);

    startActivityForResult(intent, CREATE_FILE);
}

Otwórz plik

Aplikacja może używać dokumentów jako jednostki przechowywania, w której użytkownicy wpisują dane, które mogą chcieć udostępnić innym użytkownikom lub zaimportować do innych dokumentów. (kilka przykładów: użytkownik otwiera dokument dotyczący produktywności lub książkę zapisaną jako plik EPUB).

W takich przypadkach zezwól użytkownikowi na wybranie pliku do otwarcia, wywołując intencję ACTION_OPEN_DOCUMENT, co spowoduje otwarcie systemowej aplikacji do wyboru plików. Aby wyświetlić tylko typy plików obsługiwane przez Twoją aplikację, określ typ MIME. Korzystając z dodatkowego zamiaru EXTRA_INITIAL_URI, możesz też opcjonalnie określić identyfikator URI pliku, który selektor plików powinien wyświetlać podczas pierwszego wczytywania.

Poniższy fragment kodu pokazuje, jak utworzyć i wywołać intencję otwarcia dokumentu PDF:

Kotlin

// Request code for selecting a PDF document.
const val PICK_PDF_FILE = 2

fun openFile(pickerInitialUri: Uri) {
    val intent = Intent(Intent.ACTION_OPEN_DOCUMENT).apply {
        addCategory(Intent.CATEGORY_OPENABLE)
        type = "application/pdf"

        // Optionally, specify a URI for the file that should appear in the
        // system file picker when it loads.
        putExtra(DocumentsContract.EXTRA_INITIAL_URI, pickerInitialUri)
    }

    startActivityForResult(intent, PICK_PDF_FILE)
}

Java

// Request code for selecting a PDF document.
private static final int PICK_PDF_FILE = 2;

private void openFile(Uri pickerInitialUri) {
    Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT);
    intent.addCategory(Intent.CATEGORY_OPENABLE);
    intent.setType("application/pdf");

    // Optionally, specify a URI for the file that should appear in the
    // system file picker when it loads.
    intent.putExtra(DocumentsContract.EXTRA_INITIAL_URI, pickerInitialUri);

    startActivityForResult(intent, PICK_PDF_FILE);
}

Ograniczenia dostępu

Na Androidzie 11 (poziom interfejsu API 30) i nowszych nie można użyć działania intencji ACTION_OPEN_DOCUMENT, aby poprosić użytkownika o wybranie poszczególnych plików z tych katalogów:

  • Katalog Android/data/ i wszystkie podkatalogi.
  • Katalog Android/obb/ i wszystkie podkatalogi.

Przyznawanie dostępu do zawartości katalogu

Aplikacje do zarządzania plikami i ich tworzenia zwykle zarządzają grupami plików w hierarchii katalogów. Aby udostępnić tę funkcję w swojej aplikacji, użyj działania intencji ACTION_OPEN_DOCUMENT_TREE. Umożliwia ono użytkownikowi przyznanie dostępu do całego drzewa katalogu z pewnymi wyjątkami w Androidzie 11 (poziom interfejsu API 30). Aplikacja może wtedy uzyskiwać dostęp do dowolnych plików w wybranym katalogu i jego podkatalogach.

Gdy używasz funkcji ACTION_OPEN_DOCUMENT_TREE, Twoja aplikacja uzyskuje dostęp tylko do plików w katalogu wybranym przez użytkownika. Nie masz dostępu do plików innych aplikacji znajdujących się poza tym katalogiem wybranym przez użytkownika. Dostęp kontrolowany przez użytkowników pozwala im wybrać, które treści chcą udostępniać Twojej aplikacji.

Opcjonalnie możesz użyć dodatkowej intencji EXTRA_INITIAL_URI, aby podać identyfikator URI katalogu, który selektor plików powinien wyświetlać przy pierwszym wczytywaniu.

Poniższy fragment kodu pokazuje, jak utworzyć i wywołać intencję otwarcia katalogu:

Kotlin

fun openDirectory(pickerInitialUri: Uri) {
    // Choose a directory using the system's file picker.
    val intent = Intent(Intent.ACTION_OPEN_DOCUMENT_TREE).apply {
        // Optionally, specify a URI for the directory that should be opened in
        // the system file picker when it loads.
        putExtra(DocumentsContract.EXTRA_INITIAL_URI, pickerInitialUri)
    }

    startActivityForResult(intent, your-request-code)
}

Java

public void openDirectory(Uri uriToLoad) {
    // Choose a directory using the system's file picker.
    Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE);

    // Optionally, specify a URI for the directory that should be opened in
    // the system file picker when it loads.
    intent.putExtra(DocumentsContract.EXTRA_INITIAL_URI, uriToLoad);

    startActivityForResult(intent, your-request-code);
}

Ograniczenia dostępu

Na Androidzie 11 (poziom interfejsu API 30) i nowszych nie możesz użyć działania intencji ACTION_OPEN_DOCUMENT_TREE, aby poprosić o dostęp do tych katalogów:

  • Katalog główny woluminu pamięci wewnętrznej.
  • Katalog główny każdej karty SD, którą producent urządzenia uważa za niezawodny, niezależnie od tego, czy karta jest emulowana czy wyjmowana. Niezawodny wolumin to taki, z którego aplikacja może z łatwością korzystać przez większość czasu.
  • Katalog Download.

Ponadto na Androidzie 11 (poziom interfejsu API 30) i nowszych nie można użyć działania intencji ACTION_OPEN_DOCUMENT_TREE, aby poprosić użytkownika o wybranie poszczególnych plików z tych katalogów:

  • Katalog Android/data/ i wszystkie podkatalogi.
  • Katalog Android/obb/ i wszystkie podkatalogi.

Wykonywanie operacji na wybranej lokalizacji

Gdy użytkownik wybierze plik lub katalog przy użyciu systemowego selektora plików, możesz pobrać identyfikator URI wybranego elementu, korzystając z tego kodu w onActivityResult():

Kotlin

override fun onActivityResult(
        requestCode: Int, resultCode: Int, resultData: Intent?) {
    if (requestCode == your-request-code
            && resultCode == Activity.RESULT_OK) {
        // The result data contains a URI for the document or directory that
        // the user selected.
        resultData?.data?.also { uri ->
            // Perform operations on the document using its URI.
        }
    }
}

Java

@Override
public void onActivityResult(int requestCode, int resultCode,
        Intent resultData) {
    if (requestCode == your-request-code
            && resultCode == Activity.RESULT_OK) {
        // The result data contains a URI for the document or directory that
        // the user selected.
        Uri uri = null;
        if (resultData != null) {
            uri = resultData.getData();
            // Perform operations on the document using its URI.
        }
    }
}

Dzięki odwołaniem do identyfikatora URI wybranego elementu aplikacja może wykonać na nim kilka operacji. Można np. uzyskać dostęp do metadanych elementu, edytować go lub usunąć.

Z sekcji poniżej dowiesz się, jak wykonywać działania na plikach wybranych przez użytkownika.

Określ operacje obsługiwane przez dostawcę

Różni dostawcy treści pozwalają na wykonywanie różnych operacji na dokumentach, takich jak kopiowanie dokumentów czy wyświetlanie ich miniatur. Aby określić, które operacje obsługuje dany dostawca, sprawdź wartość Document.COLUMN_FLAGS. W interfejsie aplikacji będą się wtedy wyświetlać tylko opcje obsługiwane przez dostawcę.

Zachowuj uprawnienia

Gdy aplikacja otworzy plik w celu odczytu lub zapisu, system przyzna jej uprawnienia do identyfikatora URI, które będą obowiązywać do czasu ponownego uruchomienia urządzenia użytkownika. Załóżmy jednak, że Twoja aplikacja służy do edytowania obrazów i chcesz, aby użytkownicy mieli bezpośredni dostęp do 5 ostatnio edytowanych obrazów. Jeśli urządzenie użytkownika zostało ponownie uruchomione, musisz odesłać go z powrotem do selektora systemowego, aby znaleźć pliki.

Aby zachować dostęp do plików po ponownym uruchomieniu urządzenia i zwiększyć wygodę użytkowników, aplikacja może „przyjmować” stałe uprawnienie URI oferowane przez system, jak widać w tym fragmencie kodu:

Kotlin

val contentResolver = applicationContext.contentResolver

val takeFlags: Int = Intent.FLAG_GRANT_READ_URI_PERMISSION or
        Intent.FLAG_GRANT_WRITE_URI_PERMISSION
// Check for the freshest data.
contentResolver.takePersistableUriPermission(uri, takeFlags)

Java

final int takeFlags = intent.getFlags()
            & (Intent.FLAG_GRANT_READ_URI_PERMISSION
            | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
// Check for the freshest data.
getContentResolver().takePersistableUriPermission(uri, takeFlags);

Sprawdzanie metadanych dokumentu

Mając identyfikator URI dokumentu, uzyskujesz dostęp do jego metadanych. Ten fragment pobiera metadane dokumentu określonego przez identyfikator URI i rejestruje je:

Kotlin

val contentResolver = applicationContext.contentResolver

fun dumpImageMetaData(uri: Uri) {

    // The query, because it only applies to a single document, returns only
    // one row. There's no need to filter, sort, or select fields,
    // because we want all fields for one document.
    val cursor: Cursor? = contentResolver.query(
            uri, null, null, null, null, null)

    cursor?.use {
        // moveToFirst() returns false if the cursor has 0 rows. Very handy for
        // "if there's anything to look at, look at it" conditionals.
        if (it.moveToFirst()) {

            // Note it's called "Display Name". This is
            // provider-specific, and might not necessarily be the file name.
            val displayName: String =
                    it.getString(it.getColumnIndex(OpenableColumns.DISPLAY_NAME))
            Log.i(TAG, "Display Name: $displayName")

            val sizeIndex: Int = it.getColumnIndex(OpenableColumns.SIZE)
            // If the size is unknown, the value stored is null. But because an
            // int can't be null, the behavior is implementation-specific,
            // and unpredictable. So as
            // a rule, check if it's null before assigning to an int. This will
            // happen often: The storage API allows for remote files, whose
            // size might not be locally known.
            val size: String = if (!it.isNull(sizeIndex)) {
                // Technically the column stores an int, but cursor.getString()
                // will do the conversion automatically.
                it.getString(sizeIndex)
            } else {
                "Unknown"
            }
            Log.i(TAG, "Size: $size")
        }
    }
}

Java

public void dumpImageMetaData(Uri uri) {

    // The query, because it only applies to a single document, returns only
    // one row. There's no need to filter, sort, or select fields,
    // because we want all fields for one document.
    Cursor cursor = getActivity().getContentResolver()
            .query(uri, null, null, null, null, null);

    try {
        // moveToFirst() returns false if the cursor has 0 rows. Very handy for
        // "if there's anything to look at, look at it" conditionals.
        if (cursor != null && cursor.moveToFirst()) {

            // Note it's called "Display Name". This is
            // provider-specific, and might not necessarily be the file name.
            String displayName = cursor.getString(
                    cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME));
            Log.i(TAG, "Display Name: " + displayName);

            int sizeIndex = cursor.getColumnIndex(OpenableColumns.SIZE);
            // If the size is unknown, the value stored is null. But because an
            // int can't be null, the behavior is implementation-specific,
            // and unpredictable. So as
            // a rule, check if it's null before assigning to an int. This will
            // happen often: The storage API allows for remote files, whose
            // size might not be locally known.
            String size = null;
            if (!cursor.isNull(sizeIndex)) {
                // Technically the column stores an int, but cursor.getString()
                // will do the conversion automatically.
                size = cursor.getString(sizeIndex);
            } else {
                size = "Unknown";
            }
            Log.i(TAG, "Size: " + size);
        }
    } finally {
        cursor.close();
    }
}

Otwieranie dokumentu

Jeśli odwołujesz się do identyfikatora URI dokumentu, możesz otworzyć dokument do dalszego przetworzenia. W tej sekcji znajdziesz przykłady otwierania bitmapy i strumienia wejściowego.

Bitmapa

Ten fragment kodu pokazuje, jak otworzyć plik Bitmap na podstawie jego identyfikatora URI:

Kotlin

val contentResolver = applicationContext.contentResolver

@Throws(IOException::class)
private fun getBitmapFromUri(uri: Uri): Bitmap {
    val parcelFileDescriptor: ParcelFileDescriptor =
            contentResolver.openFileDescriptor(uri, "r")
    val fileDescriptor: FileDescriptor = parcelFileDescriptor.fileDescriptor
    val image: Bitmap = BitmapFactory.decodeFileDescriptor(fileDescriptor)
    parcelFileDescriptor.close()
    return image
}

Java

private Bitmap getBitmapFromUri(Uri uri) throws IOException {
    ParcelFileDescriptor parcelFileDescriptor =
            getContentResolver().openFileDescriptor(uri, "r");
    FileDescriptor fileDescriptor = parcelFileDescriptor.getFileDescriptor();
    Bitmap image = BitmapFactory.decodeFileDescriptor(fileDescriptor);
    parcelFileDescriptor.close();
    return image;
}

Po otwarciu bitmapy możesz ją wyświetlić w ImageView.

Strumień wejściowy

Poniższy fragment kodu pokazuje, jak otworzyć obiekt ConversionStream z użyciem jego identyfikatora URI. W tym fragmencie dane z pliku są odczytywane jako ciąg znaków:

Kotlin

val contentResolver = applicationContext.contentResolver

@Throws(IOException::class)
private fun readTextFromUri(uri: Uri): String {
    val stringBuilder = StringBuilder()
    contentResolver.openInputStream(uri)?.use { inputStream ->
        BufferedReader(InputStreamReader(inputStream)).use { reader ->
            var line: String? = reader.readLine()
            while (line != null) {
                stringBuilder.append(line)
                line = reader.readLine()
            }
        }
    }
    return stringBuilder.toString()
}

Java

private String readTextFromUri(Uri uri) throws IOException {
    StringBuilder stringBuilder = new StringBuilder();
    try (InputStream inputStream =
            getContentResolver().openInputStream(uri);
            BufferedReader reader = new BufferedReader(
            new InputStreamReader(Objects.requireNonNull(inputStream)))) {
        String line;
        while ((line = reader.readLine()) != null) {
            stringBuilder.append(line);
        }
    }
    return stringBuilder.toString();
}

Edytowanie dokumentu

Za pomocą platformy Storage Access Framework możesz edytować dokument tekstowy.

Ten fragment kodu zastępuje zawartość dokumentu reprezentowanego przez podany identyfikator URI:

Kotlin

val contentResolver = applicationContext.contentResolver

private fun alterDocument(uri: Uri) {
    try {
        contentResolver.openFileDescriptor(uri, "w")?.use {
            FileOutputStream(it.fileDescriptor).use {
                it.write(
                    ("Overwritten at ${System.currentTimeMillis()}\n")
                        .toByteArray()
                )
            }
        }
    } catch (e: FileNotFoundException) {
        e.printStackTrace()
    } catch (e: IOException) {
        e.printStackTrace()
    }
}

Java

private void alterDocument(Uri uri) {
    try {
        ParcelFileDescriptor pfd = getActivity().getContentResolver().
                openFileDescriptor(uri, "w");
        FileOutputStream fileOutputStream =
                new FileOutputStream(pfd.getFileDescriptor());
        fileOutputStream.write(("Overwritten at " + System.currentTimeMillis() +
                "\n").getBytes());
        // Let the document provider know you're done by closing the stream.
        fileOutputStream.close();
        pfd.close();
    } catch (FileNotFoundException e) {
        e.printStackTrace();
    } catch (IOException e) {
        e.printStackTrace();
    }
}

Usuwanie dokumentu

Jeśli masz identyfikator URI dokumentu, a Document.COLUMN_FLAGS zawiera SUPPORTS_DELETE, możesz usunąć dokument. Na przykład:

Kotlin

DocumentsContract.deleteDocument(applicationContext.contentResolver, uri)

Java

DocumentsContract.deleteDocument(applicationContext.contentResolver, uri);

Pobierz równoważny identyfikator URI multimediów

Metoda getMediaUri() udostępnia identyfikator URI magazynu multimediów, który jest odpowiednikiem danego identyfikatora URI dostawcy dokumentów. Dwa identyfikatory URI odnoszą się do tego samego elementu. Za pomocą identyfikatora URI magazynu multimediów możesz łatwiej uzyskiwać dostęp do plików multimedialnych z pamięci współdzielonej.

Metoda getMediaUri() obsługuje identyfikatory URI ExternalStorageProvider. W Androidzie 12 (poziom interfejsu API 31) i nowszych metoda obsługuje również identyfikatory URI MediaDocumentsProvider.

Otwieranie pliku wirtualnego

Na Androidzie 7.0 (poziom interfejsu API 25) i nowszych aplikacja może korzystać z plików wirtualnych dostępnych w ramach platformy Storage Access Framework. Mimo że pliki wirtualne nie mają reprezentacji binarnej, aplikacja może otworzyć ich zawartość, wymuszając je do innego typu pliku lub wyświetlając je za pomocą intencji ACTION_VIEW.

Aby otwierać pliki wirtualne, aplikacja kliencka musi mieć specjalne zasady ich obsługi. Jeśli chcesz uzyskać reprezentację pliku w bajtach – na przykład aby wyświetlić jego podgląd – musisz poprosić dostawcę dokumentów o alternatywny typ MIME.

Gdy użytkownik dokona wyboru, użyj identyfikatora URI w danych dotyczących wyników, by określić, czy plik jest wirtualny, jak pokazano w tym fragmencie kodu:

Kotlin

private fun isVirtualFile(uri: Uri): Boolean {
    if (!DocumentsContract.isDocumentUri(this, uri)) {
        return false
    }

    val cursor: Cursor? = contentResolver.query(
            uri,
            arrayOf(DocumentsContract.Document.COLUMN_FLAGS),
            null,
            null,
            null
    )

    val flags: Int = cursor?.use {
        if (cursor.moveToFirst()) {
            cursor.getInt(0)
        } else {
            0
        }
    } ?: 0

    return flags and DocumentsContract.Document.FLAG_VIRTUAL_DOCUMENT != 0
}

Java

private boolean isVirtualFile(Uri uri) {
    if (!DocumentsContract.isDocumentUri(this, uri)) {
        return false;
    }

    Cursor cursor = getContentResolver().query(
        uri,
        new String[] { DocumentsContract.Document.COLUMN_FLAGS },
        null, null, null);

    int flags = 0;
    if (cursor.moveToFirst()) {
        flags = cursor.getInt(0);
    }
    cursor.close();

    return (flags & DocumentsContract.Document.FLAG_VIRTUAL_DOCUMENT) != 0;
}

Po sprawdzeniu, czy dokument jest plikiem wirtualnym, możesz zmienić jego typ na inny, taki jak "image/png". Poniższy fragment kodu pokazuje, jak sprawdzić, czy plik wirtualny może być reprezentowany jako obraz, a jeśli tak, pobiera strumień wejściowy z pliku wirtualnego:

Kotlin

@Throws(IOException::class)
private fun getInputStreamForVirtualFile(
        uri: Uri, mimeTypeFilter: String): InputStream {

    val openableMimeTypes: Array<String>? =
            contentResolver.getStreamTypes(uri, mimeTypeFilter)

    return if (openableMimeTypes?.isNotEmpty() == true) {
        contentResolver
                .openTypedAssetFileDescriptor(uri, openableMimeTypes[0], null)
                .createInputStream()
    } else {
        throw FileNotFoundException()
    }
}

Java

private InputStream getInputStreamForVirtualFile(Uri uri, String mimeTypeFilter)
    throws IOException {

    ContentResolver resolver = getContentResolver();

    String[] openableMimeTypes = resolver.getStreamTypes(uri, mimeTypeFilter);

    if (openableMimeTypes == null ||
        openableMimeTypes.length < 1) {
        throw new FileNotFoundException();
    }

    return resolver
        .openTypedAssetFileDescriptor(uri, openableMimeTypes[0], null)
        .createInputStream();
}

Dodatkowe materiały

Więcej informacji o przechowywaniu dokumentów i innych plikach oraz o dostępie do nich znajdziesz w tych materiałach.

Próbki

Filmy