शेयर किए गए स्टोरेज से मीडिया फ़ाइलें ऐक्सेस करना

उपयोगकर्ताओं को बेहतर अनुभव देने के लिए, कई ऐप्लिकेशन लोगों को बाहरी स्टोरेज वॉल्यूम पर उपलब्ध मीडिया को ऐक्सेस करने और उसमें योगदान करने की सुविधा देते हैं. यह फ़्रेमवर्क, मीडिया कलेक्शन में ऑप्टिमाइज़ किया गया इंडेक्स उपलब्ध कराता है. इसे मीडिया स्टोर कहा जाता है. इससे उपयोगकर्ता, इन मीडिया फ़ाइलों को आसानी से वापस पा सकते हैं और उन्हें अपडेट कर सकते हैं. ऐप्लिकेशन को अनइंस्टॉल करने के बाद भी, ये फ़ाइलें उपयोगकर्ता के डिवाइस पर बनी रहती हैं.

फ़ोटो पिकर

मीडिया स्टोर का इस्तेमाल करने के बजाय, Android फ़ोटो पिकर टूल का इस्तेमाल किया जा सकता है. यह टूल, उपयोगकर्ताओं के लिए ऐप्लिकेशन में पहले से मौजूद है. इसकी मदद से, उपयोगकर्ता अपनी पूरी मीडिया लाइब्रेरी का ऐक्सेस दिए बिना, मीडिया फ़ाइलें चुन सकते हैं. यह सुविधा सिर्फ़ उन डिवाइसों पर उपलब्ध है जिन पर यह काम करती है. ज़्यादा जानकारी के लिए, फ़ोटो पिकर गाइड देखें.

मीडिया स्टोर

मीडिया स्टोर ऐब्स्ट्रैक्शन के साथ इंटरैक्ट करने के लिए, ContentResolver ऑब्जेक्ट का इस्तेमाल करें. इसे अपने ऐप्लिकेशन के कॉन्टेक्स्ट से वापस पाएं:

Kotlin

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

Java

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 (एपीआई लेवल 30) और इससे पहले के वर्शन पर उपलब्ध नहीं है.
  • डाउनलोड की गई फ़ाइलें, जो Download/ डायरेक्ट्री में सेव होती हैं. Android 10 (एपीआई लेवल 29) और उसके बाद के वर्शन वाले डिवाइसों पर, ये फ़ाइलें MediaStore.Downloads टेबल में सेव की जाती हैं. यह टेबल, Android 9 (एपीआई लेवल 28) और इसके पहले के वर्शन पर उपलब्ध नहीं है.

मीडिया स्टोर में MediaStore.Files नाम का एक कलेक्शन भी शामिल है. इसका कॉन्टेंट इस बात पर निर्भर करता है कि आपका ऐप्लिकेशन स्कोप किए गए स्टोरेज का इस्तेमाल करता है या नहीं. यह सुविधा, Android 10 या उसके बाद के वर्शन को टारगेट करने वाले ऐप्लिकेशन के लिए उपलब्ध है.

  • स्कोप किए गए स्टोरेज की सुविधा चालू होने पर, कलेक्शन में सिर्फ़ वे फ़ोटो, वीडियो, और ऑडियो फ़ाइलें दिखती हैं जिन्हें आपके ऐप्लिकेशन ने बनाया है. ज़्यादातर डेवलपर को दूसरे ऐप्लिकेशन की मीडिया फ़ाइलें देखने के लिए, MediaStore.Files का इस्तेमाल करने की ज़रूरत नहीं होती. हालांकि, अगर आपको ऐसा करने की ज़रूरत है, तो READ_EXTERNAL_STORAGE की अनुमति के बारे में बताया जा सकता है. हालांकि, हमारा सुझाव है कि आप उन MediaStore एपीआई का इस्तेमाल करें जिनकी मदद से, ऐसी फ़ाइलें खोली जा सकती हैं जिन्हें आपके ऐप्लिकेशन ने नहीं बनाया है.
  • अगर स्कोप किए गए स्टोरेज की सुविधा उपलब्ध नहीं है या इसका इस्तेमाल नहीं किया जा रहा है, तो कलेक्शन में सभी तरह की मीडिया फ़ाइलें दिखती हैं.

ज़रूरी अनुमतियों का अनुरोध करना

मीडिया फ़ाइलों पर कार्रवाइयां करने से पहले, पक्का करें कि आपके ऐप्लिकेशन ने उन अनुमतियों के बारे में बताया हो जिनकी ज़रूरत उसे इन फ़ाइलों को ऐक्सेस करने के लिए होती है. हालांकि, इस बात का ध्यान रखें कि ऐसी अनुमतियों का एलान न करें जिनकी आपके ऐप्लिकेशन को ज़रूरत नहीं है या जिनका वह इस्तेमाल नहीं करता है.

स्टोरेज की अनुमतियां

आपके ऐप्लिकेशन को स्टोरेज का ऐक्सेस चाहिए या नहीं, यह इस बात पर निर्भर करता है कि वह सिर्फ़ अपनी मीडिया फ़ाइलों को ऐक्सेस करता है या दूसरे ऐप्लिकेशन से बनाई गई फ़ाइलों को भी ऐक्सेस करता है.

अपनी मीडिया फ़ाइलें ऐक्सेस करना

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 (एपीआई लेवल 29) या उसके बाद के वर्शन को टारगेट करता है और उसे फ़ोटो से EXIF मेटाडेटा को बिना बदलाव किए वापस पाना है, तो आपको अपने ऐप्लिकेशन के मेनिफ़ेस्ट में ACCESS_MEDIA_LOCATION अनुमति के बारे में जानकारी देनी होगी. इसके बाद, रनटाइम के दौरान इस अनुमति का अनुरोध करना होगा.

मीडिया स्टोर के अपडेट देखना

मीडिया फ़ाइलों को ज़्यादा भरोसेमंद तरीके से ऐक्सेस करने के लिए, खास तौर पर तब, जब आपका ऐप्लिकेशन मीडिया स्टोर से यूआरआई या डेटा को कैश मेमोरी में सेव करता है, तो देखें कि मीडिया स्टोर का वर्शन, मीडिया डेटा को पिछली बार सिंक करने के समय के वर्शन से बदला है या नहीं. अपडेट की जांच करने के लिए, getVersion() को कॉल करें. जवाब में मिला वर्शन एक यूनीक स्ट्रिंग होती है. मीडिया स्टोर में कोई बड़ा बदलाव होने पर, यह स्ट्रिंग बदल जाती है. अगर लौटाया गया वर्शन, आखिरी बार सिंक किए गए वर्शन से अलग है, तो अपने ऐप्लिकेशन की मीडिया कैश मेमोरी को फिर से स्कैन करें और सिंक करें.

ऐप्लिकेशन प्रोसेस के स्टार्टअप समय पर इस जांच को पूरा करें. मीडिया स्टोर से क्वेरी करते समय, आपको हर बार वर्शन की जांच करने की ज़रूरत नहीं है.

वर्शन नंबर के बारे में, लागू करने से जुड़ी कोई भी जानकारी अपने-आप न मान लें.

मीडिया कलेक्शन के बारे में क्वेरी करना

किसी खास शर्त को पूरा करने वाले मीडिया को ढूंढने के लिए, SQL जैसा कोई सिलेक्शन स्टेटमेंट इस्तेमाल करें. जैसे, पांच मिनट या उससे ज़्यादा अवधि वाला मीडिया. ऐसा स्टेटमेंट, यहां दिए गए कोड स्निपेट में दिखाया गया है:

Kotlin

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

Java

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

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

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

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

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

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

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

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

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

अपने ऐप्लिकेशन में इस तरह की क्वेरी करते समय, इन बातों का ध्यान रखें:

  • वर्कर थ्रेड में query() वाले तरीके को कॉल करें.
  • कॉलम इंडेक्स को कैश मेमोरी में सेव करें, ताकि क्वेरी के नतीजे से किसी लाइन को प्रोसेस करते समय आपको हर बार getColumnIndexOrThrow() को कॉल न करना पड़े.
  • इस उदाहरण में दिखाए गए तरीके से, आईडी को कॉन्टेंट यूआरआई में जोड़ें.
  • Android 10 और इसके बाद के वर्शन वाले डिवाइसों के लिए, MediaStore API में तय किए गए कॉलम के नाम ज़रूरी हैं. अगर आपके ऐप्लिकेशन में मौजूद कोई लाइब्रेरी, एपीआई में तय नहीं किए गए कॉलम के नाम का इस्तेमाल करती है, जैसे कि "MimeType", तो CursorWrapper का इस्तेमाल करके, अपने ऐप्लिकेशन की प्रोसेस में कॉलम के नाम का डाइनैमिक तरीके से अनुवाद करें.

फ़ाइल के थंबनेल लोड करना

अगर आपका ऐप्लिकेशन कई मीडिया फ़ाइलें दिखाता है और उपयोगकर्ता से इनमें से कोई एक फ़ाइल चुनने के लिए कहता है, तो फ़ाइलों के बजाय उनके पूर्वावलोकन वर्शन या थंबनेल लोड करना ज़्यादा बेहतर होता है.

किसी मीडिया फ़ाइल का थंबनेल लोड करने के लिए, loadThumbnail() का इस्तेमाल करें. साथ ही, उस थंबनेल का साइज़ डालें जिसे आपको लोड करना है. ऐसा इस कोड स्निपेट में दिखाए गए तरीके से करें:

Kotlin

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

Java

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

कोई मीडिया फ़ाइल खोलना

मीडिया फ़ाइल खोलने के लिए इस्तेमाल किया जाने वाला लॉजिक इस बात पर निर्भर करता है कि मीडिया कॉन्टेंट को फ़ाइल डिस्क्रिप्टर, फ़ाइल स्ट्रीम या डायरेक्ट फ़ाइल पाथ के तौर पर सबसे अच्छी तरह से दिखाया गया है या नहीं.

फ़ाइल की जानकारी देने वाला

फ़ाइल डिस्क्रिप्टर का इस्तेमाल करके मीडिया फ़ाइल खोलने के लिए, यहां दिए गए कोड स्निपेट में दिखाए गए लॉजिक का इस्तेमाल करें:

Kotlin

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

Java

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

फ़ाइल स्ट्रीम

फ़ाइल स्ट्रीम का इस्तेमाल करके मीडिया फ़ाइल खोलने के लिए, यहां दिए गए कोड स्निपेट में दिखाए गए लॉजिक का इस्तेमाल करें:

Kotlin

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

Java

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

फ़ाइल के डायरेक्ट पाथ

तीसरे पक्ष की मीडिया लाइब्रेरी के साथ आपका ऐप्लिकेशन बेहतर तरीके से काम करे, इसके लिए Android 11 (एपीआई लेवल 30) और इसके बाद के वर्शन में, शेयर किए गए स्टोरेज से मीडिया फ़ाइलें ऐक्सेस करने के लिए, MediaStore एपीआई के अलावा अन्य एपीआई का इस्तेमाल किया जा सकता है. इसके बजाय, इनमें से किसी एक एपीआई का इस्तेमाल करके, मीडिया फ़ाइलों को सीधे तौर पर ऐक्सेस किया जा सकता है:

  • File एपीआई
  • नेटिव लाइब्रेरी, जैसे कि fopen()

अगर आपके पास स्टोरेज से जुड़ी कोई अनुमति नहीं है, तो File एपीआई का इस्तेमाल करके, ऐप्लिकेशन के लिए बनी फ़ाइलों वाली डायरेक्ट्री में मौजूद फ़ाइलों को ऐक्सेस किया जा सकता है. साथ ही, आपके ऐप्लिकेशन से जुड़ी मीडिया फ़ाइलों को भी ऐक्सेस किया जा सकता है.

अगर आपका ऐप्लिकेशन, File एपीआई का इस्तेमाल करके किसी फ़ाइल को ऐक्सेस करने की कोशिश करता है और उसके पास ज़रूरी अनुमतियां नहीं हैं, तो FileNotFoundException होता है.

Android 10 (एपीआई लेवल 29) पर काम करने वाले डिवाइस पर, शेयर किए गए स्टोरेज में मौजूद अन्य फ़ाइलों को ऐक्सेस करने के लिए, हमारा सुझाव है कि आप स्कोप किए गए स्टोरेज से कुछ समय के लिए ऑप्ट आउट करें. इसके लिए, अपने ऐप्लिकेशन की मेनिफ़ेस्ट फ़ाइल में requestLegacyExternalStorage को true पर सेट करें. Android 10 पर, फ़ाइलों के नेटिव तरीकों का इस्तेमाल करके मीडिया फ़ाइलों को ऐक्सेस करने के लिए, आपको READ_EXTERNAL_STORAGE अनुमति का अनुरोध भी करना होगा.

मीडिया कॉन्टेंट ऐक्सेस करते समय ध्यान रखने वाली बातें

मीडिया कॉन्टेंट ऐक्सेस करते समय, यहां दिए गए सेक्शन में बताई गई बातों का ध्यान रखें.

संग्रहित डेटा

अगर आपका ऐप्लिकेशन मीडिया स्टोर से यूआरआई या डेटा को कैश मेमोरी में सेव करता है, तो समय-समय पर मीडिया स्टोर के अपडेट देखें. इस जांच से, आपके ऐप्लिकेशन में मौजूद कैश मेमोरी में सेव किया गया डेटा, सिस्टम में मौजूद डेटा के साथ सिंक रहता है.

परफ़ॉर्मेंस

डायरेक्ट फ़ाइल पाथ का इस्तेमाल करके मीडिया फ़ाइलों को क्रम से पढ़ने पर, परफ़ॉर्मेंस MediaStore API की परफ़ॉर्मेंस के बराबर होती है.

हालांकि, डायरेक्ट फ़ाइल पाथ का इस्तेमाल करके मीडिया फ़ाइलों को रैंडम तरीके से पढ़ने और लिखने की प्रोसेस, दो गुना धीमी हो सकती है. इन स्थितियों में, हमारा सुझाव है कि आप MediaStore एपीआई का इस्तेमाल करें.

DATA कॉलम

किसी मौजूदा मीडिया फ़ाइल को ऐक्सेस करते समय, अपने लॉजिक में DATA कॉलम की वैल्यू का इस्तेमाल किया जा सकता है. ऐसा इसलिए है, क्योंकि इस वैल्यू में फ़ाइल का मान्य पाथ मौजूद है. हालांकि, यह न मान लें कि फ़ाइल हमेशा उपलब्ध रहेगी. फ़ाइल पर आधारित किसी भी I/O गड़बड़ी को ठीक करने के लिए तैयार रहें.

मीडिया फ़ाइल बनाने या अपडेट करने के लिए, DATA कॉलम की वैल्यू का इस्तेमाल न करें. इसके बजाय, DISPLAY_NAME और RELATIVE_PATH कॉलम की वैल्यू का इस्तेमाल करें.

स्टोरेज वॉल्यूम

Android 10 या इसके बाद के वर्शन को टारगेट करने वाले ऐप्लिकेशन, उस यूनीक नाम को ऐक्सेस कर सकते हैं जो सिस्टम, हर बाहरी स्टोरेज वॉल्यूम को असाइन करता है. नाम रखने के इस सिस्टम से, कॉन्टेंट को आसानी से व्यवस्थित और इंडेक्स किया जा सकता है. साथ ही, इससे आपको यह कंट्रोल मिलता है कि नई मीडिया फ़ाइलें कहां सेव की जाएं.

इन वॉल्यूम को ध्यान में रखना खास तौर पर ज़रूरी है:

  • VOLUME_EXTERNAL वॉल्यूम से, डिवाइस पर शेयर किए गए सभी स्टोरेज वॉल्यूम का व्यू मिलता है. इस सिंथेटिक वॉल्यूम का कॉन्टेंट पढ़ा जा सकता है. हालांकि, इसमें बदलाव नहीं किया जा सकता.
  • VOLUME_EXTERNAL_PRIMARY वॉल्यूम, डिवाइस पर शेयर किए गए स्टोरेज के मुख्य वॉल्यूम को दिखाता है. आपके पास इस वॉल्यूम के कॉन्टेंट को पढ़ने और उसमें बदलाव करने का विकल्प होता है.

MediaStore.getExternalVolumeNames() को कॉल करके, अन्य वॉल्यूम खोजे जा सकते हैं:

Kotlin

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

Java

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

मीडिया कैप्चर करने की जगह की जानकारी

कुछ फ़ोटो और वीडियो के मेटाडेटा में जगह की जानकारी होती है. इससे पता चलता है कि फ़ोटो कहां खींची गई थी या वीडियो कहां रिकॉर्ड किया गया था.

आपके ऐप्लिकेशन में जगह की इस जानकारी को ऐक्सेस करने का तरीका इस बात पर निर्भर करता है कि आपको किसी फ़ोटो या वीडियो के लिए जगह की जानकारी ऐक्सेस करनी है.

फ़ोटोग्राफ़

अगर आपका ऐप्लिकेशन स्कोप किए गए स्टोरेज का इस्तेमाल करता है, तो सिस्टम डिफ़ॉल्ट रूप से जगह की जानकारी को छिपा देता है. इस जानकारी को ऐक्सेस करने के लिए, यह तरीका अपनाएं:

  1. अपने ऐप्लिकेशन के मेनिफ़ेस्ट में, ACCESS_MEDIA_LOCATION अनुमति के लिए अनुरोध करें.
  2. अपने MediaStore ऑब्जेक्ट से, फ़ोटो के सटीक बाइट पाएं. इसके लिए, setRequireOriginal() को कॉल करें और फ़ोटो का यूआरआई पास करें. ऐसा नीचे दिए गए कोड स्निपेट में दिखाया गया है:

    Kotlin

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

    Java

    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 क्लास का इस्तेमाल करें. यह जानकारी, यहां दिए गए कोड स्निपेट में दिखाई गई है. इस क्लास का इस्तेमाल करने के लिए, आपके ऐप्लिकेशन को किसी अन्य अनुमति का अनुरोध करने की ज़रूरत नहीं है.

Kotlin

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

Java

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:// यूआरआई का इस्तेमाल करें. इसके बारे में, कॉन्टेंट प्रोवाइडर बनाने के लिए गाइड में बताया गया है.

मीडिया फ़ाइलों का ऐप्लिकेशन एट्रिब्यूशन

Android 10 या इसके बाद के वर्शन को टारगेट करने वाले किसी ऐप्लिकेशन के लिए, स्कोप किए गए स्टोरेज की सुविधा चालू होने पर, सिस्टम हर मीडिया फ़ाइल के लिए ऐप्लिकेशन एट्रिब्यूट करता है. इससे यह तय होता है कि जब ऐप्लिकेशन ने स्टोरेज से जुड़ी कोई अनुमति नहीं मांगी है, तब वह किन फ़ाइलों को ऐक्सेस कर सकता है. हर फ़ाइल को सिर्फ़ एक ऐप्लिकेशन से जोड़ा जा सकता है. इसलिए, अगर आपका ऐप्लिकेशन ऐसी मीडिया फ़ाइल बनाता है जिसे फ़ोटो, वीडियो या ऑडियो फ़ाइलों के मीडिया कलेक्शन में सेव किया जाता है, तो आपके ऐप्लिकेशन के पास उस फ़ाइल का ऐक्सेस होता है.

हालांकि, अगर उपयोगकर्ता आपका ऐप्लिकेशन अनइंस्टॉल करके फिर से इंस्टॉल करता है, तो आपको उन फ़ाइलों को ऐक्सेस करने का अनुरोध करना होगा जिन्हें आपके ऐप्लिकेशन ने बनाया था. इसके लिए, READ_EXTERNAL_STORAGE पर क्लिक करें. अनुमति के लिए यह अनुरोध करना ज़रूरी है, क्योंकि सिस्टम इस फ़ाइल को ऐप्लिकेशन के पहले से इंस्टॉल किए गए वर्शन से जोड़ता है. ऐसा नए वर्शन के साथ नहीं करता.

Android 16 या उसके बाद के वर्शन पर चल रहे डिवाइसों पर, एसडीके 36 या उसके बाद के वर्शन को टारगेट करने वाले किसी ऐप्लिकेशन से, फ़ोटो और वीडियो की अनुमतियां मांगे जाने पर, जिन उपयोगकर्ताओं ने चुनिंदा मीडिया का ऐक्सेस सीमित किया है उन्हें फ़ोटो पिकर में, ऐप्लिकेशन के मालिकाना हक वाली सभी फ़ोटो पहले से चुनी हुई दिखेंगी. उपयोगकर्ता, पहले से चुने गए किसी भी आइटम से चुने हुए का निशान हटा सकते हैं. इससे ऐप्लिकेशन के पास उन फ़ोटो और वीडियो का ऐक्सेस नहीं रहेगा.

कोई आइटम जोड़ें

किसी मौजूदा कलेक्शन में मीडिया आइटम जोड़ने के लिए, यहां दिए गए कोड जैसा कोड इस्तेमाल करें. यह कोड स्निपेट, Android 10 या इसके बाद के वर्शन पर चलने वाले डिवाइसों पर VOLUME_EXTERNAL_PRIMARY वॉल्यूम को ऐक्सेस करता है. ऐसा इसलिए है, क्योंकि इन डिवाइसों पर किसी वॉल्यूम के कॉन्टेंट में सिर्फ़ तब बदलाव किया जा सकता है, जब वह प्राइमरी वॉल्यूम हो. इसके बारे में स्टोरेज वॉल्यूम सेक्शन में बताया गया है.

Kotlin

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

Java

// 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 कलेक्शन से जुड़ी डायरेक्ट्री में किसी लंबे गाने को सेव करते समय, IS_PENDING फ़्लैग का इस्तेमाल करने का तरीका दिखाया गया है:MediaStore.Audio

Kotlin

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

Java

// 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 सेट किया जा सकता है. इससे सिस्टम को यह जानकारी मिलती है कि नई फ़ाइलों को कहां सेव करना है.

किसी आइटम को अपडेट करना

ऐसी मीडिया फ़ाइल को अपडेट करने के लिए जो आपके ऐप्लिकेशन के मालिकाना हक में है, इस तरह के कोड का इस्तेमाल करें:

Kotlin

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

Java

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

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

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

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

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

अगर स्कोप किए गए स्टोरेज की सुविधा उपलब्ध नहीं है या चालू नहीं है, तो ऊपर दिए गए कोड स्निपेट में दिखाई गई प्रोसेस, उन फ़ाइलों के लिए भी काम करती है जिनका मालिकाना हक आपके ऐप्लिकेशन के पास नहीं है.

नेटिव कोड में अपडेट करना

अगर आपको नेटिव लाइब्रेरी का इस्तेमाल करके मीडिया फ़ाइलें लिखनी हैं, तो अपने Java या Kotlin कोड से फ़ाइल के साथ जुड़े फ़ाइल डिस्क्रिप्टर को अपने नेटिव कोड में पास करें.

नीचे दिए गए कोड स्निपेट में, मीडिया ऑब्जेक्ट के फ़ाइल डिस्क्रिप्टर को आपके ऐप्लिकेशन के नेटिव कोड में पास करने का तरीका बताया गया है:

Kotlin

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

Java

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 को कैप्चर करना होगा. इसके बाद, उपयोगकर्ता से अनुरोध किया जा सकता है कि वह आपके ऐप्लिकेशन को उस आइटम के लिए लिखने का ऐक्सेस दे. इसके लिए, यहां दिए गए कोड स्निपेट का इस्तेमाल करें:

Kotlin

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

Java

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() तरीके का इस्तेमाल करें. इसके बारे में, मीडिया फ़ाइलों के ग्रुप मैनेज करने के तरीके वाले सेक्शन में बताया गया है.

अगर आपके ऐप्लिकेशन का कोई ऐसा इस्तेमाल है जो स्कोप किए गए स्टोरेज के दायरे में नहीं आता है, तो सुविधा के लिए अनुरोध करें और कुछ समय के लिए स्कोप किए गए स्टोरेज से ऑप्ट आउट करें.

कोई आइटम हटाना

अगर आपके ऐप्लिकेशन को मीडिया स्टोर में किसी आइटम की ज़रूरत नहीं है, तो उसे हटाने के लिए, इस कोड स्निपेट में दिखाए गए लॉजिक का इस्तेमाल करें:

Kotlin

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

Java

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

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

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

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

अगर स्कोप किए गए स्टोरेज की सुविधा उपलब्ध नहीं है या चालू नहीं है, तो अन्य ऐप्लिकेशन के मालिकाना हक वाली फ़ाइलों को हटाने के लिए, ऊपर दिए गए कोड स्निपेट का इस्तेमाल किया जा सकता है. हालांकि, अगर स्कोप किए गए स्टोरेज की सुविधा चालू है, तो आपको अपने ऐप्लिकेशन से हटाई जाने वाली हर फ़ाइल के लिए RecoverableSecurityException को कैप्चर करना होगा. इसके बारे में मीडिया आइटम अपडेट करने वाले सेक्शन में बताया गया है.

अगर आपका ऐप्लिकेशन Android 11 या इसके बाद के वर्शन पर काम करता है, तो उपयोगकर्ताओं को मीडिया फ़ाइलों का कोई ग्रुप चुनने की सुविधा दी जा सकती है, ताकि वे उसे हटा सकें. createTrashRequest() तरीके या createDeleteRequest() तरीके का इस्तेमाल करें. इनके बारे में, मीडिया फ़ाइलों के ग्रुप मैनेज करने के तरीके वाले सेक्शन में बताया गया है.

अगर आपके ऐप्लिकेशन का कोई ऐसा इस्तेमाल है जो स्कोप किए गए स्टोरेज के दायरे में नहीं आता है, तो सुविधा के लिए अनुरोध करें और कुछ समय के लिए स्कोप किए गए स्टोरेज से ऑप्ट आउट करें.

मीडिया फ़ाइलों में हुए अपडेट का पता लगाना

आपके ऐप्लिकेशन को उन स्टोरेज वॉल्यूम की पहचान करनी पड़ सकती है जिनमें मीडिया फ़ाइलें मौजूद हैं. इन फ़ाइलों को ऐप्लिकेशन ने पिछले समय की तुलना में जोड़ा या बदला है. इन बदलावों का पता सबसे सटीक तरीके से लगाने के लिए, getGeneration() में स्टोरेज वॉल्यूम पास करें. जब तक मीडिया स्टोर का वर्शन नहीं बदलता, तब तक इस तरीके से मिलने वाली वैल्यू समय के साथ बढ़ती जाती है.

खास तौर पर, getGeneration(), मीडिया कॉलम में मौजूद तारीखों से ज़्यादा सटीक होता है. जैसे, DATE_ADDED और DATE_MODIFIED. ऐसा इसलिए है, क्योंकि ऐप्लिकेशन के setLastModified() कॉल करने पर या उपयोगकर्ता के सिस्टम क्लॉक में बदलाव करने पर, मीडिया कॉलम की वैल्यू बदल सकती हैं.

मीडिया फ़ाइलों के ग्रुप मैनेज करना

Android 11 और इसके बाद के वर्शन पर, उपयोगकर्ता से मीडिया फ़ाइलों का ग्रुप चुनने के लिए कहा जा सकता है. इसके बाद, इन मीडिया फ़ाइलों को एक ही ऑपरेशन में अपडेट किया जा सकता है. इन तरीकों से, अलग-अलग डिवाइसों पर बेहतर तरीके से मीडिया को सिंक किया जा सकता है. साथ ही, इनसे उपयोगकर्ताओं को अपने मीडिया कलेक्शन को मैनेज करने में आसानी होती है.

"एक साथ कई अपडेट" करने की सुविधा देने वाले तरीकों में ये शामिल हैं:

createWriteRequest()
उपयोगकर्ता से अनुरोध करें कि वह आपके ऐप्लिकेशन को मीडिया फ़ाइलों के चुने गए ग्रुप के डेटा में बदलाव करने का ऐक्सेस दे.
createFavoriteRequest()
उपयोगकर्ता से अनुरोध करें कि वह डिवाइस पर मौजूद मीडिया फ़ाइलों को "पसंदीदा" के तौर पर मार्क करे. जिस ऐप्लिकेशन के पास इस फ़ाइल को पढ़ने का ऐक्सेस है वह यह देख सकता है कि उपयोगकर्ता ने फ़ाइल को "पसंदीदा" के तौर पर मार्क किया है.
createTrashRequest()

उपयोगकर्ता से अनुरोध करें कि वह बताई गई मीडिया फ़ाइलों को डिवाइस के ट्रैश में रखे. सिस्टम के तय किए गए समय के बाद, ट्रैश में मौजूद आइटम हमेशा के लिए मिटा दिए जाते हैं.

createDeleteRequest()

उपयोगकर्ता से अनुरोध करें कि वह चुनी गई मीडिया फ़ाइलों को तुरंत हमेशा के लिए मिटा दे. इसके लिए, उन्हें पहले ट्रैश में न रखे.

इनमें से किसी भी तरीके का इस्तेमाल करने के बाद, सिस्टम एक PendingIntent ऑब्जेक्ट बनाता है. जब आपका ऐप्लिकेशन इस इंटेंट को शुरू करता है, तब उपयोगकर्ताओं को एक डायलॉग दिखता है. इसमें, ऐप्लिकेशन को चुनी गई मीडिया फ़ाइलों को अपडेट या मिटाने की सहमति देने का अनुरोध किया जाता है.

उदाहरण के लिए, createWriteRequest() को कॉल करने का तरीका यहां दिया गया है:

Kotlin

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)

Java

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

उपयोगकर्ता के जवाब का आकलन करें. अगर उपयोगकर्ता ने सहमति दी है, तो मीडिया ऑपरेशन जारी रखें. इसके अलावा, उपयोगकर्ता को यह भी बताएं कि आपके ऐप्लिकेशन को इस अनुमति की ज़रूरत क्यों है:

Kotlin

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

Java

@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 (एपीआई लेवल 31) या इसके बाद के वर्शन को टारगेट करता है, तो आपके पास यह अनुरोध करने का विकल्प होता है कि उपयोगकर्ता, आपके ऐप्लिकेशन को मीडिया मैनेजमेंट की खास अनुमति दें. इस अनुमति से आपका ऐप्लिकेशन, फ़ाइल से जुड़ी हर कार्रवाई के लिए उपयोगकर्ता से अनुमति मांगे बिना ये काम कर सकता है:

  • createWriteRequest() का इस्तेमाल करके, फ़ाइलों में बदलाव करें.
  • createTrashRequest() का इस्तेमाल करके, फ़ाइलों को ट्रैश में ले जाना और वहां से वापस लाना.
  • createDeleteRequest() का इस्तेमाल करके फ़ाइलें मिटाएं.

इसके लिए, यह तरीका अपनाएं:

  1. अपने ऐप्लिकेशन की मेनिफ़ेस्ट फ़ाइल में, MANAGE_MEDIA अनुमति और READ_EXTERNAL_STORAGE अनुमति का एलान करें.

    पुष्टि करने वाला डायलॉग बॉक्स दिखाए बिना createWriteRequest() को कॉल करने के लिए, ACCESS_MEDIA_LOCATION अनुमति भी दें.

  2. अपने ऐप्लिकेशन में, उपयोगकर्ता को एक यूज़र इंटरफ़ेस (यूआई) दिखाएं. इसमें बताएं कि वह आपके ऐप्लिकेशन को मीडिया मैनेज करने का ऐक्सेस क्यों देना चाहेगा.

  3. ACTION_REQUEST_MANAGE_MEDIA इंटेंट ऐक्शन को शुरू करें. इससे उपयोगकर्ता, सिस्टम सेटिंग में मौजूद मीडिया मैनेजमेंट ऐप्लिकेशन स्क्रीन पर पहुंच जाते हैं. यहां से, उपयोगकर्ता ऐप्लिकेशन को खास ऐक्सेस दे सकते हैं.

ऐसे इस्तेमाल के उदाहरण जिनके लिए मीडिया स्टोर के विकल्प की ज़रूरत होती है

अगर आपका ऐप्लिकेशन मुख्य रूप से इनमें से कोई एक काम करता है, तो MediaStore एपीआई के बजाय किसी दूसरे एपीआई का इस्तेमाल करें.

अन्य तरह की फ़ाइलों के साथ काम करना

अगर आपका ऐप्लिकेशन ऐसे दस्तावेज़ों और फ़ाइलों के साथ काम करता है जिनमें सिर्फ़ मीडिया कॉन्टेंट नहीं होता, जैसे कि ईपीयूबी या PDF फ़ाइल एक्सटेंशन का इस्तेमाल करने वाली फ़ाइलें, तो ACTION_OPEN_DOCUMENT इंटेंट ऐक्शन का इस्तेमाल करें. इसके बारे में, दस्तावेज़ों और अन्य फ़ाइलों को सेव करने और उन्हें ऐक्सेस करने से जुड़ी गाइड में बताया गया है.

साथी ऐप्लिकेशन में फ़ाइल शेयर करने की सुविधा

अगर आपको कंपैनियन ऐप्लिकेशन का सुइट उपलब्ध कराना है, जैसे कि मैसेजिंग ऐप्लिकेशन और प्रोफ़ाइल ऐप्लिकेशन, तो content:// यूआरआई का इस्तेमाल करके, फ़ाइल शेयर करने की सुविधा सेट अप करें. हमारा सुझाव है कि आप इस वर्कफ़्लो को सुरक्षा के सबसे सही तरीके के तौर पर अपनाएं.

अन्य संसाधन

मीडिया को सेव करने और उसे ऐक्सेस करने के तरीके के बारे में ज़्यादा जानने के लिए, यहां दिए गए संसाधन देखें.

सैंपल

  • MediaStore, GitHub पर उपलब्ध है

वीडियो