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

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

انتخاب‌گر عکس

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

فروشگاه رسانه

برای تعامل با انتزاع فروشگاه رسانه، از شیء ContentResolver استفاده کنید که از بافت برنامه‌تان بازیابی می‌کنید:

کاتلین

val projection = arrayOf(media-database-columns-to-retrieve)
val selection = sql-where-clause-with-placeholder-variables
val selectionArgs = values-of-placeholder-variables
val sortOrder = sql-order-by-clause

applicationContext.contentResolver.query(
    MediaStore.media-type.Media.EXTERNAL_CONTENT_URI,
    projection,
    selection,
    selectionArgs,
    sortOrder
)?.use { cursor ->
    while (cursor.moveToNext()) {
        // Use an ID column from the projection to get
        // a URI representing the media item itself.
    }
}

جاوا

String[] projection = new String[] {
        media-database-columns-to-retrieve
};
String selection = sql-where-clause-with-placeholder-variables;
String[] selectionArgs = new String[] {
        values-of-placeholder-variables
};
String sortOrder = sql-order-by-clause;

Cursor cursor = getApplicationContext().getContentResolver().query(
    MediaStore.media-type.Media.EXTERNAL_CONTENT_URI,
    projection,
    selection,
    selectionArgs,
    sortOrder
);

while (cursor.moveToNext()) {
    // Use an ID column from the projection to get
    // a URI representing the media item itself.
}

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

  • تصاویر، ازجمله عکس‌ها و نماگرفت‌ها، که در فهرست‌های راهنمای DCIM/ و Pictures/ ذخیره شده‌اند. سیستم این فایل‌ها را به جدول MediaStore.Images اضافه می‌کند.
  • ویدیوها که در فهرست‌های راهنمای DCIM/، Movies/، و Pictures/ ذخیره شده‌اند. سیستم این فایل‌ها را به جدول MediaStore.Video اضافه می‌کند.
  • فایل‌های صوتی که در دایرکتوری‌های Alarms/، Audiobooks/، Music/، Notifications/، Podcasts/، و Ringtones/ ذخیره شده‌اند. علاوه‌براین، سیستم فهرست‌های پخش صوتی را که در دایرکتوری‌های Music/ یا Movies/ هستند و همچنین ضبط‌های گفتاری را که در دایرکتوری Recordings/ هستند تشخیص می‌دهد. سیستم این فایل‌ها را به جدول MediaStore.Audio اضافه می‌کند. دایرکتوری Recordings/ در Android 11 (سطح میانای برنامه‌سازی کاربردی ۳۰) و نسخه‌های پایین‌تر دردسترس نیست.
  • فایل‌های بارگیری‌شده که در فهرست راهنمای Download/ ذخیره شده‌اند. در دستگاه‌هایی که Android 10 (سطح میانای برنامه‌سازی کاربردی ۲۹) و بالاتر را اجرا می‌کنند، این فایل‌ها در جدول MediaStore.Downloads ذخیره می‌شوند. این جدول در Android 9 (سطح میانای برنامه‌سازی کاربردی ۲۸) و نسخه‌های پایین‌تر دردسترس نیست.

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

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

درخواست اجازه‌های لازم

پیش‌از انجام عملیات روی فایل‌های رسانه‌ای، مطمئن شوید برنامه‌تان اجازه‌هایی را که برای دسترسی به این فایل‌ها نیاز دارد اعلام کرده باشد. اما مراقب باشید اجازه‌هایی را که برنامه‌تان نیاز ندارد یا از آن‌ها استفاده نمی‌کند اعلام نکنید.

اجازه‌های فضای ذخیره‌سازی

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

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

در دستگاه‌هایی که Android نسخه ۱۰ یا بالاتر دارند، برای دسترسی و اصلاح فایل‌های رسانه‌ای که متعلق به برنامه شما است، ازجمله فایل‌های موجود در مجموعه MediaStore.Downloads، به اجازه‌های مربوط به فضای ذخیره‌سازی نیاز ندارید. برای مثال، اگر درحال توسعه برنامه دوربین هستید، برای دسترسی به عکس‌هایی که می‌گیرد، نیازی به درخواست اجازه‌های مربوط به فضای ذخیره‌سازی ندارید، زیرا برنامه شما مالک تصاویری است که در فضای ذخیره‌سازی رسانه می‌نویسید.

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

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

تا زمانی که فایلی ازطریق پُرسمان‌های MediaStore.Images، MediaStore.Video، یا MediaStore.Audio قابل‌مشاهده باشد، بااستفاده از پُرسمان MediaStore.Files نیز قابل‌مشاهده است.

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

<!-- Required only if your app needs to access images or photos
     that other apps created. -->
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES" />

<!-- Required only if your app needs to access videos
     that other apps created. -->
<uses-permission android:name="android.permission.READ_MEDIA_VIDEO" />

<!-- Required only if your app needs to access audio files
     that other apps created. -->
<uses-permission android:name="android.permission.READ_MEDIA_AUDIO" />

<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>

<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"
                 android:maxSdkVersion="29" />

برای برنامه‌هایی که در دستگاه‌های قدیمی اجرا می‌شوند، اجازه‌های اضافی لازم است

اگر برنامه‌تان در دستگاهی استفاده می‌شود که Android نسخه ۹ یا پایین‌تر را اجرا می‌کند، یا اگر برنامه‌تان موقتاً از فضای ذخیره‌سازی محدودشده انصراف داده است، باید برای دسترسی به هر فایل رسانه‌ای، اجازه READ_EXTERNAL_STORAGE را درخواست کنید. اگر می‌خواهید فایل‌های رسانه‌ای را اصلاح کنید، باید اجازه WRITE_EXTERNAL_STORAGE را نیز درخواست کنید.

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

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

اجازه مکان رسانه

اگر برنامه شما Android 10 (سطح API‏ ۲۹) یا بالاتر را هدف قرار می‌دهد و نیاز دارد فراداده EXIF ویرایش‌نشده را از عکس‌ها بازیابی کند، باید اجازه ACCESS_MEDIA_LOCATION را در مانیفست برنامه‌تان اعلام کنید، سپس این اجازه را در زمان اجرا درخواست کنید.

بررسی به‌روزرسانی‌های فروشگاه رسانه

برای دسترسی مطمئن‌تر به فایل‌های رسانه‌ای، به‌ویژه اگر برنامه‌تان URI یا داده‌های فضای ذخیره‌سازی رسانه‌ها را ذخیره می‌کند، بررسی کنید که آیا نسخه فضای ذخیره‌سازی رسانه‌ها نسبت‌به آخرین باری که داده‌های رسانه‌ای را همگام‌سازی کرده‌اید تغییر کرده است یا نه. برای انجام این بررسی برای به‌روزرسانی‌ها، با getVersion() تماس بگیرید. نسخه برگردانده‌شده رشته‌ای منحصربه‌فرد است که هرگاه فضای ذخیره‌سازی رسانه به‌طور قابل‌توجهی تغییر کند، تغییر می‌کند. اگر نسخه برگردانده‌شده با آخرین نسخه همگام‌سازی‌شده متفاوت است، حافظه پنهان رسانه برنامه‌تان را دوباره اسکن و همگام‌سازی کنید.

این بررسی را در زمان راه‌اندازی فرایند برنامه تکمیل کنید. نیازی نیست هربار که از فضای ذخیره‌سازی رسانه پُرسمان می‌کنید، نسخه را بررسی کنید.

هیچ جزئیات پیاده‌سازی‌ای را درباره شماره نسخه فرض نکنید.

پرس‌وجو کردن مجموعه رسانه

برای پیدا کردن رسانه‌ای که مجموعه خاصی از شرایط را برآورده می‌کند، مثلاً مدت‌زمان ۵ دقیقه یا بیشتر، از عبارت انتخاب SQL-مانند مشابه آنچه در تکه‌کد زیر نشان داده شده است استفاده کنید:

کاتلین

// Need the READ_EXTERNAL_STORAGE permission if accessing video files that your
// app didn't create.

// Container for information about each video.
data class Video(val uri: Uri,
    val name: String,
    val duration: Int,
    val size: Int
)
val videoList = mutableListOf<Video>()

val collection =
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
        MediaStore.Video.Media.getContentUri(
            MediaStore.VOLUME_EXTERNAL
        )
    } else {
        MediaStore.Video.Media.EXTERNAL_CONTENT_URI
    }

val projection = arrayOf(
    MediaStore.Video.Media._ID,
    MediaStore.Video.Media.DISPLAY_NAME,
    MediaStore.Video.Media.DURATION,
    MediaStore.Video.Media.SIZE
)

// Show only videos that are at least 5 minutes in duration.
val selection = "${MediaStore.Video.Media.DURATION} >= ?"
val selectionArgs = arrayOf(
    TimeUnit.MILLISECONDS.convert(5, TimeUnit.MINUTES).toString()
)

// Display videos in alphabetical order based on their display name.
val sortOrder = "${MediaStore.Video.Media.DISPLAY_NAME} ASC"

val query = ContentResolver.query(
    collection,
    projection,
    selection,
    selectionArgs,
    sortOrder
)
query?.use { cursor ->
    // Cache column indices.
    val idColumn = cursor.getColumnIndexOrThrow(MediaStore.Video.Media._ID)
    val nameColumn =
            cursor.getColumnIndexOrThrow(MediaStore.Video.Media.DISPLAY_NAME)
    val durationColumn =
            cursor.getColumnIndexOrThrow(MediaStore.Video.Media.DURATION)
    val sizeColumn = cursor.getColumnIndexOrThrow(MediaStore.Video.Media.SIZE)

    while (cursor.moveToNext()) {
        // Get values of columns for a given video.
        val id = cursor.getLong(idColumn)
        val name = cursor.getString(nameColumn)
        val duration = cursor.getInt(durationColumn)
        val size = cursor.getInt(sizeColumn)

        val contentUri: Uri = ContentUris.withAppendedId(
            MediaStore.Video.Media.EXTERNAL_CONTENT_URI,
            id
        )

        // Stores column values and the contentUri in a local object
        // that represents the media file.
        videoList += Video(contentUri, name, duration, size)
    }
}

جاوا

// Need the READ_EXTERNAL_STORAGE permission if accessing video files that your
// app didn't create.

// Container for information about each video.
class Video {
    private final Uri uri;
    private final String name;
    private final int duration;
    private final int size;

    public Video(Uri uri, String name, int duration, int size) {
        this.uri = uri;
        this.name = name;
        this.duration = duration;
        this.size = size;
    }
}
List<Video> videoList = new ArrayList<Video>();

Uri collection;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
    collection = MediaStore.Video.Media.getContentUri(MediaStore.VOLUME_EXTERNAL);
} else {
    collection = MediaStore.Video.Media.EXTERNAL_CONTENT_URI;
}

String[] projection = new String[] {
    MediaStore.Video.Media._ID,
    MediaStore.Video.Media.DISPLAY_NAME,
    MediaStore.Video.Media.DURATION,
    MediaStore.Video.Media.SIZE
};
String selection = MediaStore.Video.Media.DURATION +
        " >= ?";
String[] selectionArgs = new String[] {
    String.valueOf(TimeUnit.MILLISECONDS.convert(5, TimeUnit.MINUTES));
};
String sortOrder = MediaStore.Video.Media.DISPLAY_NAME + " ASC";

try (Cursor cursor = getApplicationContext().getContentResolver().query(
    collection,
    projection,
    selection,
    selectionArgs,
    sortOrder
)) {
    // Cache column indices.
    int idColumn = cursor.getColumnIndexOrThrow(MediaStore.Video.Media._ID);
    int nameColumn =
            cursor.getColumnIndexOrThrow(MediaStore.Video.Media.DISPLAY_NAME);
    int durationColumn =
            cursor.getColumnIndexOrThrow(MediaStore.Video.Media.DURATION);
    int sizeColumn = cursor.getColumnIndexOrThrow(MediaStore.Video.Media.SIZE);

    while (cursor.moveToNext()) {
        // Get values of columns for a given video.
        long id = cursor.getLong(idColumn);
        String name = cursor.getString(nameColumn);
        int duration = cursor.getInt(durationColumn);
        int size = cursor.getInt(sizeColumn);

        Uri contentUri = ContentUris.withAppendedId(
                MediaStore.Video.Media.EXTERNAL_CONTENT_URI, id);

        // Stores column values and the contentUri in a local object
        // that represents the media file.
        videoList.add(new Video(contentUri, name, duration, size));
    }
}

هنگام اجرای چنین پُرسمانی در برنامه‌تان، موارد زیر را درنظر داشته باشید:

  • روش query() را در رشته عامل فراخوانی کنید.
  • شاخص‌های ستون را ذخیره کنید تا لازم نباشد getColumnIndexOrThrow() را هربار که ردیفی را از نتیجه پُرسمان پردازش می‌کنید فراخوانی کنید.
  • همان‌طور که در این مثال نشان داده شده است، شناسه را به شناسه منبع یکسان محتوا اضافه کنید.
  • دستگاه‌های دارای Android نسخه ۱۰ و بالاتر به نام‌های ستونی نیاز دارند که در MediaStore API تعریف شده‌اند. اگر کتابخانه وابسته‌ای در برنامه‌تان انتظار نام ستونی را دارد که در میانای برنامه‌سازی کاربردی تعریف نشده است، مثلاً "MimeType"، از CursorWrapper استفاده کنید تا نام ستون را به‌طور پویا در فرایند برنامه‌تان ترجمه کنید.

بار کردن تصاویر کوچک فایل

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

برای بار کردن تصویر کوچک برای فایل رسانه داده‌شده، از loadThumbnail() استفاده کنید و اندازه تصویر کوچکی را که می‌خواهید بار شود، همان‌طور که در تکه‌کد زیر نشان داده شده است، ارسال کنید:

کاتلین

// Load thumbnail of a specific media item.
val thumbnail: Bitmap =
        applicationContext.contentResolver.loadThumbnail(
        content-uri, Size(640, 480), null)

جاوا

// Load thumbnail of a specific media item.
Bitmap thumbnail =
        getApplicationContext().getContentResolver().loadThumbnail(
        content-uri, new Size(640, 480), null);

باز کردن فایل رسانه‌ای

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

توصیف‌گر فایل

برای باز کردن فایل رسانه‌ای بااستفاده از توصیف‌گر فایل، از منطقی مشابه آنچه در تکه‌کد زیر نشان داده شده است استفاده کنید:

کاتلین

// Open a specific media item using ParcelFileDescriptor.
val resolver = applicationContext.contentResolver

// "rw" for read-and-write.
// "rwt" for truncating or overwriting existing file contents.
val readOnlyMode = "r"
resolver.openFileDescriptor(content-uri, readOnlyMode).use { pfd ->
    // Perform operations on "pfd".
}

جاوا

// Open a specific media item using ParcelFileDescriptor.
ContentResolver resolver = getApplicationContext()
        .getContentResolver();

// "rw" for read-and-write.
// "rwt" for truncating or overwriting existing file contents.
String readOnlyMode = "r";
try (ParcelFileDescriptor pfd =
        resolver.openFileDescriptor(content-uri, readOnlyMode)) {
    // Perform operations on "pfd".
} catch (IOException e) {
    e.printStackTrace();
}

جاری‌سازی فایل

برای باز کردن فایل رسانه بااستفاده از جاری‌سازی فایل، از منطقی مشابه آنچه در تکه‌کد زیر نشان داده شده است استفاده کنید:

کاتلین

// Open a specific media item using InputStream.
val resolver = applicationContext.contentResolver
resolver.openInputStream(content-uri).use { stream ->
    // Perform operations on "stream".
}

جاوا

// Open a specific media item using InputStream.
ContentResolver resolver = getApplicationContext()
        .getContentResolver();
try (InputStream stream = resolver.openInputStream(content-uri)) {
    // Perform operations on "stream".
}

مسیرهای مستقیم فایل

برای کمک به عملکرد روان‌تر برنامه‌تان با کتابخانه‌های رسانه‌ای طرف سوم، Android 11 (سطح API‏ 30) و بالاتر به شما امکان می‌دهد از میاناهای برنامه‌سازی کاربردی دیگری به‌غیراز میانای برنامه‌سازی کاربردی MediaStore برای دسترسی به فایل‌های رسانه‌ای از فضای ذخیره‌سازی مشترک استفاده کنید. درعوض می‌توانید مستقیماً به فایل‌های رسانه‌ای دسترسی پیدا کنید بااستفاده از یکی از میاناهای برنامه‌سازی کاربردی زیر:

  • File API
  • کتابخانه‌های بومی، مثل fopen()

اگر هیچ اجازه مرتبط با فضای ذخیره‌سازی ندارید، می‌توانید بااستفاده از میانای برنامه‌سازی کاربردی File به فایل‌های موجود در دایرکتوری مختص برنامه‌تان و همچنین فایل‌های رسانه‌ای منسوب به برنامه‌تان دسترسی پیدا کنید.

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

برای دسترسی به فایل‌های دیگر در فضای ذخیره‌سازی مشترک در دستگاهی که Android 10 (سطح میانای برنامه‌سازی کاربردی ۲۹) را اجرا می‌کند، توصیه می‌کنیم با تنظیم requestLegacyExternalStorage روی true در فایل مانیفست برنامه‌تان، به‌طور موقت از فضای ذخیره‌سازی محدودشده انصراف دهید. برای دسترسی به فایل‌های رسانه‌ای بااستفاده از روش‌های فایل‌های بومی در Android 10، باید اجازه READ_EXTERNAL_STORAGE را نیز درخواست کنید.

ملاحظات هنگام دسترسی به محتوای رسانه

هنگام دسترسی به محتوای رسانه، ملاحظات مطرح‌شده در بخش‌های زیر را درنظر داشته باشید.

داده‌های موجود در حافظه پنهان

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

عملکرد

وقتی بااستفاده از مسیرهای مستقیم فایل، خواندن متوالی فایل‌های رسانه‌ای را انجام می‌دهید، عملکرد با عملکرد MediaStore API قابل‌مقایسه است.

اما وقتی خواندن و نوشتن تصادفی فایل‌های رسانه‌ای را بااستفاده از مسیرهای مستقیم فایل انجام می‌دهید، این فرایند می‌تواند تا دو برابر کندتر باشد. در این شرایط، توصیه می‌کنیم به‌جای آن از MediaStore API استفاده کنید.

ستون داده‌ها

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

ازسوی‌دیگر، برای ایجاد یا به‌روزرسانی فایل رسانه‌ای، از مقدار ستون DATA استفاده نکنید. درعوض، از مقادیر ستون‌های DISPLAY_NAME و RELATIVE_PATH استفاده کنید.

فضاهای ذخیره‌سازی

برنامه‌هایی که Android نسخه ۱۰ یا بالاتر را هدف قرار می‌دهند می‌توانند به نام یکتایی که سیستم به هر فضای ذخیره‌سازی خارجی اختصاص می‌دهد دسترسی داشته باشند. این سیستم نام‌گذاری به شما کمک می‌کند محتوا را به‌طور کارآمد سازمان‌دهی و نمایه‌سازی کنید و به شما امکان می‌دهد کنترل کنید فایل‌های رسانه‌ای جدید کجا ذخیره شوند.

به‌ویژه خوب است که میزان‌های صدای زیر را به‌خاطر بسپارید:

  • VOLUME_EXTERNAL نمایی از همه حجم‌های فضای ذخیره‌سازی مشترک در دستگاه ارائه می‌دهد. می‌توانید محتوای این جلد مصنوعی را بخوانید، اما نمی‌توانید محتوا را تغییر دهید.
  • VOLUME_EXTERNAL_PRIMARY نشان‌دهنده فضای ذخیره‌سازی مشترک اصلی در دستگاه است. می‌توانید محتوای این جلد را بخوانید و اصلاح کنید.

می‌توانید با فراخوانی MediaStore.getExternalVolumeNames()، جلدهای دیگر را کاوش کنید:

کاتلین

val volumeNames: Set<String> = MediaStore.getExternalVolumeNames(context)
val firstVolumeName = volumeNames.iterator().next()

جاوا

Set<String> volumeNames = MediaStore.getExternalVolumeNames(context);
String firstVolumeName = volumeNames.iterator().next();

مکانی که رسانه در آن ضبط شده است

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

نحوه دسترسی به این اطلاعات مکان در برنامه‌تان به این بستگی دارد که آیا باید به اطلاعات مکان برای عکس دسترسی داشته باشید یا برای ویدیو.

عکس‌ها

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

  1. اجازه ACCESS_MEDIA_LOCATION را در مانیفست برنامه‌تان درخواست کنید.
  2. از شیء MediaStore، بایت‌های دقیق عکس را با فراخوانی setRequireOriginal() و ارسال URI عکس دریافت کنید، همان‌طور که در تکه‌کد زیر نشان داده شده است:

    کاتلین

    val photoUri: Uri = Uri.withAppendedPath(
            MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
            cursor.getString(idColumnIndex)
    )
    
    // Get location data using the Exifinterface library.
    // Exception occurs if ACCESS_MEDIA_LOCATION permission isn't granted.
    photoUri = MediaStore.setRequireOriginal(photoUri)
    contentResolver.openInputStream(photoUri)?.use { stream ->
        ExifInterface(stream).run {
            // If lat/long is null, fall back to the coordinates (0, 0).
            val latLong = latLong ?: doubleArrayOf(0.0, 0.0)
        }
    }

    جاوا

    Uri photoUri = Uri.withAppendedPath(
            MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
            cursor.getString(idColumnIndex));
    
    final double[] latLong;
    
    // Get location data using the Exifinterface library.
    // Exception occurs if ACCESS_MEDIA_LOCATION permission isn't granted.
    photoUri = MediaStore.setRequireOriginal(photoUri);
    InputStream stream = getContentResolver().openInputStream(photoUri);
    if (stream != null) {
        ExifInterface exifInterface = new ExifInterface(stream);
        double[] returnedLatLong = exifInterface.getLatLong();
    
        // If lat/long is null, fall back to the coordinates (0, 0).
        latLong = returnedLatLong != null ? returnedLatLong : new double[2];
    
        // Don't reuse the stream associated with
        // the instance of "ExifInterface".
        stream.close();
    } else {
        // Failed to load the stream, so return the coordinates (0, 0).
        latLong = new double[2];
    }

ویدیوها

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

کاتلین

val retriever = MediaMetadataRetriever()
val context = applicationContext

// Find the videos that are stored on a device by querying the video collection.
val query = ContentResolver.query(
    collection,
    projection,
    selection,
    selectionArgs,
    sortOrder
)
query?.use { cursor ->
    val idColumn = cursor.getColumnIndexOrThrow(MediaStore.Video.Media._ID)
    while (cursor.moveToNext()) {
        val id = cursor.getLong(idColumn)
        val videoUri: Uri = ContentUris.withAppendedId(
            MediaStore.Video.Media.EXTERNAL_CONTENT_URI,
            id
        )
        extractVideoLocationInfo(videoUri)
    }
}

private fun extractVideoLocationInfo(videoUri: Uri) {
    try {
        retriever.setDataSource(context, videoUri)
    } catch (e: RuntimeException) {
        Log.e(APP_TAG, "Cannot retrieve video file", e)
    }
    // Metadata uses a standardized format.
    val locationMetadata: String? =
            retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_LOCATION)
}

جاوا

MediaMetadataRetriever retriever = new MediaMetadataRetriever();
Context context = getApplicationContext();

// Find the videos that are stored on a device by querying the video collection.
try (Cursor cursor = context.getContentResolver().query(
    collection,
    projection,
    selection,
    selectionArgs,
    sortOrder
)) {
    int idColumn = cursor.getColumnIndexOrThrow(MediaStore.Video.Media._ID);
    while (cursor.moveToNext()) {
        long id = cursor.getLong(idColumn);
        Uri videoUri = ContentUris.withAppendedId(
                MediaStore.Video.Media.EXTERNAL_CONTENT_URI, id);
        extractVideoLocationInfo(videoUri);
    }
}

private void extractVideoLocationInfo(Uri videoUri) {
    try {
        retriever.setDataSource(context, videoUri);
    } catch (RuntimeException e) {
        Log.e(APP_TAG, "Cannot retrieve video file", e);
    }
    // Metadata uses a standardized format.
    String locationMetadata = retriever.extractMetadata(
            MediaMetadataRetriever.METADATA_KEY_LOCATION);
}

هم‌رسانی

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

برای هم‌رسانی فایل‌های رسانه‌ای، از content:// URI استفاده کنید، همان‌طور که در راهنمای ایجاد ارائه‌دهنده محتوا توصیه شده است.

ارجاع برنامه فایل‌های رسانه‌ای

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

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

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

افزودن مورد

برای افزودن فایل رسانه‌ای به مجموعه موجود، از کدی مشابه کد زیر استفاده کنید. این تکه‌کد به فضای ذخیره‌سازی VOLUME_EXTERNAL_PRIMARY در دستگاه‌های دارای Android نسخه ۱۰ یا بالاتر دسترسی پیدا می‌کند. دلیلش این است که در این دستگاه‌ها، فقط درصورتی می‌توانید محتوای درایو را تغییر دهید که درایو اصلی باشد، همان‌طور که در بخش درایوهای فضای ذخیره‌سازی توضیح داده شده است.

کاتلین

// Add a specific media item.
val resolver = applicationContext.contentResolver

// Find all audio files on the primary external storage device.
val audioCollection =
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
        MediaStore.Audio.Media.getContentUri(
            MediaStore.VOLUME_EXTERNAL_PRIMARY
        )
    } else {
        MediaStore.Audio.Media.EXTERNAL_CONTENT_URI
    }

// Publish a new song.
val newSongDetails = ContentValues().apply {
    put(MediaStore.Audio.Media.DISPLAY_NAME, "My Song.mp3")
}

// Keep a handle to the new song's URI in case you need to modify it
// later.
val myFavoriteSongUri = resolver
        .insert(audioCollection, newSongDetails)

جاوا

// Add a specific media item.
ContentResolver resolver = getApplicationContext()
        .getContentResolver();

// Find all audio files on the primary external storage device.
Uri audioCollection;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
    audioCollection = MediaStore.Audio.Media
            .getContentUri(MediaStore.VOLUME_EXTERNAL_PRIMARY);
} else {
    audioCollection = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI;
}

// Publish a new song.
ContentValues newSongDetails = new ContentValues();
newSongDetails.put(MediaStore.Audio.Media.DISPLAY_NAME,
        "My Song.mp3");

// Keep a handle to the new song's URI in case you need to modify it
// later.
Uri myFavoriteSongUri = resolver
        .insert(audioCollection, newSongDetails);

روشن/خاموش کردن وضعیت معلقه برای فایل‌های رسانه‌ای

اگر برنامه‌تان عملیات‌های بالقوه زمان‌بری را انجام می‌دهد، مثل نوشتن در فایل‌های رسانه‌ای، مفید است که دسترسی انحصاری به فایل درحال پردازش داشته باشید. در دستگاه‌هایی که Android نسخه ۱۰ یا بالاتر را اجرا می‌کنند، برنامه‌تان می‌تواند با تنظیم مقدار پرچم IS_PENDING روی ۱، این دسترسی انحصاری را دریافت کند. فقط برنامه شما می‌تواند فایل را مشاهده کند تا زمانی که برنامه شما مقدار IS_PENDING را به ۰ برگرداند.

تکه‌کد زیر براساس تکه‌کد قبلی ساخته شده است. این قطعه کد نشان می‌دهد که چگونه از پرچم IS_PENDING هنگام ذخیره کردن آهنگ طولانی در دایرکتوری مربوط به مجموعه MediaStore.Audio استفاده کنید:

کاتلین

// Add a media item that other apps don't see until the item is
// fully written to the media store.
val resolver = applicationContext.contentResolver

// Find all audio files on the primary external storage device.
val audioCollection =
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
        MediaStore.Audio.Media.getContentUri(
            MediaStore.VOLUME_EXTERNAL_PRIMARY
        )
    } else {
        MediaStore.Audio.Media.EXTERNAL_CONTENT_URI
    }

val songDetails = ContentValues().apply {
    put(MediaStore.Audio.Media.DISPLAY_NAME, "My Workout Playlist.mp3")
    put(MediaStore.Audio.Media.IS_PENDING, 1)
}

val songContentUri = resolver.insert(audioCollection, songDetails)

// "w" for write.
resolver.openFileDescriptor(songContentUri, "w", null).use { pfd ->
    // Write data into the pending audio file.
}

// Now that you're finished, release the "pending" status and let other apps
// play the audio track.
songDetails.clear()
songDetails.put(MediaStore.Audio.Media.IS_PENDING, 0)
resolver.update(songContentUri, songDetails, null, null)

جاوا

// Add a media item that other apps don't see until the item is
// fully written to the media store.
ContentResolver resolver = getApplicationContext()
        .getContentResolver();

// Find all audio files on the primary external storage device.
Uri audioCollection;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
    audioCollection = MediaStore.Audio.Media
            .getContentUri(MediaStore.VOLUME_EXTERNAL_PRIMARY);
} else {
    audioCollection = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI;
}

ContentValues songDetails = new ContentValues();
songDetails.put(MediaStore.Audio.Media.DISPLAY_NAME,
        "My Workout Playlist.mp3");
songDetails.put(MediaStore.Audio.Media.IS_PENDING, 1);

Uri songContentUri = resolver
        .insert(audioCollection, songDetails);

// "w" for write.
try (ParcelFileDescriptor pfd =
        resolver.openFileDescriptor(songContentUri, "w", null)) {
    // Write data into the pending audio file.
}

// Now that you're finished, release the "pending" status and let other apps
// play the audio track.
songDetails.clear();
songDetails.put(MediaStore.Audio.Media.IS_PENDING, 0);
resolver.update(songContentUri, songDetails, null, null);

راهنمایی برای مکان فایل ارائه دهید

وقتی برنامه‌تان رسانه‌ای را در دستگاهی با Android 10 ذخیره می‌کند، رسانه به‌طور پیش‌فرض براساس نوع آن سازمان‌دهی می‌شود. برای مثال، به‌طور پیش‌فرض فایل‌های تصویری جدید در دایرکتوری Environment.DIRECTORY_PICTURES قرار می‌گیرند که با مجموعه MediaStore.Images مطابقت دارد.

اگر برنامه‌تان از مکان خاصی که فایل‌ها می‌توانند در آن ذخیره شوند مطلع است، مثلاً آلبوم عکسی به‌نام Pictures/MyVacationPictures، می‌توانید MediaColumns.RELATIVE_PATH را تنظیم کنید تا به سیستم راهنمایی ارائه دهد که فایل‌های جدیداً نوشته‌شده را کجا ذخیره کند.

به‌روزرسانی مورد

برای به‌روزرسانی فایل رسانه‌ای که متعلق به برنامه شما است، از کدی مشابه کد زیر استفاده کنید:

کاتلین

// Updates an existing media item.
val mediaId = // MediaStore.Audio.Media._ID of item to update.
val resolver = applicationContext.contentResolver

// When performing a single item update, prefer using the ID.
val selection = "${MediaStore.Audio.Media._ID} = ?"

// By using selection + args you protect against improper escaping of // values.
val selectionArgs = arrayOf(mediaId.toString())

// Update an existing song.
val updatedSongDetails = ContentValues().apply {
    put(MediaStore.Audio.Media.DISPLAY_NAME, "My Favorite Song.mp3")
}

// Use the individual song's URI to represent the collection that's
// updated.
val numSongsUpdated = resolver.update(
        myFavoriteSongUri,
        updatedSongDetails,
        selection,
        selectionArgs)

جاوا

// Updates an existing media item.
long mediaId = // MediaStore.Audio.Media._ID of item to update.
ContentResolver resolver = getApplicationContext()
        .getContentResolver();

// When performing a single item update, prefer using the ID.
String selection = MediaStore.Audio.Media._ID + " = ?";

// By using selection + args you protect against improper escaping of
// values. Here, "song" is an in-memory object that caches the song's
// information.
String[] selectionArgs = new String[] { getId().toString() };

// Update an existing song.
ContentValues updatedSongDetails = new ContentValues();
updatedSongDetails.put(MediaStore.Audio.Media.DISPLAY_NAME,
        "My Favorite Song.mp3");

// Use the individual song's URI to represent the collection that's
// updated.
int numSongsUpdated = resolver.update(
        myFavoriteSongUri,
        updatedSongDetails,
        selection,
        selectionArgs);

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

به‌روزرسانی در کد بومی

اگر لازم است فایل‌های رسانه‌ای را بااستفاده از کتابخانه‌های بومی بنویسید، توصیف‌گر فایل مرتبط با فایل را از کد مبتنی بر Java یا Kotlin به کد بومی‌تان منتقل کنید.

تکه‌کد زیر نحوه ارسال توصیف‌گر فایل شیء رسانه به کد بومی برنامه را نشان می‌دهد:

کاتلین

val contentUri: Uri = ContentUris.withAppendedId(
        MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
        cursor.getLong(BaseColumns._ID))
val fileOpenMode = "r"
val parcelFd = resolver.openFileDescriptor(contentUri, fileOpenMode)
val fd = parcelFd?.detachFd()
// Pass the integer value "fd" into your native code. Remember to call
// close(2) on the file descriptor when you're done using it.

جاوا

Uri contentUri = ContentUris.withAppendedId(
        MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
        cursor.getLong(Integer.parseInt(BaseColumns._ID)));
String fileOpenMode = "r";
ParcelFileDescriptor parcelFd =
        resolver.openFileDescriptor(contentUri, fileOpenMode);
if (parcelFd != null) {
    int fd = parcelFd.detachFd();
    // Pass the integer value "fd" into your native code. Remember to call
    // close(2) on the file descriptor when you're done using it.
}

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

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

بااین‌حال، می‌توانید با گرفتن RecoverableSecurityException که پلاتفرم ارائه می‌دهد، رضایت کاربر را برای اصلاح فایل دریافت کنید. سپس می‌توانید از کاربر درخواست کنید که به برنامه شما دسترسی نوشتن به آن مورد خاص را اعطا کند، همان‌طور که در تکه‌کد زیر نشان داده شده است:

کاتلین

// Apply a grayscale filter to the image at the given content URI.
try {
    // "w" for write.
    contentResolver.openFileDescriptor(image-content-uri, "w")?.use {
        setGrayscaleFilter(it)
    }
} catch (securityException: SecurityException) {
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
        val recoverableSecurityException = securityException as?
            RecoverableSecurityException ?:
            throw RuntimeException(securityException.message, securityException)

        val intentSender =
            recoverableSecurityException.userAction.actionIntent.intentSender
        intentSender?.let {
            startIntentSenderForResult(intentSender, image-request-code,
                    null, 0, 0, 0, null)
        }
    } else {
        throw RuntimeException(securityException.message, securityException)
    }
}

جاوا

try {
    // "w" for write.
    ParcelFileDescriptor imageFd = getContentResolver()
            .openFileDescriptor(image-content-uri, "w");
    setGrayscaleFilter(imageFd);
} catch (SecurityException securityException) {
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
        RecoverableSecurityException recoverableSecurityException;
        if (securityException instanceof RecoverableSecurityException) {
            recoverableSecurityException =
                    (RecoverableSecurityException)securityException;
        } else {
            throw new RuntimeException(
                    securityException.getMessage(), securityException);
        }
        IntentSender intentSender =recoverableSecurityException.getUserAction()
                .getActionIntent().getIntentSender();
        startIntentSenderForResult(intentSender, image-request-code,
                null, 0, 0, 0, null);
    } else {
        throw new RuntimeException(
                securityException.getMessage(), securityException);
    }
}

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

یا اگر برنامه‌تان در Android نسخه ۱۱ یا بالاتر اجرا می‌شود، می‌توانید به کاربران اجازه دهید به برنامه‌تان دسترسی نوشتن به گروهی از فایل‌های رسانه‌ای را اعطا کنند. از روش createWriteRequest() استفاده کنید، همان‌طور که در بخش مربوط به نحوه مدیریت گروه‌های فایل‌های رسانه‌ای توضیح داده شده است.

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

برداشتن مورد

برای برداشتن موردی که برنامه‌تان دیگر در فضای ذخیره‌سازی رسانه به آن نیاز ندارد، از منطقی مشابه آنچه در تکه‌کد زیر نشان داده شده است استفاده کنید:

کاتلین

// Remove a specific media item.
val resolver = applicationContext.contentResolver

// URI of the image to remove.
val imageUri = "..."

// WHERE clause.
val selection = "..."
val selectionArgs = "..."

// Perform the actual removal.
val numImagesRemoved = resolver.delete(
        imageUri,
        selection,
        selectionArgs)

جاوا

// Remove a specific media item.
ContentResolver resolver = getApplicationContext()
        getContentResolver();

// URI of the image to remove.
Uri imageUri = "...";

// WHERE clause.
String selection = "...";
String[] selectionArgs = "...";

// Perform the actual removal.
int numImagesRemoved = resolver.delete(
        imageUri,
        selection,
        selectionArgs);

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

اگر برنامه‌تان در Android نسخه ۱۱ یا بالاتر اجرا می‌شود، می‌توانید به کاربران اجازه دهید گروهی از فایل‌های رسانه‌ای را برای حذف انتخاب کنند. از روش createTrashRequest() یا روش createDeleteRequest() استفاده کنید، همان‌طور که در بخش مربوط به نحوه مدیریت گروه‌های فایل‌های رسانه‌ای توضیح داده شده است.

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

تشخیص به‌روزرسانی‌های فایل‌های رسانه‌ای

ممکن است برنامه‌تان نیاز داشته باشد حجم‌های فضای ذخیره‌سازی حاوی فایل‌های رسانه‌ای را که برنامه‌ها اضافه یا اصلاح کرده‌اند، در مقایسه با نقطه زمانی قبلی، شناسایی کند. برای تشخیص این تغییرات به مطمئن‌ترین شکل، حجم فضای ذخیره‌سازی موردنظر را به getGeneration() منتقل کنید. تا زمانی که نسخه فروشگاه رسانه تغییر نکند، مقدار برگشتی این روش به‌طور یکنواخت درطول زمان افزایش می‌یابد.

به‌ویژه، getGeneration() از تاریخ‌های موجود در ستون‌های رسانه، مانند DATE_ADDED و DATE_MODIFIED، مستحکم‌تر است. دلیلش این است که وقتی برنامه‌ای setLastModified() را فرامی‌خواند یا وقتی کاربر ساعت سیستم را تغییر می‌دهد، مقادیر ستون رسانه ممکن است تغییر کند.

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

در Android نسخه ۱۱ و بالاتر، می‌توانید از کاربر بخواهید گروهی از فایل‌های رسانه‌ای را انتخاب کند، سپس این فایل‌های رسانه‌ای را در یک عملیات به‌روزرسانی کنید. این روش‌ها سازگاری بهتری را در دستگاه‌های مختلف ارائه می‌دهند و مدیریت مجموعه‌های رسانه‌ای را برای کاربران آسان‌تر می‌کنند.

روش‌هایی که این عملکرد «به‌روزرسانی دسته‌ای» را ارائه می‌دهند شامل موارد زیر است:

createWriteRequest()
از کاربر بخواهید به برنامه شما اجازه دسترسی نوشتن به گروه مشخص‌شده از فایل‌های رسانه‌ای را بدهد.
createFavoriteRequest()
درخواست می‌کند که کاربر فایل‌های رسانه‌ای مشخص‌شده را به‌عنوان برخی از رسانه‌های «موردعلاقه» خود در دستگاه علامت‌گذاری کند. هر برنامه‌ای که دسترسی خواندن به این فایل را داشته باشد می‌تواند ببیند که کاربر فایل را به‌عنوان «موردپسند» علامت‌گذاری کرده است.
createTrashRequest()

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

createDeleteRequest()

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

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

برای مثال، در اینجا نحوه ساختاردهی تماس با createWriteRequest() آورده شده است:

کاتلین

val urisToModify = /* A collection of content URIs to modify. */
val editPendingIntent = MediaStore.createWriteRequest(contentResolver,
        urisToModify)

// Launch a system prompt requesting user permission for the operation.
startIntentSenderForResult(editPendingIntent.intentSender, EDIT_REQUEST_CODE,
    null, 0, 0, 0)

جاوا

List<Uri> urisToModify = /* A collection of content URIs to modify. */
PendingIntent editPendingIntent = MediaStore.createWriteRequest(contentResolver,
                  urisToModify);

// Launch a system prompt requesting user permission for the operation.
startIntentSenderForResult(editPendingIntent.getIntentSender(),
    EDIT_REQUEST_CODE, null, 0, 0, 0);

پاسخ کاربر را ارزیابی کنید. اگر کاربر رضایت داد، عملیات رسانه را ادامه دهید. درغیراین‌صورت، به کاربر توضیح دهید چرا برنامه‌تان به این اجازه نیاز دارد:

کاتلین

override fun onActivityResult(requestCode: Int, resultCode: Int,
                 data: Intent?) {
    ...
    when (requestCode) {
        EDIT_REQUEST_CODE ->
            if (resultCode == Activity.RESULT_OK) {
                /* Edit request granted; proceed. */
            } else {
                /* Edit request not granted; explain to the user. */
            }
    }
}

جاوا

@Override
protected void onActivityResult(int requestCode, int resultCode,
                   @Nullable Intent data) {
    ...
    if (requestCode == EDIT_REQUEST_CODE) {
        if (resultCode == Activity.RESULT_OK) {
            /* Edit request granted; proceed. */
        } else {
            /* Edit request not granted; explain to the user. */
        }
    }
}

می‌توانید از همین الگوی کلی با createFavoriteRequest()، createTrashRequest()، و createDeleteRequest() استفاده کنید.

اجازه مدیریت رسانه

کاربران ممکن است به برنامه خاصی برای مدیریت رسانه اعتماد کنند، مثلاً برای ویرایش مکرر فایل‌های رسانه‌ای. اگر برنامه شما Android 11 یا بالاتر را هدف قرار می‌دهد و برنامه گالری پیش‌فرض دستگاه نیست، باید هربار که برنامه‌تان تلاش می‌کند فایلی را اصلاح یا حذف کند، کادر گفتگوی تأیید را به کاربر نشان دهید.

اگر برنامه شما Android 12 (سطح میانای برنامه‌سازی کاربردی ۳۱) یا بالاتر را هدف قرار می‌دهد، می‌توانید درخواست کنید که کاربران به برنامه شما اجازه دسترسی به اجازه ویژه مدیریت رسانه را بدهند. این اجازه به برنامه شما امکان می‌دهد هریک از کارهای زیر را بدون نیاز به درخواست از کاربر برای هر عملیات فایل انجام دهد:

  • تغییر دادن فایل‌ها، بااستفاده از createWriteRequest().
  • انتقال فایل‌ها به حذف‌شده‌ها و خارج کردن آن‌ها از حذف‌شده‌ها بااستفاده از createTrashRequest().
  • حذف فایل‌ها، بااستفاده از createDeleteRequest().

برای انجام این کار، مراحل زیر را تکمیل کنید:

  1. اجازه MANAGE_MEDIA و اجازه READ_EXTERNAL_STORAGE را در فایل مانیفست برنامه‌تان اعلام کنید.

    برای تماس با createWriteRequest() بدون نمایش کادر گفتگوی تأیید، اجازه ACCESS_MEDIA_LOCATION را نیز اعلام کنید.

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

  3. کنش قصد ACTION_REQUEST_MANAGE_MEDIA را فراخوانی کنید. این کار کاربران را به صفحه برنامه‌های مدیریت رسانه در تنظیمات سیستم هدایت می‌کند. از اینجا، کاربران می‌توانند دسترسی ویژه به برنامه را اعطا کنند.

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

اگر برنامه شما در درجه اول یکی از نقش‌های زیر را ایفا می‌کند، جایگزینی برای میاناهای برنامه‌سازی کاربردی MediaStore درنظر بگیرید.

کار با انواع دیگر فایل‌ها

اگر برنامه‌تان با اسناد و فایل‌هایی کار می‌کند که منحصراً حاوی محتوای رسانه‌ای نیستند، مثلاً فایل‌هایی که از پسوند فایل EPUB یا PDF استفاده می‌کنند، از کنش هدف ACTION_OPEN_DOCUMENT استفاده کنید، همان‌طور که در راهنمای ذخیره و دسترسی به اسناد و فایل‌های دیگر شرح داده شده است.

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

در مواردی که مجموعه‌ای از برنامه‌های همراه ارائه می‌دهید، مثلاً برنامه پیام‌رسانی و برنامه نمایه، هم‌رسانی فایل را بااستفاده از شناسه منبع یکسان content:// راه‌اندازی کنید. همچنین این گردش کار را به‌عنوان روال مطلوب امنیتی توصیه می‌کنیم.

منابع بیشتر

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

نمونه‌ها

ویدیوها