الوصول إلى المستندات والملفات الأخرى من مساحة التخزين المشتركة

على الأجهزة التي تعمل بنظام التشغيل Android 4.4 (مستوى واجهة برمجة التطبيقات 19) والإصدارات الأحدث، يمكن لتطبيقك التفاعل مع موفِّر المستندات، بما في ذلك وحدات التخزين الخارجية ومساحة التخزين المستندة إلى السحابة الإلكترونية، وذلك باستخدام إطار عمل الوصول إلى مساحة التخزين. يتيح هذا الإطار للمستخدمين التفاعل مع أداة اختيار مضمّنة بالنظام لاختيار موفّر مستندات واختيار مستندات وملفات أخرى محدّدة ليتمكّن تطبيقك من إنشائها أو فتحها أو تعديلها.

بما أنّ المستخدم يشارك في اختيار الملفات أو الأدلة التي يمكن لتطبيقك الوصول إليها، لا تتطلّب هذه الآلية أي أذونات للنظام، ويتم تحسين تحكّم المستخدم وخصوصيته. بالإضافة إلى ذلك، تظل هذه الملفات المخزَّنة خارج دليل خاص بالتطبيق وخارج متجر الوسائط على الجهاز بعد إلغاء تثبيت تطبيقك.

يتضمن استخدام إطار العمل الخطوات التالية:

  1. يستدعي أحد التطبيقات نية تحتوي على إجراء مرتبط بمساحة التخزين. يتوافق هذا الإجراء مع حالة استخدام معيّنة يوفّرها إطار العمل.
  2. تظهر للمستخدم أداة اختيار مضمّنة بالنظام تتيح له تصفُّح موفّر المستندات واختيار موقع جغرافي أو مستند يتم فيه تنفيذ الإجراء المتعلّق بمساحة التخزين.
  3. يحصل التطبيق على إذن بالقراءة والكتابة في معرّف الموارد المنتظم (URI) الذي يمثّل الموقع الجغرافي أو المستند الذي اختاره المستخدم. باستخدام عنوان URI هذا، يمكن للتطبيق تنفيذ عمليات على الموقع الجغرافي الذي تم اختياره.

لإتاحة الوصول إلى ملفات الوسائط على الأجهزة التي تعمل بالإصدار 9 من نظام التشغيل Android (المستوى 28 من واجهة برمجة التطبيقات) أو إصدار أقدم، عليك تقديم بيان لإذن READ_EXTERNAL_STORAGE وضبط maxSdkVersion على 28.

يوضّح هذا الدليل حالات الاستخدام المختلفة التي يتيحها إطار العمل للتعامل مع الملفات والمستندات الأخرى. وتوضّح أيضًا كيفية تنفيذ العمليات على الموقع الجغرافي الذي يختاره المستخدم.

حالات استخدام الوصول إلى المستندات والملفات الأخرى

يتيح "إطار عمل أذونات الوصول إلى مساحة التخزين" حالات الاستخدام التالية للوصول إلى الملفات والمستندات الأخرى.

إنشاء ملف جديد
يتيح إجراء intent‏ ACTION_CREATE_DOCUMENT للمستخدمين حفظ ملف في موقع جغرافي محدّد.
فتح مستند أو ملف
يتيح إجراء intent‏ ACTION_OPEN_DOCUMENT للمستخدمين اختيار مستند أو ملف معيّن لفتحه.
منح الإذن بالوصول إلى محتوى دليل
يتيح إجراء intent‏ ACTION_OPEN_DOCUMENT_TREE المتوفّر على الإصدار 5.0 من نظام التشغيل Android (مستوى واجهة برمجة التطبيقات 21) والإصدارات الأحدث، للمستخدمين اختيار دليل معيّن، ما يمنح تطبيقك إذن الوصول إلى جميع الملفات والأدلة الفرعية داخل هذا الدليل.

تقدّم الأقسام التالية إرشادات حول كيفية ضبط كل حالة استخدام.

إنشاء ملف جديد

استخدِم إجراء intent‏ ACTION_CREATE_DOCUMENT لتحميل أداة اختيار الملفات في النظام والسماح للمستخدم باختيار موقع جغرافي لكتابة محتوى ملف. وتشبه هذه العملية تلك المستخدَمة في مربّعات الحوار "حفظ باسم" التي تستخدمها أنظمة التشغيل الأخرى.

ملاحظة: لا يمكن لـ "ACTION_CREATE_DOCUMENT" استبدال ملف حالي. إذا حاول تطبيقك حفظ ملف بالاسم نفسه، يضيف النظام رقمًا بين قوسين في نهاية اسم الملف.

على سبيل المثال، إذا حاول تطبيقك حفظ ملف باسم confirmation.pdf في دليل يحتوي على ملف بهذا الاسم، يحفظ النظام الملف الجديد باسم confirmation(1).pdf.

عند ضبط intent، حدِّد اسم الملف ونوع MIME، وحدِّد اختياريًا معرّف الموارد المنتظم (URI) للملف أو الدليل الذي يجب أن تعرضه أداة اختيار الملفات عند تحميلها لأول مرة باستخدام EXTRA_INITIAL_URI intent extra.

يوضّح مقتطف الرمز التالي كيفية إنشاء الغرض واستدعائه لإنشاء ملف:

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 الذي يفتح تطبيق أداة اختيار الملفات في النظام. ولعرض أنواع الملفات التي يتيحها تطبيقك فقط، حدِّد نوع MIME. يمكنك أيضًا تحديد معرّف الموارد المنتظم (URI) للملف الذي يجب أن تعرضه أداة اختيار الملفات عند تحميلها لأول مرة باستخدام الإضافة EXTRA_INITIAL_URI لطلب intent.

يوضّح مقتطف الرمز التالي كيفية إنشاء الطلب واستدعائه لفتح مستند 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 (مستوى واجهة برمجة التطبيقات 30) والإصدارات الأحدث، لا يمكنك استخدام إجراء intent ACTION_OPEN_DOCUMENT لطلب اختيار المستخدم لملفات فردية من الأدلة التالية:

  • دليل Android/data/ وجميع الأدلة الفرعية
  • دليل Android/obb/ وجميع الأدلة الفرعية

منح الإذن بالوصول إلى محتوى دليل

عادةً ما تدير تطبيقات إدارة الملفات وإنشاء الوسائط مجموعات من الملفات في التسلسل الهرمي للدليل. لتوفير هذه الإمكانية في تطبيقك، استخدِم إجراء intent‏ ACTION_OPEN_DOCUMENT_TREE الذي يسمح للمستخدم بمنح إذن الوصول إلى شجرة دليل كاملة، مع بعض الاستثناءات بدءًا من الإصدار 11 من نظام التشغيل Android (المستوى 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);
}

قيود الوصول

على الإصدار 11 من نظام التشغيل Android (المستوى 30 لواجهة برمجة التطبيقات) والإصدارات الأحدث، لا يمكنك استخدام الإجراء المقصود ACTION_OPEN_DOCUMENT_TREE لطلب الوصول إلى الأدلة التالية:

  • الدليل الجذري لوحدة التخزين الداخلية
  • الدليل الجذري لكل وحدة تخزين في بطاقة SD تعتبرها الشركة المصنّعة للجهاز موثوقة، بغض النظر عمّا إذا كانت البطاقة محاكاة أو قابلة للإزالة. وحدة التخزين الموثوقة هي التي يمكن للتطبيق الوصول إليها بنجاح في معظم الأوقات.
  • دليل "Download"

علاوةً على ذلك، في الإصدار 11 من نظام التشغيل Android (المستوى 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 باستخدام معرّف الموارد المنتظم (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.

مصدر الإدخال

يوضّح مقتطف الرمز التالي كيفية فتح عنصر 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) للوسائط المخزّنة، يمكنك بسهولة أكبر الوصول إلى ملفات الوسائط من مساحة التخزين المشتركة.

تتيح طريقة getMediaUri() استخدام معرّفات الموارد المنتظمة (URI) لـ ExternalStorageProvider. في الإصدار 12 من نظام التشغيل Android (المستوى 31 لواجهة برمجة التطبيقات) والإصدارات الأحدث، تتيح الطريقة أيضًا استخدام معرّفات الموارد المنتظمة (URI) من النوع MediaDocumentsProvider.

فتح ملف افتراضي

في الإصدار Android 7.0 (المستوى 25 لواجهة برمجة التطبيقات) والإصدارات الأحدث، يمكن لتطبيقك الاستفادة من الملفات الافتراضية التي يوفّرها "إطار عمل الوصول إلى مساحة التخزين". على الرغم من أنّ الملفات الافتراضية لا تحتوي على تمثيل ثنائي، يمكن لتطبيقك فتح محتواها من خلال فرض نوع ملف مختلف عليها أو من خلال عرض هذه الملفات باستخدام إجراء الغرض 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();
}

مراجع إضافية

لمزيد من المعلومات حول كيفية تخزين المستندات والملفات الأخرى والوصول إليها، راجع الموارد التالية.

نماذج

الفيديوهات