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

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

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

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

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

لإتاحة الوصول إلى ملفات الوسائط على الأجهزة التي تعمل بالإصدار 9 من Android (المستوى 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، وحدد عند الضرورة عنوان URL للملف أو الدليل الذي يجب أن يعرضه "أداة اختيار الملفات" عند تحميلها لأول مرة باستخدام العنصر الإضافي لنية الإجراء 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 intent extra.

يوضِّح المقتطف البرمجي التالي كيفية إنشاء النية لفتح مستند 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);
}

قيود الوصول

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

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

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

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

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

يمكنك اختياريًا تحديد معرّف الموارد المنتظم للدليل الذي يجب أن يُظهره "أداة اختيار الملفات" عند تحميلها لأول مرة باستخدام العنصر الإضافي EXTRA_INITIAL_URI intent.

يوضِّح المقتطف التالي من الرمز البرمجي كيفية إنشاء النية لفتح directory وتنفيذها:

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

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

الاحتفاظ بالأذونات

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

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

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

فحص البيانات الوصفية للمستند

عندما يكون لديك معرّف الموارد المتسلسل لمستند، يمكنك الوصول إلى بياناته الوصفية. تلتقط هذه القطعة من الرمز البرمجي البيانات الوصفية لمستند يحدّده عنوان URL، وتُسجّلها:

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

تعديل مستند

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

يُعيد مقتطف الرمز البرمجي التالي كتابة محتوى المستند الذي يمثّله عنوان URL المحدّد:

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

استرداد عنوان URL مكافئ للوسائط

توفِّر الطريقة getMediaUri() معرّف موارد منتظم (URI) لمتجر الوسائط يقابل معرّف الموارد المنتظم (URI) الخاص بلموفّر المستندات. يشير معرّفَا الموارد المنتظمَين إلى العنصر الأساسي نفسه. باستخدام معرّف الموارد المنتظم (URI) لمتجر الوسائط، يمكنك الوصول بسهولة إلى ملفات الوسائط من مساحة التخزين المشتركة.

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

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

في الإصدار Android 7.0 (المستوى 25 لواجهة برمجة التطبيقات) والإصدارات الأحدث، يمكن لتطبيقك الاستفادة من الملفات الافتراضية التي يوفّرها "إطار عمل الوصول إلى مساحة التخزين". على الرغم من أنّ الملفات الافتراضية لا تتضمّن تمثيلًا ثنائيًا، يمكن لتطبيقك فتح محتوياتها من خلال إجبارها على أن تصبح نوع ملف مختلفًا أو من خلال عرض هذه الملفات باستخدام الإجراء ACTION_VIEW.

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

بعد أن يختار المستخدم ملفًا، استخدِم معرّف الموارد المنتظم في بيانات النتائج لتحديد ما إذا كان الملف افتراضيًا، كما هو موضّح في مقتطف الرمز البرمجي التالي:

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

مصادر إضافية

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

نماذج

الفيديوهات