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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

  • دليل 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 لهذا الملف، ويبقى هذا الإذن صالحًا إلى أن تتم إعادة تشغيل جهاز المستخدم. على الرغم من ذلك، لنفترض أنّ تطبيقك مخصّص لتعديل الصور، وتريد أن يتمكّن المستخدمون من الوصول إلى الصور الخمس التي قاموا بتعديلها مؤخرًا، من تطبيقك مباشرةً. وإذا تمت إعادة تشغيل جهاز المستخدم، عليك إرساله مرة أخرى إلى منتقي النظام للعثور على الملفات.

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

إدخال البث

يعرض مقتطف الرمز التالي كيفية فتح كائن EnterStream وفقًا لمعرّف الموارد المنتظم (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();
}

تعديل مستند

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

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

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

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

في نظام التشغيل 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();
}

مراجع إضافية

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

العيّنات

الفيديوهات الطويلة