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 interakcję z dostawcą dokumentów, w tym z zewnętrznych woluminów pamięci masowej i magazynu w chmurze, za pomocą interfejsu Storage Access Framework. Ta platforma umożliwia użytkownikom korzystanie z systemowego selektora do wybierania dostawcy dokumentów i określania konkretnych dokumentów oraz innych plików, które aplikacja ma tworzyć, otwierać i modyfikować.

Użytkownik wybiera pliki i katalogi, do których ma dostęp Twoja aplikacja, więc ten mechanizm nie wymaga żadnych uprawnień systemowych. Dzięki temu użytkownik ma większą kontrolę nad prywatnością. Dodatkowo te pliki, które są przechowywane poza katalogiem aplikacji i poza magazynem multimediów, pozostają na urządzeniu po odinstalowaniu aplikacji.

Korzystanie z ramy obejmuje te kroki:

  1. Aplikacja wywołuje intencję zawierającą działanie związane z przechowywaniem. To działanie odpowiada konkretnemu przypadkowi użycia, który jest dostępny w ramach.
  2. Użytkownik widzi selektor systemowy, który umożliwia przeglądanie dostawcy dokumentów i wybranie lokalizacji lub dokumentu, w przypadku których ma zostać wykonane działanie związane z przechowywaniem.
  3. Aplikacja uzyskuje dostęp do odczytu i zapisu do identyfikatora URI, który reprezentuje wybraną przez użytkownika lokalizację lub dokument. Za pomocą tego identyfikatora URI aplikacja może wykonywać operacje na wybranej lokalizacji.

Aby umożliwić 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 wartość parametru maxSdkVersion na 28.

W tym przewodniku opisaliśmy różne przypadki użycia, które obsługuje ten framework w przypadku pracy z plikami i innymi dokumentami. Wyjaśnia ona też, jak wykonywać operacje 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 dotyczące dostępu do plików i innych dokumentów.

Tworzenie nowego pliku
Akcja intencjonalna ACTION_CREATE_DOCUMENTpozwala użytkownikom zapisać plik w konkretnej lokalizacji.
Otwieranie dokumentu lub pliku
Akcja intencjonalna ACTION_OPEN_DOCUMENTpozwala użytkownikom wybrać konkretny dokument lub plik do otwarcia.
Przyznawanie dostępu do zawartości katalogu
Działanie intencyjne ACTION_OPEN_DOCUMENT_TREE, dostępne w Androidzie 5.0 (poziom API 21) i nowszych wersjach, umożliwia użytkownikom wybranie konkretnego katalogu, co daje Twojej aplikacji dostęp do wszystkich plików i podkatalogów w tym katalogu.

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

Tworzenie nowego pliku

Użyj działania intencyjnego ACTION_CREATE_DOCUMENT, aby załadować okno wyboru plików systemu i zezwolić użytkownikowi na wybranie lokalizacji, w której ma zostać zapisany plik. Ten proces jest podobny do tego, który jest używany w oknach „Zapisz jako” w innych systemach operacyjnych.

Uwaga: ACTION_CREATE_DOCUMENT nie może zastąpić istniejącego pliku. Jeśli aplikacja próbuje zapisać plik o tej samej nazwie, system doda na końcu nazwy pliku liczbę w nawiasach.

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

Podczas konfigurowania intencji podaj nazwę pliku i typ MIME, a opcjonalnie też URI pliku lub katalogu, który selektor plików powinien wyświetlić podczas pierwszego wczytania, używając opcjonalnego atrybutu EXTRA_INITIAL_URI intencji.

Ten fragment kodu pokazuje, jak utworzyć i wywołać intencję tworzenia 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

Twoja aplikacja może używać dokumentów jako jednostki pamięci, w której użytkownicy wpisują dane, które chcą udostępnić innym użytkownikom lub zaimportować do innych dokumentów. Przykłady obejmują otwieranie przez użytkownika dokumentu związanego z produkcyjnością lub książki zapisanej jako plik EPUB.

W takich przypadkach pozwól użytkownikowi wybrać plik do otwarcia, wywołując intencję ACTION_OPEN_DOCUMENT, która otwiera aplikację do wybierania plików w systemie. Aby wyświetlać tylko typy plików obsługiwane przez aplikację, określ typ MIME. Opcjonalnie możesz też określić URI pliku, który ma być wyświetlany przez selektor plików podczas pierwszego wczytywania, za pomocą atrybutu EXTRA_INITIAL_URI w intencji dodatkowej.

Ten fragment kodu pokazuje, jak utworzyć i wywołać intencję otwierania 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

W Androidzie 11 (poziom interfejsu API 30) lub nowszym nie możesz użyć działania intencjonalnego 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 tworzenia multimediów zwykle zarządzają grupami plików w hierarchii katalogów. Aby udostępnić tę funkcję w aplikacji, użyj działania intencjonalnego ACTION_OPEN_DOCUMENT_TREE, które pozwala użytkownikowi przyznać dostęp do całego drzewa katalogów, z pewnymi wyjątkami od Androida 11 (poziom interfejsu API 30). Aplikacja może wtedy uzyskać dostęp do dowolnego pliku w wybranym katalogu i jego podkatalogach.

Gdy używasz ACTION_OPEN_DOCUMENT_TREE, aplikacja uzyskuje dostęp tylko do plików w katalogu wybranym przez użytkownika. Nie masz dostępu do innych plików Firebase, które znajdują się poza tym katalogiem wybranym przez użytkownika. Ten dostęp kontrolowany przez użytkownika pozwala mu wybrać, które treści chce udostępnić aplikacji.

Opcjonalnie możesz określić URI katalogu, który selektor plików powinien wyświetlić podczas pierwszego wczytania, używając opcjonalnego atrybutu intencji EXTRA_INITIAL_URI.

Ten fragment kodu pokazuje, jak utworzyć i wywołać intencję otwierania 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

W Androidzie 11 (poziom interfejsu API 30) lub nowszym nie możesz używać działania intencjonalnego ACTION_OPEN_DOCUMENT_TREE, aby uzyskać 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 uznaje za wiarygodną, niezależnie od tego, czy karta jest emulowana, czy wymienna. Wolumin niezawodny to taki, do którego aplikacja może uzyskać dostęp przez większość czasu.
  • Katalog Download.

Ponadto w Androidzie 11 (poziom interfejsu API 30) i nowszych nie możesz używać działania intencjonalnego 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 za pomocą selektora plików systemu, możesz pobrać identyfikator URI wybranego elementu, używając 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 odniesieniu do identyfikatora URI wybranego elementu aplikacja może wykonywać na nim różne operacje. Możesz na przykład uzyskać dostęp do metadanych elementu, edytować go na miejscu i usunąć.

W kolejnych sekcjach dowiesz się, jak wykonywać działania na plikach wybranych przez użytkownika.

Określanie operacji obsługiwanych przez dostawcę

Różni dostawcy treści umożliwiają wykonywanie różnych operacji na dokumentach, takich jak kopiowanie dokumentu czy wyświetlanie jego miniatury. Aby określić, które operacje obsługuje dany dostawca, sprawdź wartość parametru Document.COLUMN_FLAGS. Interfejs aplikacji może wtedy wyświetlać tylko opcje obsługiwane przez dostawcę.

Utrzymywanie uprawnień

Gdy aplikacja otwiera plik do odczytu lub zapisu, system przyznaje jej uprawnienia do tego pliku, które są ważne do czasu ponownego uruchomienia urządzenia przez użytkownika. Załóżmy jednak, że Twoja aplikacja służy do edycji zdjęć i chcesz, aby użytkownicy mogli uzyskać dostęp do 5 ostatnio edytowanych zdjęć bezpośrednio z aplikacji. Jeśli urządzenie użytkownika zostanie ponownie uruchomione, będzie musiał wrócić do selektora systemowego, aby znaleźć pliki.

Aby zachować dostęp do plików po ponownym uruchomieniu urządzenia i zapewnić lepszy interfejs użytkownika, aplikacja może „przyjąć” trwałe uprawnienia do URI, które oferuje system, jak pokazano 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

Gdy masz identyfikator URI dokumentu, uzyskujesz dostęp do jego metadanych. Ten fragment kodu pobiera metadane dokumentu określonego przez identyfikator URI i zapisuje 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

Dzięki odwołaniu do identyfikatora URI dokumentu możesz otworzyć dokument do dalszego przetwarzania. W tej sekcji znajdziesz przykłady otwierania bitmapy i strumienia danych wejściowych.

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 wyświetlić ją w oknie ImageView.

Strumień wejściowy

Ten fragment kodu pokazuje, jak otworzyć obiekt InputStream na podstawie jego URI. W tym fragmencie wiersze pliku są odczytywane do ciągu:

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ą interfejsu Storage Access Framework możesz edytować dokument tekstowy na miejscu.

Ten fragment kodu zastępuje zawartość dokumentu reprezentowanego przez dany 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 adres URI dokumentu, a Document.COLUMN_FLAGS dokumentu zawiera SUPPORTS_DELETE, możesz usunąć dokument. Przykład:

Kotlin

DocumentsContract.deleteDocument(applicationContext.contentResolver, uri)

Java

DocumentsContract.deleteDocument(applicationContext.contentResolver, uri);

Pobieranie równoważnego identyfikatora URI multimediów

Metoda getMediaUri() udostępnia identyfikator URI magazynu multimediów, który jest odpowiednikiem podanego identyfikatora URI dostawcy dokumentów. Oba identyfikatory URI odnoszą się do tego samego elementu. Za pomocą adresu URI magazynu multimediów możesz łatwiej uzyskać 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 ta metoda obsługuje też URI-y MediaDocumentsProvider.

Otwieranie pliku wirtualnego

W Androidzie 7.0 (poziom interfejsu API 25) i nowszym aplikacja może korzystać z plików wirtualnych udostępnianych przez Storage Access Framework. Mimo że pliki wirtualne nie mają reprezentacji binarnej, aplikacja może otworzyć ich zawartość, wymuszając na nich inny typ pliku lub wyświetlając je za pomocą działania intencyjnego ACTION_VIEW.

Aby otwierać pliki wirtualne, aplikacja klienta musi zawierać specjalną logikę do ich obsługi. Jeśli chcesz uzyskać reprezentację bajtową pliku (np. 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 wyników, aby 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 zweryfikowaniu, że dokument jest plikiem wirtualnym, możesz skonwertować go na alternatywny typ MIME, np. "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 plików oraz uzyskiwaniu do nich dostępu znajdziesz w tych materiałach.

Próbki

Filmy