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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

کاتلین

// 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 مشخص کنید. همچنین، می‌توانید به صورت اختیاری با استفاده از تابع EXTRA_INITIAL_URI آدرس اینترنتی (URI) فایلی را که انتخابگر فایل باید هنگام اولین بارگذاری نمایش دهد، مشخص کنید.

قطعه کد زیر نحوه ایجاد و فراخوانی intent برای باز کردن یک سند 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);
}

محدودیت‌های دسترسی

در اندروید ۱۱ (سطح API 30) و بالاتر، نمی‌توانید از اکشن اینتنت ACTION_OPEN_DOCUMENT برای درخواست انتخاب فایل‌های تکی از دایرکتوری‌های زیر توسط کاربر استفاده کنید:

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

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

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

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

به صورت اختیاری، می‌توانید با استفاده از اینتنت EXTRA_INITIAL_URI آدرس اینترنتی (URI) دایرکتوری که انتخابگر فایل باید هنگام اولین بارگذاری نمایش دهد را مشخص کنید.

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

کاتلین

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

محدودیت‌های دسترسی

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

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

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

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

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

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

کاتلین

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

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

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

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

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

مجوزهای دائمی

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

برای حفظ دسترسی به فایل‌ها در هنگام راه‌اندازی مجدد دستگاه و ایجاد یک تجربه کاربری بهتر، برنامه شما می‌تواند مجوز دسترسی پایدار 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 یک سند را دارید، به فراداده‌های آن دسترسی پیدا می‌کنید. این قطعه کد، فراداده‌های سندی را که توسط 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();
    }
}

حذف یک سند

اگر آدرس اینترنتی (URI) یک سند را دارید و Document.COLUMN_FLAGS سند حاوی SUPPORTS_DELETE است، می‌توانید سند را حذف کنید. برای مثال:

کاتلین

DocumentsContract.deleteDocument(applicationContext.contentResolver, uri)

جاوا

DocumentsContract.deleteDocument(applicationContext.contentResolver, uri);

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

متد getMediaUri() یک URI مربوط به حافظه رسانه ارائه می‌دهد که معادل URI مربوط به ارائه‌دهنده اسناد داده شده است. این دو URI به یک آیتم اصلی اشاره دارند. با استفاده از URI مربوط به حافظه رسانه، می‌توانید راحت‌تر به فایل‌های رسانه‌ای از حافظه مشترک دسترسی پیدا کنید .

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

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

در اندروید ۷.۰ (سطح API 25) و بالاتر، برنامه شما می‌تواند از فایل‌های مجازی که چارچوب دسترسی به ذخیره‌سازی (Storage Access Framework) در دسترس قرار می‌دهد، استفاده کند. اگرچه فایل‌های مجازی نمایش دودویی ندارند، برنامه شما می‌تواند محتویات آنها را با مجبور کردن آنها به تبدیل به یک نوع فایل متفاوت یا با مشاهده آن فایل‌ها با استفاده از اکشن 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;
}

پس از تأیید اینکه سند یک فایل مجازی است، می‌توانید فایل را به یک نوع MIME جایگزین، مانند "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();
}

منابع اضافی

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

نمونه‌ها

ویدیوها