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

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

انتخابگر عکس

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

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

برای تعامل با انتزاع فروشگاه رسانه، از یک شی 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 (سطح API 30) و پایین‌تر در دسترس نیست.
  • فایل های دانلود شده، که در دایرکتوری Download/ ذخیره می شوند. در دستگاه‌هایی که Android 10 (سطح API 29) و بالاتر دارند، این فایل‌ها در جدول MediaStore.Downloads ذخیره می‌شوند. این جدول در Android 9 (سطح API 28) و پایین‌تر در دسترس نیست.

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

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

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

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

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

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

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

در دستگاه‌هایی که Android 10 یا بالاتر را اجرا می‌کنند، برای دسترسی و تغییر فایل‌های رسانه‌ای که برنامه شما از جمله فایل‌های مجموعه 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 نسخه 9 یا پایین‌تر است، یا اگر برنامه شما به‌طور موقت از فضای ذخیره‌سازی محدوده انصراف داده است، باید برای دسترسی به هر فایل رسانه‌ای، مجوز READ_EXTERNAL_STORAGE را درخواست کنید. اگر می خواهید فایل های رسانه ای را تغییر دهید، باید مجوز WRITE_EXTERNAL_STORAGE را نیز درخواست کنید.

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

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

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

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

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

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

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

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

پرس و جو از یک مجموعه رسانه ای

برای یافتن رسانه ای که مجموعه خاصی از شرایط را برآورده می کند، مانند مدت زمان 5 دقیقه یا بیشتر، از عبارت انتخابی 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() را در یک thread کارگر فراخوانی کنید.
  • اندیس های ستون را کش کنید تا نیازی به فراخوانی getColumnIndexOrThrow() در هر بار پردازش یک ردیف از نتیجه پرس و جو نداشته باشید.
  • همانطور که در این مثال نشان داده شده است، شناسه را به URI محتوا اضافه کنید.
  • دستگاه‌هایی که Android 10 و بالاتر را اجرا می‌کنند ، به نام ستون‌هایی نیاز دارند که در MediaStore API تعریف شده‌اند. اگر یک کتابخانه وابسته در برنامه شما انتظار نام ستونی را دارد که در 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) و بالاتر به شما امکان می‌دهد از APIهایی غیر از MediaStore API برای دسترسی به فایل‌های رسانه از فضای ذخیره‌سازی مشترک استفاده کنید. در عوض می‌توانید مستقیماً با استفاده از یکی از APIهای زیر به فایل‌های رسانه دسترسی داشته باشید:

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

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

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

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

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

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

داده های ذخیره شده در حافظه پنهان

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

عملکرد

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

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

ستون DATA

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

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

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

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

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

  • حجم 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 10 یا بالاتر را هدف قرار می‌دهد، سیستم یک برنامه را به هر فایل رسانه‌ای نسبت می‌دهد ، که فایل‌هایی را که برنامه شما زمانی که درخواستی برای مجوز ذخیره‌سازی درخواست نکرده است، مشخص می‌کند که می‌تواند به آنها دسترسی داشته باشد. هر فایل را می توان تنها به یک برنامه نسبت داد. بنابراین، اگر برنامه شما یک فایل رسانه ای ایجاد می کند که در مجموعه رسانه عکس ها، فیلم ها یا فایل های صوتی ذخیره شده است، برنامه شما به فایل دسترسی دارد.

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

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

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

کاتلین

// 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 10 یا بالاتر دارند، برنامه شما می‌تواند این دسترسی انحصاری را با تنظیم مقدار پرچم IS_PENDING روی 1 دریافت کند. فقط برنامه شما می‌تواند فایل را مشاهده کند تا زمانی که برنامه شما مقدار IS_PENDING را به 0 تغییر دهد.

قطعه کد زیر بر روی قطعه کد قبلی ساخته شده است. این قطعه نحوه استفاده از پرچم 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);

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

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

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

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

کاتلین

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 11 یا بالاتر اجرا می‌شود، می‌توانید به کاربران اجازه دهید به برنامه شما دسترسی نوشتن به گروهی از فایل‌های رسانه را بدهند. از روش 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() را فراخوانی می‌کند یا زمانی که کاربر ساعت سیستم را تغییر می‌دهد، مقادیر ستون رسانه می‌توانند تغییر کنند.

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

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

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

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 (سطح API 31) یا بالاتر را هدف قرار می‌دهد، می‌توانید درخواست کنید که کاربران به برنامه شما اجازه دسترسی ویژه به مدیریت رسانه را بدهند. این مجوز به برنامه شما اجازه می‌دهد هر یک از کارهای زیر را بدون نیاز به درخواست هر فایل از کاربر انجام دهد:

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

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

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

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

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

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

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

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

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

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

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

منابع اضافی

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

نمونه ها

ویدیوها

،

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

انتخابگر عکس

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

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

برای تعامل با انتزاع فروشگاه رسانه، از یک شی 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 (سطح API 30) و پایین‌تر در دسترس نیست.
  • فایل های دانلود شده، که در دایرکتوری Download/ ذخیره می شوند. در دستگاه‌هایی که Android 10 (سطح API 29) و بالاتر دارند، این فایل‌ها در جدول MediaStore.Downloads ذخیره می‌شوند. این جدول در Android 9 (سطح API 28) و پایین‌تر در دسترس نیست.

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

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

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

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

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

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

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

در دستگاه‌هایی که Android 10 یا بالاتر را اجرا می‌کنند، برای دسترسی و تغییر فایل‌های رسانه‌ای که برنامه شما از جمله فایل‌های مجموعه 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 نسخه 9 یا پایین‌تر است، یا اگر برنامه شما به‌طور موقت از فضای ذخیره‌سازی محدوده انصراف داده است، باید برای دسترسی به هر فایل رسانه‌ای، مجوز READ_EXTERNAL_STORAGE را درخواست کنید. اگر می خواهید فایل های رسانه ای را تغییر دهید، باید مجوز WRITE_EXTERNAL_STORAGE را نیز درخواست کنید.

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

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

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

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

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

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

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

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

پرس و جو از یک مجموعه رسانه ای

برای یافتن رسانه ای که مجموعه خاصی از شرایط را برآورده می کند، مانند مدت زمان 5 دقیقه یا بیشتر، از عبارت انتخابی 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() را در یک thread کارگر فراخوانی کنید.
  • اندیس های ستون را کش کنید تا نیازی به فراخوانی getColumnIndexOrThrow() در هر بار پردازش یک ردیف از نتیجه پرس و جو نداشته باشید.
  • همانطور که در این مثال نشان داده شده است، شناسه را به URI محتوا اضافه کنید.
  • دستگاه‌هایی که Android 10 و بالاتر را اجرا می‌کنند ، به نام ستون‌هایی نیاز دارند که در MediaStore API تعریف شده‌اند. اگر یک کتابخانه وابسته در برنامه شما انتظار نام ستونی را دارد که در 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) و بالاتر به شما امکان می‌دهد از APIهایی غیر از MediaStore API برای دسترسی به فایل‌های رسانه از فضای ذخیره‌سازی مشترک استفاده کنید. در عوض می‌توانید مستقیماً با استفاده از یکی از APIهای زیر به فایل‌های رسانه دسترسی داشته باشید:

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

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

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

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

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

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

داده های ذخیره شده در حافظه پنهان

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

عملکرد

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

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

ستون DATA

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

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

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

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

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

  • حجم 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 10 یا بالاتر را هدف قرار می‌دهد، سیستم یک برنامه را به هر فایل رسانه‌ای نسبت می‌دهد ، که فایل‌هایی را که برنامه شما زمانی که درخواستی برای مجوز ذخیره‌سازی درخواست نکرده است، مشخص می‌کند که می‌تواند به آنها دسترسی داشته باشد. هر فایل را می توان تنها به یک برنامه نسبت داد. بنابراین، اگر برنامه شما یک فایل رسانه ای ایجاد می کند که در مجموعه رسانه عکس ها، فیلم ها یا فایل های صوتی ذخیره شده است، برنامه شما به فایل دسترسی دارد.

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

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

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

کاتلین

// 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 10 یا بالاتر دارند، برنامه شما می‌تواند این دسترسی انحصاری را با تنظیم مقدار پرچم IS_PENDING روی 1 دریافت کند. فقط برنامه شما می‌تواند فایل را مشاهده کند تا زمانی که برنامه شما مقدار IS_PENDING را به 0 تغییر دهد.

قطعه کد زیر بر روی قطعه کد قبلی ساخته شده است. این قطعه نحوه استفاده از پرچم 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);

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

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

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

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

کاتلین

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 11 یا بالاتر اجرا می‌شود، می‌توانید به کاربران اجازه دهید به برنامه شما دسترسی نوشتن به گروهی از فایل‌های رسانه را بدهند. از روش 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() را فراخوانی می‌کند یا زمانی که کاربر ساعت سیستم را تغییر می‌دهد، مقادیر ستون رسانه می‌توانند تغییر کنند.

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

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

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

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 (سطح API 31) یا بالاتر را هدف قرار می‌دهد، می‌توانید درخواست کنید که کاربران به برنامه شما اجازه دسترسی ویژه به مدیریت رسانه را بدهند. این مجوز به برنامه شما اجازه می‌دهد هر یک از کارهای زیر را بدون نیاز به درخواست هر فایل از کاربر انجام دهد:

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

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

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

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

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

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

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

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

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

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

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

منابع اضافی

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

نمونه ها

ویدیوها