לגשת למסמכים ולקבצים אחרים מתוך נפח האחסון המשותף

במכשירים עם Android 4.4 (רמת API 19) ואילך, האפליקציה יכולה לתקשר עם ספק מסמכים, כולל נפחי אחסון חיצוניים ואחסון מבוסס-ענן, באמצעות Storage Access Framework. המסגרת הזו מאפשרת למשתמשים לקיים אינטראקציה עם בורר מערכת כדי לבחור ספק מסמכים ולבחור מסמכים ספציפיים וקבצים אחרים שהאפליקציה שלכם תיצור, תפתח או תשנה.

המשתמש מעורב בבחירת הקבצים או הספריות שאפליקצייתכם יכולה לגשת אליהם, ולכן המנגנון הזה לא דורש הרשאות מערכת, והשליטה של המשתמש בפרטיות משופר. בנוסף, הקבצים האלה, שמאוחסנים מחוץ לספרייה ספציפית לאפליקציה ומחוץ למאגר המדיה, נשארים במכשיר גם אחרי הסרת האפליקציה.

כדי להשתמש במסגרת, מבצעים את השלבים הבאים:

  1. אפליקציה מפעילה כוונה (intent) שמכילה פעולה שקשורה לאחסון. הפעולה הזו תואמת לתרחיש לדוגמה ספציפי שזמין במסגרת.
  2. המשתמש רואה בורר מערכת שמאפשר לו לעיין בספק מסמכים ולבחור מיקום או מסמך שבהם תתבצע הפעולה שקשורה לאחסון.
  3. האפליקציה מקבלת גישת קריאה וכתיבה ב-URI שמייצג את המיקום או המסמך שהמשתמש בחר. באמצעות ה-URI הזה, האפליקציה יכולה לבצע פעולות במיקום שנבחר.

כדי לתמוך בגישה לקובצי מדיה במכשירים עם Android 9 (רמת API‏ 28) ואילך, צריך להצהיר על ההרשאה READ_EXTERNAL_STORAGE ולהגדיר את maxSdkVersion לערך 28.

במדריך הזה מוסבר על התרחישים לדוגמה השונים שבהם המסגרת תומכת בעבודה עם קבצים ומסמכים אחרים. בנוסף, מוסבר איך לבצע פעולות במיקום שבחר המשתמש.

תרחישים לדוגמה של גישה למסמכים ולקבצים אחרים

Storage Access Framework תומך בתרחישים לדוגמה הבאים לגישה לקבצים ולמסמכים אחרים.

יצירת קובץ חדש
פעולת כוונת הרכישה ACTION_CREATE_DOCUMENT מאפשרת למשתמשים לשמור קובץ במיקום ספציפי.
פתיחת מסמך או קובץ
פעולת הכוונה ACTION_OPEN_DOCUMENT מאפשרת למשתמשים לבחור מסמך או קובץ ספציפיים לפתיח.
מתן גישה לתוכן של ספרייה
פעולת הכוונה ACTION_OPEN_DOCUMENT_TREE זמינה ב-Android 5.0 (רמת API 21) ואילך, ומאפשרת למשתמשים לבחור ספרייה ספציפית, וכך מעניקה לאפליקציה גישה לכל הקבצים והספריות המשניות בספרייה הזו.

בקטעים הבאים מפורטות הנחיות להגדרה של כל תרחיש לדוגמה.

יצירת קובץ חדש

משתמשים בפעולת הכוונה ACTION_CREATE_DOCUMENT כדי לטעון את הבורר של מערכת הקבצים ולאפשר למשתמש לבחור מיקום שבו יכתבו את תוכן הקובץ. התהליך הזה דומה לתהליך שמתבצע בתיבת הדו-שיח 'שמירה כקובץ' שמשמשת מערכות הפעלה אחרות.

הערה: לא ניתן להשתמש ב-ACTION_CREATE_DOCUMENT כדי לשכתב קובץ קיים. אם האפליקציה תנסה לשמור קובץ עם אותו שם, המערכת תוסיף מספר בסוגריים בסוף שם הקובץ.

לדוגמה, אם האפליקציה מנסה לשמור קובץ בשם confirmation.pdf בספרייה שכבר יש בה קובץ בשם הזה, המערכת שומרת את הקובץ החדש בשם confirmation(1).pdf.

כשמגדירים את ה-Intent, צריך לציין את שם הקובץ ואת סוג ה-MIME, ואם רוצים, לציין את ה-URI של הקובץ או הספרייה שבורר הקבצים צריך להציג כשהוא נטען בפעם הראשונה, באמצעות תוספת כוונה מסוג EXTRA_INITIAL_URI.

קטע הקוד הבא מראה איך יוצרים את הכוונה ליצירת קובץ ומפעילים אותה:

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

פתיחת קובץ

יכול להיות שהאפליקציה שלכם תשתמש במסמכים בתור יחידת האחסון שבה המשתמשים מזינים נתונים שהם רוצים לשתף עם עמיתים או לייבא למסמכים אחרים. כמה דוגמאות: משתמש פותח מסמך פרודוקטיביות או פותח ספר שנשמר כקובץ EPUB.

במקרים כאלה, צריך לאפשר למשתמש לבחור את הקובץ שייפתח על ידי הפעלת ה-intent‏ ACTION_OPEN_DOCUMENT, שפותח את אפליקציית הבחירה של הקבצים במערכת. כדי להציג רק את סוגי הקבצים שהאפליקציה תומכת בהם, צריך לציין סוג MIME. אפשר גם לציין את ה-URI של הקובץ שבורר הקבצים אמור להציג בטעינה הראשונה שלו, באמצעות הפרמטר הנוסף של הכוונה EXTRA_INITIAL_URI.

בקטע הקוד הבא מוסבר איך יוצרים את הכוונה לפתוח מסמך 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);
}

הגבלות גישה

ב-Android 11 (רמת API‏ 30) ואילך, אי אפשר להשתמש בפעולת הכוונה ACTION_OPEN_DOCUMENT כדי לבקש מהמשתמש לבחור קבצים ספציפיים מהספריות הבאות:

  • הספרייה Android/data/ וכל ספריות המשנה.
  • הספרייה Android/obb/ וכל ספריות המשנה.

הענקת גישה לתוכן של ספרייה

אפליקציות לניהול קבצים וליצירת מדיה מנהלות בדרך כלל קבוצות של קבצים בהיררכיית ספריות. כדי לספק את היכולת הזו באפליקציה, צריך להשתמש בפעולת ה-Intent‏ ACTION_OPEN_DOCUMENT_TREE, שמאפשרת למשתמש להעניק גישה לעץ ספריות שלם, עם כמה יוצאים מן הכלל החל מ-Android 11 (רמת API ‏30). לאחר מכן, לאפליקציה תהיה גישה לכל קובץ בספרייה שנבחרה ובכל אחת מספריות המשנה שלה.

כשמשתמשים ב-ACTION_OPEN_DOCUMENT_TREE, לאפליקציה יש גישה רק לקבצים שבספרייה שהמשתמש בחר. אין לכם גישה לקבצים של אפליקציות אחרות שנמצאים מחוץ לספרייה שהמשתמש בחר. הגישה הזו, שבה המשתמש קובע מה הוא רוצה לשתף עם האפליקציה, מאפשרת למשתמשים לבחור בדיוק איזה תוכן הם רוצים לשתף.

לחלופין, אפשר לציין את ה-URI של הספרייה שבורר הקבצים צריך להציג כשהוא נטען בפעם הראשונה, באמצעות תוספת כוונה מסוג EXTRA_INITIAL_URI.

בקטע הקוד הבא מוצג איך יוצרים את ה-intent לפתיח ספרייה ומפעילים אותו:

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

הגבלות גישה

ב-Android מגרסה 11 (רמת API 30) ואילך, אי אפשר להשתמש בפעולת ה-Intent ACTION_OPEN_DOCUMENT_TREE כדי לבקש גישה לספריות הבאות:

  • ספריית הבסיס של נפח האחסון הפנימי.
  • ספריית השורש של כל נפח אחסון בכרטיס SD שנחשב על ידי יצרן המכשיר כמהימנה, ללא קשר אם הכרטיס הוא משובט או נשלף. נפח אמין הוא נפח שאפליקציה יכולה לגשת אליו בהצלחה רוב הזמן.
  • הספרייה Download.

בנוסף, ב-Android 11 (רמת API‏ 30) ואילך, אי אפשר להשתמש בפעולת הכוונה ACTION_OPEN_DOCUMENT_TREE כדי לבקש מהמשתמש לבחור קבצים ספציפיים מהספריות הבאות:

  • הספרייה Android/data/ וכל ספריות המשנה.
  • הספרייה Android/obb/ וכל ספריות המשנה.

ביצוע פעולות במיקום שנבחר

אחרי שהמשתמש בוחר קובץ או ספרייה באמצעות בורר הקבצים של המערכת, אפשר לאחזר את ה-URI של הפריט שנבחר באמצעות הקוד הבא ב-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.
        }
    }
}

כשמקבלים הפניה למזהה ה-URI של הפריט שנבחר, האפליקציה יכולה לבצע כמה פעולות על הפריט. לדוגמה, תוכלו לגשת למטא-נתונים של הפריט, לערוך את הפריט במקום ולמחוק אותו.

בקטעים הבאים מוסבר איך לבצע פעולות על הקבצים שהמשתמש בחר.

איך בודקים אילו פעולות ספקים תומכים בהן

ספקי תוכן שונים מאפשרים לבצע פעולות שונות במסמכים, כמו העתקת המסמך או הצגת התמונה הממוזערת שלו. כדי לקבוע באילו פעולות ספק מסוים תומך, בודקים את הערך של Document.COLUMN_FLAGS. לאחר מכן, ממשק המשתמש של האפליקציה יכול להציג רק את האפשרויות שהספק תומך בהן.

שמירת ההרשאות

כשהאפליקציה פותחת קובץ לקריאה או לכתיבה, המערכת מעניקה לאפליקציה הרשאת URI לקובץ הזה, שתוקפה נמשך עד שהמכשיר של המשתמש מופעל מחדש. עם זאת, נניח שהאפליקציה שלכם היא לעריכת תמונות ואתם רוצים שהמשתמשים יוכלו לגשת ישירות ל-5 התמונות שהם ערכו לאחרונה. אם המכשיר של המשתמש הופעל מחדש, תצטרכו להחזיר את המשתמש לבורר המערכת כדי למצוא את הקבצים.

כדי לשמור על הגישה לקבצים בהפעלות מחדש של המכשיר וליצור חוויית משתמש טובה יותר, האפליקציה יכולה "לקבל" את הרשאת ה-URI הקבועה שהמערכת מציעה, כפי שמוצג בקטע הקוד הבא:

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

בדיקת המטא-נתונים של המסמך

כשיש לכם את ה-URI של מסמך, אתם מקבלים גישה למטא-נתונים שלו. קטע הקוד הזה אוסף את המטא-נתונים של מסמך שצוין באמצעות ה-URI, ומתעדה אותו ביומן:

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

פתיחת מסמך

כשיש הפניה ל-URI של מסמך, אפשר לפתוח מסמך להמשך עיבוד. בקטע הזה מוצגות דוגמאות לפתיחת קובץ bitmap וסטרימינג של קלט.

מפת סיביות (bitmap)

קטע הקוד הבא מראה איך לפתוח קובץ Bitmap לפי ה-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;
}

אחרי שפותחים את קובץ ה-bitmap, אפשר להציג אותו ב-ImageView.

מקור קלט

בקטע הקוד הבא מוצג איך לפתוח אובייקט InputStream לפי ה-URI שלו. בקטע הקוד הזה, השורות של הקובץ נקראות למחרוזת:

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

עריכת מסמך

אפשר להשתמש ב-Storage Access Framework כדי לערוך מסמך טקסט במקום.

קטע הקוד הבא מחליף את תוכן המסמך שמיוצג על ידי ה-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();
    }
}

מחיקת מסמך

אם יש לכם את ה-URI של מסמך, וDocument.COLUMN_FLAGS של המסמך מכיל את SUPPORTS_DELETE, תוכלו למחוק את המסמך. לדוגמה:

Kotlin

DocumentsContract.deleteDocument(applicationContext.contentResolver, uri)

Java

DocumentsContract.deleteDocument(applicationContext.contentResolver, uri);

אחזור URI מקביל של מדיה

השיטה getMediaUri() מספקת URI של מאגר מדיה שזהה ל-URI של ספק המסמכים שצוין. שני מזהי ה-URI מתייחסים לאותו פריט בסיסי. באמצעות ה-URI של מאגר המדיה, תוכלו לגשת בקלות לקובצי מדיה מנפח האחסון המשותף.

ה-method getMediaUri() תומכת במזהי URI של ExternalStorageProvider. ב-Android 12 (רמת API 31) ואילך, השיטה תומכת גם ב-URI של MediaDocumentsProvider.

פתיחת קובץ וירטואלי

בגרסה 7.0 של Android (API ברמה 25) ואילך, האפליקציה יכולה להשתמש בקבצים וירטואליים שזמינים במסגרת של Storage Access Framework. למרות שלקבצים וירטואליים אין ייצוג בינארי, האפליקציה יכולה לפתוח את התוכן שלהם על ידי כפייה שלהם לסוג קובץ אחר, או על ידי הצגת הקבצים האלה באמצעות פעולת הכוונה ACTION_VIEW.

כדי לפתוח קבצים וירטואליים, אפליקציית הלקוח צריכה לכלול לוגיקה מיוחדת לטיפול בהם. אם רוצים לקבל ייצוג של הקובץ בבייט, למשל, כדי לראות תצוגה מקדימה של הקובץ, צריך לבקש סוג MIME חלופי מספק המסמכים.

אחרי שהמשתמש מבצע בחירה, משתמשים ב-URI בנתוני התוצאות כדי לקבוע אם הקובץ הוא וירטואלי, כפי שמתואר בקטע הקוד הבא:

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

אחרי שמוודאים שהמסמך הוא קובץ וירטואלי, אפשר לאלץ את הקובץ לסוג MIME חלופי, כמו "image/png". קטע הקוד הבא מראה איך לבדוק אם קובץ וירטואלי יכול להיות מיוצג כתמונה, ואם כן, מקבל שידור קלט מהקובץ הווירטואלי:

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

מקורות מידע נוספים

למידע נוסף על אחסון מסמכים וקובצי אחרים ועל גישה אליהם, תוכלו לעיין במקורות המידע הבאים.

דוגמיות

סרטונים