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

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

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

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

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

لإتاحة الوصول إلى ملفات الوسائط على الأجهزة التي تعمل بالإصدار 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.

عند ضبط 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، ما يؤدي إلى فتح تطبيق "أداة اختيار الملفات" على النظام. ولعرض أنواع الملفات التي يتوافق معها تطبيقك فقط، يجب تحديد نوع MIME. يمكنك أيضًا تحديد عنوان URL للملف الذي يجب أن تعرضه أداة اختيار الملفات عند تحميله لأول مرة باستخدام هدف 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 لواجهة برمجة التطبيقات) والإصدارات الأحدث، لا يمكنك استخدام إجراء intent 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 لواجهة برمجة التطبيقات) والإصدارات الأحدث، لا يمكنك استخدام إجراء intent ACTION_OPEN_DOCUMENT_TREE لطلب الوصول إلى الأدلّة التالية:

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

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

مصادر إضافية

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

العيّنات

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