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 wolnościami pamięci zewnętrznej i miejscem na dane w chmurze, za pomocą platformy Storage Access Framework. Dzięki niej użytkownicy mogą korzystać z selektora systemowego, aby wybrać dostawcę dokumentów oraz konkretne dokumenty i inne pliki do utworzenia, otwarcia lub zmodyfikowania przez aplikację.

To użytkownik decyduje o wyborze plików lub katalogów, do których aplikacja ma dostęp, dlatego ten mechanizm nie wymaga żadnych uprawnień systemu, a dodatkowo kontrola użytkowników i prywatność są większe. Dodatkowo te pliki, które są przechowywane poza katalogiem dla aplikacji i poza sklepem multimedialnym, pozostają na urządzeniu po odinstalowaniu aplikacji.

Aby korzystać z platformy, wykonaj te czynności:

  1. Aplikacja wywołuje intencję, która zawiera działanie związane z pamięcią. To działanie odpowiada konkretnemu przypadkowi użycia udostępnianemu 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.
  3. Aplikacja uzyskuje uprawnienia do odczytu i zapisu do identyfikatora URI, który reprezentuje wybraną lokalizację lub dokument użytkownika. Za pomocą tego identyfikatora URI aplikacja może wykonywać operacje w wybranej lokalizacji.

Aby zapewnić obsługę dostępu do plików multimedialnych na urządzeniach z Androidem 9 (poziom interfejsu API 28) lub starszym, zadeklaruj uprawnienia READ_EXTERNAL_STORAGE i ustaw maxSdkVersion na 28.

W tym przewodniku omawiamy różne przypadki użycia obsługiwane przez platformę w przypadku pracy z plikami i innymi dokumentami. Wyjaśniamy także, jak wykonywać operacje w lokalizacji wybranej przez użytkownika.

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

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

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

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

Tworzenie nowego pliku

Użyj działania intencji ACTION_CREATE_DOCUMENT, aby wczytać systemowy selektor plików i umożliwić użytkownikowi wybranie lokalizacji, w której będzie można zapisać zawartość pliku. Ten proces przypomina okno dialogowe „Zapisz jako” używane 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, system doda na jego końcu liczbę w nawiasach.

Jeśli na przykład aplikacja próbuje zapisać plik o nazwie confirmation.pdf w katalogu, w którym jest już plik o tej nazwie, system zapisze nowy plik o nazwie confirmation(1).pdf.

Podczas konfigurowania intencji określ nazwę pliku i typ MIME oraz opcjonalnie wskaż identyfikator URI pliku lub katalogu, który selektor plików ma wyświetlić po pierwszym wczytaniu, korzystając z dodatkowej intencji EXTRA_INITIAL_URI.

Ten 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 pamięci, w której użytkownicy wpisują dane, które chcą udostępnić innym użytkownikom lub importować do innych dokumentów. Przykładami takich treści są otwieranie dokumentu zwiększającego produktywność 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 spowoduje otwarcie systemowej aplikacji z selektorem plików. Aby wyświetlić tylko typy plików obsługiwane przez Twoją aplikację, określ typ MIME. Korzystając z dodatkowej intencji EXTRA_INITIAL_URI, możesz też opcjonalnie określić identyfikator URI pliku, który ma być wyświetlany w selektorze plików po pierwszym wczytaniu.

Ten 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

W Androidzie 11 (poziom interfejsu API 30) i nowszych nie można używać 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 tworzenia multimediów 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, które umożliwia użytkownikowi przyznanie dostępu do całego drzewa katalogów z pewnymi wyjątkami, począwszy od Androida 11 (poziom API 30). Aplikacja może wtedy uzyskać dostęp do dowolnego pliku w wybranym katalogu i dowolnych jego podkatalogach.

Gdy używasz ACTION_OPEN_DOCUMENT_TREE, aplikacja uzyskuje dostęp tylko do plików w katalogu, który użytkownik wybierze. Nie masz dostępu do plików innych aplikacji znajdujących się poza tym katalogiem wybranym przez użytkownika. Dzięki temu dostępowi użytkownicy mogą dokładnie wybrać, jakie treści chcą udostępnić Twojej aplikacji.

Opcjonalnie możesz użyć dodatkowej intencji EXTRA_INITIAL_URI, aby określić identyfikator URI katalogu, który ma być wyświetlany przez selektor plików po pierwszym wczytaniu.

Ten 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

W Androidzie 11 (poziom interfejsu API 30) i nowszych nie można używać 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 woluminu karty SD, który producent urządzenia uznaje za niezawodny, bez względu na to, czy karta jest emulowana czy wyjmowana. Niezawodny wolumin to taki, do którego aplikacja ma zazwyczaj dostęp przez większość czasu.
  • Katalog Download.

Ponadto w Androidzie 11 (poziom interfejsu API 30) i nowszych nie można używać 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 za pomocą systemowego selektora plików, możesz pobrać jego identyfikator URI, 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 uzyskaniu odniesienia do identyfikatora URI wybranego elementu aplikacja może wykonać na nim kilka operacji. Można na przykład uzyskać dostęp do metadanych elementu, edytować go i usunąć.

W sekcjach 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 na dokumentach odmiennych operacji, takich jak kopiowanie dokumentu lub wyświetlanie jego miniatury. Aby określić, które operacje obsługuje dany dostawca, sprawdź wartość Document.COLUMN_FLAGS. Interfejs aplikacji może zawierać tylko opcje obsługiwane przez dostawcę.

Zachowaj uprawnienia

Gdy aplikacja otwiera plik do odczytu lub zapisu, system przyzna jej uprawnienia do tego pliku przyznane przez identyfikator URI. Trwa ono do momentu ponownego uruchomienia urządzenia użytkownika. Załóżmy jednak, że Twoja aplikacja służy do edycji obrazów i chcesz, aby użytkownicy mieli dostęp do 5 ostatnio edytowanych obrazów bezpośrednio w aplikacji. Jeśli urządzenie użytkownika zostało ponownie uruchomione, musisz odesłać go do selektora systemowego, aby znalazł pliki.

Aby zachować dostęp do plików na różnych urządzeniach podczas ponownego uruchamiania i zapewnić większą wygodę użytkownikom, aplikacja może „przyjąć” trwałe uprawnienia identyfikatora URI przyznane 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

Dostęp do metadanych dokumentu uzyskasz, jeśli masz identyfikator URI dokumentu. 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

Wykorzystując odniesienie do identyfikatora URI dokumentu, możesz go otworzyć do dalszego przetworzenia. W tej sekcji znajdziesz przykłady otwierania bitmapy i strumienia wejściowego.

Bitmapa

Ten fragment kodu pokazuje, jak otworzyć plik Bitmap z podanym identyfikatorem 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ć na mapie ImageView.

Strumień wejściowy

Ten fragment kodu pokazuje, jak otworzyć obiekt IngressStream z jego identyfikatorem URI. W tym fragmencie wiersze pliku są odczytywane w postaci ciągu 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

Dokument tekstowy możesz też edytować za pomocą platformy Storage Access Framework.

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. Oba identyfikatory URI odnoszą się do tego samego elementu podstawowego. Korzystając z identyfikatora 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 metoda obsługuje też identyfikatory URI MediaDocumentsProvider.

Otwieranie pliku wirtualnego

W Androidzie 7.0 (poziom interfejsu API 25) i nowszych 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ść przez zmuszenie ich do zmiany typu pliku lub wyświetlenie tych plików za pomocą działania intencji ACTION_VIEW.

Aby można było otwierać pliki wirtualne, aplikacja kliencka musi zawierać specjalną logikę do ich obsługi. Jeśli chcesz, by plik był przedstawiany w bajtach (na przykład na potrzeby jego podglądu), musisz poprosić dostawcę dokumentów o alternatywny typ MIME.

Gdy użytkownik dokona wyboru, użyj w danych wyników identyfikatora URI, 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 sprawdzeniu, czy dokument jest plikiem wirtualnym, możesz zmienić go na inny typ MIME, na przykład "image/png". Poniższy fragment kodu pokazuje, jak sprawdzić, czy plik wirtualny może być reprezentowany jako obraz, a jeśli tak, pobiera z niego strumień wejściowy:

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 uzyskiwanie do nich dostępu znajdziesz w tych materiałach:

Próbki

Filmy