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

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

انتخابگر عکس

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

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

برای تعامل با انتزاع انبار رسانه، از یک شیء 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/ در اندروید ۱۱ (سطح API ۳۰) و پایین‌تر در دسترس نیست.
  • فایل‌های دانلود شده که در دایرکتوری Download/ ذخیره می‌شوند. در دستگاه‌هایی که اندروید ۱۰ (سطح API ۲۹) و بالاتر را اجرا می‌کنند، این فایل‌ها در جدول MediaStore.Downloads ذخیره می‌شوند. این جدول در اندروید ۹ (سطح API ۲۸) و پایین‌تر در دسترس نیست.

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

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

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

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

مجوزهای ذخیره‌سازی

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

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

در دستگاه‌هایی که اندروید ۱۰ یا بالاتر را اجرا می‌کنند، برای دسترسی و تغییر فایل‌های رسانه‌ای که برنامه شما در اختیار دارد ، از جمله فایل‌های موجود در مجموعه 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" />

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

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

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

اگر برنامه شما می‌خواهد به فایلی در مجموعه MediaStore.Downloads که توسط برنامه شما ایجاد نشده است دسترسی پیدا کند، باید از چارچوب دسترسی به فضای ذخیره‌سازی (Storage Access Framework) استفاده کنید. برای کسب اطلاعات بیشتر در مورد نحوه استفاده از این چارچوب، به بخش اسناد دسترسی و سایر فایل‌ها از فضای ذخیره‌سازی مشترک مراجعه کنید.

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

اگر برنامه شما اندروید ۱۰ (سطح API 29) یا بالاتر را هدف قرار می‌دهد و نیاز به بازیابی فراداده‌های 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() را در یک worker thread فراخوانی کنید.
  • اندیس‌های ستون را ذخیره کنید تا نیازی نباشد هر بار که یک ردیف از نتیجه‌ی پرس‌وجو را پردازش می‌کنید، getColumnIndexOrThrow() را فراخوانی کنید.
  • همانطور که در این مثال نشان داده شده است، شناسه را به URI محتوا اضافه کنید.
  • دستگاه‌هایی که اندروید ۱۰ و بالاتر را اجرا می‌کنند، به نام ستون‌هایی نیاز دارند که در API 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".
}

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

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

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

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

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

برای دسترسی به سایر فایل‌های موجود در فضای ذخیره‌سازی مشترک در دستگاهی که اندروید ۱۰ (سطح API 29) را اجرا می‌کند، توصیه می‌کنیم با تنظیم requestLegacyExternalStorage روی true در فایل مانیفست برنامه خود ، موقتاً از فضای ذخیره‌سازی محدود (scoped storage) انصراف دهید . برای دسترسی به فایل‌های رسانه‌ای با استفاده از متدهای فایل‌های بومی در اندروید ۱۰، باید مجوز READ_EXTERNAL_STORAGE را نیز درخواست کنید.

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

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

داده‌های ذخیره‌شده

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

عملکرد

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

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

ستون داده

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

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

حجم‌های ذخیره‌سازی

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

به خاطر سپردن جلدهای زیر به ویژه مفید است:

  • پارتیشن 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();

مکانی که رسانه‌ها در آن دستگیر شدند

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

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

عکس‌ها

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

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

اشتراک‌گذاری

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

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

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

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

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

وقتی برنامه‌ای که SDK 36 یا بالاتر را روی دستگاه‌هایی که اندروید ۱۶ یا بالاتر دارند، درخواست مجوز عکس و ویدیو می‌کند، کاربرانی که دسترسی به رسانه‌های انتخاب‌شده را محدود می‌کنند، عکس‌های متعلق به برنامه را که از قبل در انتخابگر عکس انتخاب شده‌اند، مشاهده خواهند کرد. کاربران می‌توانند هر یک از این موارد از پیش انتخاب‌شده را لغو کنند که این امر دسترسی برنامه به آن عکس‌ها و ویدیوها را لغو می‌کند.

یک مورد اضافه کنید

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

کاتلین

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

تغییر وضعیت در انتظار برای فایل‌های رسانه‌ای

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

یه راهنمایی برای محل فایل بدید

وقتی برنامه شما رسانه را روی دستگاهی که اندروید ۱۰ را اجرا می‌کند ذخیره می‌کند، به‌طور پیش‌فرض رسانه بر اساس نوع آن سازماندهی می‌شود. برای مثال، به‌طور پیش‌فرض فایل‌های تصویر جدید در دایرکتوری 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);

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

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

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

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

کاتلین

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

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

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

با این حال، می‌توانید با دریافت خطای 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);
    }
}

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

از طرف دیگر، اگر برنامه شما روی اندروید ۱۱ یا بالاتر اجرا می‌شود، می‌توانید به کاربران اجازه دهید به برنامه شما دسترسی نوشتن روی گروهی از فایل‌های رسانه‌ای را اعطا کنند. از متد 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 دریافت کنید، همانطور که در بخش مربوط به به‌روزرسانی موارد رسانه توضیح داده شده است.

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

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

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

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

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

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

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

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

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

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

createDeleteRequest()

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

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

برای مثال، در اینجا نحوه‌ی فراخوانی تابع 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() استفاده کنید.

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

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

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

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

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

    برای فراخوانی تابع createWriteRequest() بدون نمایش کادر محاوره‌ای تأیید، مجوز ACCESS_MEDIA_LOCATION را نیز تعریف کنید.

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

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

از مواردی استفاده کنید که به جایگزینی برای فروشگاه رسانه نیاز دارند

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

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

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

اشتراک‌گذاری فایل در برنامه‌های همراه

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

منابع اضافی

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

نمونه‌ها

ویدیوها