دسترسی به اسناد و فایل‌های دیگر از فضای ذخیره‌سازی مشترک

در دستگاه‌هایی که Android نسخه ۴.۴ (میانای برنامه‌سازی کاربردی سطح ۱۹) و بالاتر را اجرا می‌کنند، برنامه‌تان می‌تواند بااستفاده از «چارچوب دسترسی به فضای ذخیره‌سازی» با ارائه‌دهنده اسناد، ازجمله حجم‌های فضای ذخیره‌سازی خارجی و فضای ذخیره‌سازی مبتنی بر ابر، تعامل داشته باشد. این چارچوب به کاربران امکان می‌دهد با انتخابگر سیستم تعامل داشته باشند تا ارائه‌دهنده اسناد را انتخاب کنند و اسناد خاص و فایل‌های دیگر را برای برنامه شما انتخاب کنند تا ایجاد، باز، یا اصلاح شود.

چون کاربر در انتخاب فایل‌ها یا دایرکتوری‌هایی که برنامه‌تان می‌تواند به آن‌ها دسترسی داشته باشد مشارکت دارد، این سازوکار به هیچ اجازه سیستمی نیاز ندارد و کنترل و حریم خصوصی کاربر بهبود می‌یابد. علاوه‌براین، این فایل‌ها که خارج از دایرکتوری مختص برنامه و خارج از فضای ذخیره‌سازی رسانه ذخیره می‌شوند، پس‌از حذف نصب برنامه در دستگاه باقی می‌مانند.

استفاده از چارچوب شامل مراحل زیر است:

  1. برنامه‌ای قصدی را فراخوانی می‌کند که حاوی کنشی مرتبط با فضای ذخیره‌سازی است. این کنش با مورد استفاده خاصی که چارچوب دردسترس قرار می‌دهد مطابقت دارد.
  2. کاربر یک انتخابگر سیستم را می‌بیند که به او اجازه می‌دهد یک ارائه‌دهنده اسناد را مرور کند و مکان یا سندی را انتخاب کند که کنش مربوط به فضای ذخیره‌سازی در آنجا انجام می‌شود.
  3. برنامه به URI که نشان‌دهنده مکان یا سند انتخاب‌شده کاربر است دسترسی خواندن و نوشتن پیدا می‌کند. بااستفاده از این URI، برنامه می‌تواند عملیات‌هایی را در مکان انتخاب‌شده انجام دهد.

برای پشتیبانی از دسترسی به فایل رسانه در دستگاه‌هایی که Android 9 (سطح میانای برنامه‌سازی کاربردی ۲۸) یا پایین‌تر را اجرا می‌کنند، اجازه READ_EXTERNAL_STORAGE را اعلام کنید و maxSdkVersion را روی 28 تنظیم کنید.

این راهنما موارد استفاده مختلفی را که چارچوب برای کار با فایل‌ها و اسناد دیگر پشتیبانی می‌کند توضیح می‌دهد. همچنین نحوه انجام عملیات در مکان انتخاب‌شده توسط کاربر را توضیح می‌دهد.

موارد استفاده برای دسترسی به اسناد و فایل‌های دیگر

«چارچوب دسترسی به فضای ذخیره‌سازی» از موارد استفاده زیر برای دسترسی به فایل‌ها و اسناد دیگر پشتیبانی می‌کند.

ایجاد فایلی جدید
کنش هدف ACTION_CREATE_DOCUMENT به کاربران اجازه می‌دهد فایل را در مکان خاصی ذخیره کنند.
باز کردن سند یا فایل
کنش هدف ACTION_OPEN_DOCUMENT به کاربران اجازه می‌دهد سند یا فایل خاصی را برای باز کردن انتخاب کنند.
اعطای دسترسی به محتوای دایرکتوری
کنش هدف ACTION_OPEN_DOCUMENT_TREE که در Android نسخه ۵.۰ (سطح API‏ ۲۱) و بالاتر دردسترس است، به کاربران اجازه می‌دهد فهرست راهنمای خاصی را انتخاب کنند و به برنامه شما اجازه دسترسی به همه فایل‌ها و زیرفهرست‌های راهنمای موجود در آن فهرست راهنما را بدهند.

بخش‌های زیر راهنمایی‌هایی درباره نحوه پیکربندی هر مورد استفاده ارائه می‌دهند.

ایجاد فایلی جدید

از کنش هدف ACTION_CREATE_DOCUMENT برای بار کردن انتخابگر فایل سیستم استفاده کنید و به کاربر اجازه دهید مکانی را برای نوشتن محتوای فایل انتخاب کند. این فرایند شبیه به فرایندی است که در کادرهای گفتگوی «ذخیره به‌عنوان» در سایر سیستم‌عامل‌ها استفاده می‌شود.

توجه: ACTION_CREATE_DOCUMENT نمی‌تواند روی فایل موجود رونویسی کند. اگر برنامه‌تان سعی کند فایلی را با همان نام ذخیره کند، سیستم عددی را در پرانتز به انتهای نام فایل اضافه می‌کند.

برای مثال، اگر برنامه‌تان سعی کند فایلی به‌نام confirmation.pdf را در دایرکتوری‌ای ذخیره کند که ازقبل فایلی با آن نام دارد، سیستم فایل جدید را با نام confirmation(1).pdf ذخیره می‌کند.

هنگام پیکربندی هدف، نام فایل و نوع MIME را مشخص کنید و به‌صورت اختیاری، URI فایل یا دایرکتوری را که انتخاب‌گر فایل باید هنگام بارگیری اولیه بااستفاده از EXTRA_INITIAL_URI هدف اضافی نمایش دهد، مشخص کنید.

تکه‌کد زیر نحوه ایجاد و فراخوانی قصد برای ایجاد فایل را نشان می‌دهد:

کاتلین

// 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)
}

جاوا

// 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 extra مشخص کنید.

تکه‌کد زیر نحوه ایجاد و فراخوانی قصد برای باز کردن سند PDF را نشان می‌دهد:

کاتلین

// 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)
}

جاوا

// 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 (سطح میانای برنامه‌سازی کاربردی ۳۰) و بالاتر، نمی‌توانید از کنش هدف ACTION_OPEN_DOCUMENT برای درخواست از کاربر برای انتخاب فایل‌های تکی از دایرکتوری‌های زیر استفاده کنید:

  • دایرکتوری Android/data/ و همه زیردایرکتوری‌ها.
  • دایرکتوری Android/obb/ و همه زیردایرکتوری‌ها.

اعطای دسترسی به محتوای دایرکتوری

برنامه‌های مدیریت فایل و ایجاد رسانه معمولاً گروه‌های فایل را در سلسله‌مراتب دایرکتوری مدیریت می‌کنند. برای ارائه این قابلیت در برنامه‌تان، از کنش هدف ACTION_OPEN_DOCUMENT_TREE استفاده کنید که به کاربر اجازه می‌دهد دسترسی به کل درخت دایرکتوری را اعطا کند، با چند استثنا که از Android 11 (سطح API‏ ۳۰) شروع می‌شود. سپس برنامه‌تان می‌تواند به هر فایلی در فهرست راهنمای انتخاب‌شده و هریک از زیرفهرست‌های راهنمای آن دسترسی پیدا کند.

هنگام استفاده از ACTION_OPEN_DOCUMENT_TREE، برنامه‌تان فقط به فایل‌های موجود در دایرکتوری‌ای که کاربر انتخاب می‌کند دسترسی پیدا می‌کند. به فایل‌های برنامه‌های دیگر که خارج از این دایرکتوری انتخاب‌شده توسط کاربر قرار دارند دسترسی ندارید. این دسترسی تحت کنترل کاربر به کاربران امکان می‌دهد دقیقاً انتخاب کنند که با هم‌رسانی کدام محتوا با برنامه شما راحت هستند.

درصورت تمایل، می‌توانید بااستفاده از EXTRA_INITIAL_URI intent extra، شناسه منبع یکسان دایرکتوری‌ای را که انتخاب‌گر فایل باید هنگام بار شدن اولیه نمایش دهد مشخص کنید.

تکه‌کد زیر نحوه ایجاد و فراخوانی قصد برای باز کردن دایرکتوری را نشان می‌دهد:

کاتلین

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

جاوا

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 (سطح میانای برنامه کاربردی ۳۰) و بالاتر، نمی‌توانید از کنش هدف ACTION_OPEN_DOCUMENT_TREE برای درخواست دسترسی به دایرکتوری‌های زیر استفاده کنید:

  • دایرکتوری ریشه حجم حافظه داخلی.
  • دایرکتوری ریشه هر حجم کارت SD که سازنده دستگاه آن را قابل‌اطمینان می‌داند، صرف‌نظر از اینکه کارت شبیه‌سازی‌شده یا جداشدنی باشد. فضای ذخیره‌سازی قابل‌اطمینان فضایی است که برنامه بتواند در اکثر مواقع با موفقیت به آن دسترسی داشته باشد.
  • فهرست راهنمای Download.

علاوه‌براین، در Android نسخه ۱۱ (سطح میانای برنامه‌سازی کاربردی ۳۰) و بالاتر، نمی‌توانید از کنش هدف ACTION_OPEN_DOCUMENT_TREE برای درخواست از کاربر برای انتخاب فایل‌های تکی از فهرست‌های راهنمای زیر استفاده کنید:

  • دایرکتوری Android/data/ و همه زیردایرکتوری‌ها.
  • دایرکتوری Android/obb/ و همه زیردایرکتوری‌ها.

انجام عملیات در مکان انتخاب‌شده

پس‌از اینکه کاربر فایل یا دایرکتوری را بااستفاده از انتخاب‌گر فایل سیستم انتخاب کرد، می‌توانید URI مورد انتخاب‌شده را بااستفاده از کد زیر در onActivityResult() بازیابی کنید:

کاتلین

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.
        }
    }
}

جاوا

@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.
        }
    }
}

با دریافت مرجع شناسه منبع یکسان مورد انتخاب‌شده، برنامه‌تان می‌تواند چندین عملیات روی آن مورد انجام دهد. برای مثال، می‌توانید به فراداده مورد دسترسی پیدا کنید، مورد را در محل ویرایش کنید، و مورد را حذف کنید.

بخش‌های زیر نشان می‌دهند که چگونه کنش‌ها را روی فایل‌هایی که کاربر انتخاب می‌کند تکمیل کنید.

تعیین عملیاتی که ارائه‌دهنده پشتیبانی می‌کند

ارائه‌دهندگان محتوای مختلف اجازه می‌دهند عملیات‌های مختلفی روی اسناد انجام شود—مانند کپی کردن سند یا مشاهده ریزعکس سند. برای تعیین اینکه ارائه‌دهنده موردنظر از کدام عملیات پشتیبانی می‌کند، مقدار Document.COLUMN_FLAGS را بررسی کنید. سپس واسط کاربر برنامه‌تان می‌تواند فقط گزینه‌هایی را نشان دهد که ارائه‌دهنده پشتیبانی می‌کند.

حفظ اجازه‌ها

وقتی برنامه‌تان فایلی را برای خواندن یا نوشتن باز می‌کند، سیستم به برنامه‌تان اجازه URI برای آن فایل می‌دهد که تا زمانی که دستگاه کاربر بازراه‌اندازی شود ادامه دارد. اما فرض کنید برنامه شما یک برنامه ویرایش تصویر است و می‌خواهید کاربران بتوانند مستقیماً از برنامه شما به ۵ تصویری که اخیراً ویرایش کرده‌اند دسترسی داشته باشند. اگر دستگاه کاربر بازراه‌اندازی شده باشد، باید کاربر را به انتخاب‌گر سیستم برگردانید تا فایل‌ها را پیدا کند.

برای حفظ دسترسی به فایل‌ها در سراسر بازراه‌اندازی‌های دستگاه و ایجاد تجربه کاربری بهتر، برنامه شما می‌تواند اعطای اجازه URI ماندگار را که سیستم ارائه می‌دهد «بگیرد»، همان‌طور که در تکه‌کد زیر نشان داده شده است:

کاتلین

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)

جاوا

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 را می‌گیرد و آن را ثبت می‌کند:

کاتلین

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")
        }
    }
}

جاوا

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 آن باز کرد:

کاتلین

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
}

جاوا

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 آن باز کرد. در این قطعه کد، خطوط فایل در یک رشته خوانده می‌شوند:

کاتلین

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

جاوا

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 داده‌شده را رونویسی می‌کند:

کاتلین

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

جاوا

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

حذف کردن سند

اگر شناسه منبع یکسان برای سند را دارید و Document.COLUMN_FLAGS سند حاوی SUPPORTS_DELETE است، می‌توانید سند را حذف کنید. برای مثال:

کاتلین

DocumentsContract.deleteDocument(applicationContext.contentResolver, uri)

جاوا

DocumentsContract.deleteDocument(applicationContext.contentResolver, uri);

بازیابی URI رسانه معادل

روش getMediaUri() یک URI فروشگاه رسانه ارائه می‌دهد که معادل URI ارائه‌دهنده اسناد داده‌شده است. این ۲ شناسه منبع یکسان به یک مورد اساسی اشاره دارند. بااستفاده از شناسه منبع یکسان فضای ذخیره‌سازی رسانه‌ها، می‌توانید راحت‌تر به فایل‌های رسانه‌ای در فضای ذخیره‌سازی مشترک دسترسی پیدا کنید.

روش getMediaUri() از شناسه منبع یکسان ExternalStorageProvider پشتیبانی می‌کند. در Android 12 (سطح میانای برنامه‌سازی کاربردی ۳۱) و بالاتر، این روش از MediaDocumentsProvider URI نیز پشتیبانی می‌کند.

باز کردن فایل مجازی

در Android 7.0 (سطح API‏ ۲۵) و بالاتر، برنامه‌تان می‌تواند از فایل‌های مجازی که «چارچوب دسترسی به فضای ذخیره‌سازی» دردسترس قرار می‌دهد استفاده کند. هرچند فایل‌های مجازی نمایش دودویی ندارند، برنامه‌تان می‌تواند محتوای آن‌ها را با تبدیل اجباری به نوع فایل دیگری یا با مشاهده آن فایل‌ها بااستفاده از کنش قصد ACTION_VIEW باز کند.

برای باز کردن فایل‌های مجازی، برنامه کارخواه شما باید منطق ویژه‌ای برای مدیریت آن‌ها داشته باشد. اگر می‌خواهید نمایش بایتی فایل را دریافت کنید (مثلاً برای پیش‌دید فایل)، باید نوع MIME جایگزینی را از ارائه‌دهنده اسناد درخواست کنید.

پس‌از اینکه کاربر انتخابی انجام داد، از URI در داده‌های نتایج استفاده کنید تا تعیین کنید آیا فایل مجازی است یا نه، همان‌طور که در تکه‌کد زیر نشان داده شده است:

کاتلین

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
}

جاوا

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

پس‌از اینکه تأیید کردید سند یک فایل مجازی است، می‌توانید فایل را به یک نوع رسانه جایگزین، مانند "image/png"، تبدیل کنید. تکه‌کد زیر نشان می‌دهد که چگونه می‌توان بررسی کرد که آیا یک فایل مجازی می‌تواند به‌عنوان یک تصویر نمایش داده شود یا خیر، و اگر چنین باشد، یک جریان ورودی از فایل مجازی دریافت می‌کند:

کاتلین

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

جاوا

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

منابع بیشتر

برای اطلاعات بیشتر درباره نحوه ذخیره و دسترسی به اسناد و فایل‌های دیگر، به منابع زیر مراجعه کنید.

نمونه‌ها

ویدیوها