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

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

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

השימוש ב-framework כולל את השלבים הבאים:

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

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

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

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

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

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

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

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

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

הערה: 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.

במקרים כאלה, אפשר למשתמש לבחור את הקובץ לפתיחה על ידי הפעלת ACTION_OPEN_DOCUMENT Intent, שפותח את האפליקציה של המערכת לבחירת קבצים. כדי להציג רק את הסוגים של קבצים שהאפליקציה שלך תומכת בהם, צריך לציין סוג 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) ואילך, אי אפשר להשתמש פעולת Intent אחת (ACTION_OPEN_DOCUMENT) כדי לבקש מהמשתמש לבחור אדם פרטי מהספריות הבאות:

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

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

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

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

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

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

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) ואילך, אי אפשר להשתמש פעולת Intent אחת (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;
}

אחרי שפותחים את מפת הסיביות, אפשר להציג אותה ImageView

שידור קלט

קטע הקוד הבא מראה איך לפתוח אובייקט קלטStream בהינתן 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() ה-method method מספקת URI של מאגר מדיה שזהה למסמכים הנתונים ה-URI של הספק. 2 מזהי ה-URI מתייחסים לאותו פריט בסיסי. שימוש במדיה שומרים את ה-URI, אפשר לגשת בקלות לקובצי מדיה דרך קבצים משותפים אחסון נוסף.

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

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

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

כדי לפתוח קבצים וירטואליים, אפליקציית הלקוח צריכה לכלול לוגיקה מיוחדת לטיפול אותם. אם רוצים לקבל ייצוג בבייט של הקובץ – לתצוגה מקדימה של הקובץ, לדוגמה, צריך לבקש סוג 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();
}

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

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

דוגמיות

סרטונים