Mengakses file media dari penyimpanan bersama

Untuk memberikan pengalaman pengguna yang lebih kaya, banyak aplikasi memungkinkan pengguna berkontribusi dan mengakses media yang tersedia pada volume penyimpanan eksternal. Framework ini menyediakan indeks yang dioptimalkan ke koleksi media, yang disebut penyimpanan media, yang memungkinkan untuk mengambil dan memperbarui file media ini dengan lebih mudah. Bahkan setelah aplikasi Anda di-uninstal, file ini tetap berada di perangkat pengguna.

Untuk berinteraksi dengan abstraksi penyimpanan media, gunakan objek ContentResolver yang Anda ambil dari konteks aplikasi:

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

Sistem memindai volume penyimpanan eksternal secara otomatis dan menambahkan file media ke koleksi yang terdefinisi dengan baik berikut ini:

  • Gambar, termasuk foto dan screenshot, yang disimpan di direktori DCIM/ dan Pictures/. Sistem akan menambahkan file ini ke tabel MediaStore.Images.
  • Video, yang disimpan di direktori DCIM/, Movies/, dan Pictures/. Sistem akan menambahkan file ini ke tabel MediaStore.Video.
  • File audio, yang disimpan di direktori Alarms/, Audiobooks/, Music/, Notifications/, Podcasts/, dan Ringtones/, serta playlist audio yang berada di direktori Music/ atau Movies/. Sistem akan menambahkan file ini ke tabel MediaStore.Audio.
  • File yang didownload, yang disimpan di dalam direktori Download/. Pada perangkat yang menjalankan Android 10 (API level 29) dan yang lebih tinggi, file ini disimpan di tabel MediaStore.Downloads. Tabel ini tidak tersedia di Android 9 (API level 28) versi yang lebih rendah.

Penyimpanan media juga menyertakan koleksi bernama MediaStore.Files. Kontennya bergantung pada apakah aplikasi Anda menggunakan penyimpanan terbatas, tersedia di aplikasi yang menargetkan Android 10 atau yang lebih tinggi:

  • Jika penyimpanan terbatas diaktifkan, koleksi hanya akan menampilkan foto, video, dan file audio yang dibuat oleh aplikasi Anda.
  • Jika penyimpanan terbatas tidak tersedia atau tidak sedang digunakan, koleksi akan menampilkan semua jenis file media.

Meminta izin yang diperlukan

Sebelum menjalankan operasi pada file media, pastikan aplikasi Anda telah mendeklarasikan izin yang diperlukan untuk mengakses file ini. Namun, perlu diingat bahwa aplikasi Anda tidak boleh mendeklarasikan izin yang tidak diperlukan atau digunakan.

Izin penyimpanan

Model izin untuk mengakses file media di aplikasi Anda bergantung pada apakah aplikasi Anda menggunakan penyimpanan terbatas, tersedia di aplikasi yang menargetkan Android 10 atau yang lebih tinggi.

Penyimpanan terbatas diaktifkan

Jika aplikasi Anda menggunakan penyimpanan terbatas, seharusnya aplikasi Anda meminta izin terkait penyimpanan hanya untuk perangkat yang menjalankan Android 9 (API level 28) atau yang lebih rendah. Anda dapat memberlakukan kondisi ini dengan menambahkan atribut android:maxSdkVersion pada pernyataan izin di file manifes aplikasi Anda:

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

Tidak perlu meminta izin terkait penyimpanan untuk perangkat yang menjalankan Android 10 atau yang lebih tinggi. Aplikasi Anda dapat berkontribusi untuk koleksi media yang terdefinisi dengan baik, termasuk koleksi MediaStore.Downloads, tanpa meminta izin terkait penyimpanan. Misalnya, jika Anda mengembangkan aplikasi kamera, Anda tidak perlu meminta izin terkait penyimpanan karena aplikasi Anda memiliki image yang Anda tulis ke penyimpanan media.

Untuk mengakses file yang telah dibuat oleh aplikasi lain, masing-masing kondisi berikut ini harus terpenuhi:

Secara khusus, jika aplikasi Anda ingin mengakses file dalam koleksi MediaStore.Downloads yang tidak dibuat oleh aplikasi Anda, Anda harus menggunakan Storage Access Framework. Untuk mempelajari lebih lanjut petunjuk menggunakan framework ini, lihat panduan mengenai petunjuk mengakses dokumen dan file lainnya.

Penyimpanan terbatas tidak tersedia

Jika aplikasi Anda digunakan pada perangkat yang menjalankan Android 9 atau yang lebih rendah, atau jika aplikasi Anda menggunakan fitur kompatibilitas penyimpanan, Anda harus meminta izin READ_EXTERNAL_STORAGE untuk mengakses file media. Jika ingin mengubah file media, Anda juga harus meminta izin WRITE_EXTERNAL_STORAGE.

Izin lokasi media

Jika aplikasi Anda menggunakan penyimpanan terbatas, agar aplikasi Anda dapat mengambil metadata Exif yang tidak tersunting dari foto, Anda harus mendeklarasikan izin ACCESS_MEDIA_LOCATION di manifes aplikasi Anda, lalu meminta izin ini pada waktu proses.

Mengkueri koleksi media

Untuk menemukan media yang memenuhi sekumpulan kondisi tertentu, seperti durasi 5 menit atau lebih, gunakan pernyataan pilihan seperti SQL yang mirip dengan yang ditunjukkan pada cuplikan kode berikut ini:

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 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(
    MediaStore.Video.Media.EXTERNAL_CONTENT_URI,
    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>();

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(
    MediaStore.Video.Media.EXTERNAL_CONTENT_URI,
    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));
    }
}

Saat menjalankan kueri semacam itu di aplikasi Anda, perhatikan hal berikut ini:

  • Panggil metode query() di thread pekerja.
  • Simpan indeks kolom dalam cache sehingga tidak perlu memanggil getColumnIndexOrThrow() setiap kali Anda memproses baris dari hasil kueri.
  • Tambahkan ID ke URI konten, seperti yang ditunjukkan dalam cuplikan kode.
  • Perangkat yang menjalankan Android 10 dan yang lebih tinggi memerlukan nama kolom yang ditentukan di MediaStore API. Jika library dependen dalam aplikasi Anda memerlukan nama kolom yang tidak ditentukan dalam API, misalnya "MimeType", gunakan CursorWrapper untuk menerjemahkan nama kolom secara dinamis dalam proses aplikasi Anda.

Memuat thumbnail file

Jika aplikasi Anda menampilkan beberapa file media dan meminta pengguna memilih salah satu file ini, akan lebih efisien untuk memuat versi pratinjau—atau thumbnail—bukan file itu sendiri.

Untuk memuat thumbnail file media tertentu, gunakan loadThumbnail() dan teruskan ukuran thumbnail yang ingin Anda muat, seperti yang ditunjukkan dalam cuplikan kode berikut ini:

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

Membuka file media

Logika khusus yang Anda gunakan untuk membuka file media bergantung pada apakah konten media sebaiknya ditunjukkan sebagai deskriptor file atau aliran file:

Deskriptor file

Untuk membuka file media menggunakan deskriptor file, gunakan logika yang mirip dengan yang ditunjukkan dalam cuplikan kode berikut ini:

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

Aliran file

Untuk membuka file media menggunakan aliran file, gunakan logika yang mirip dengan yang ditunjukkan dalam cuplikan kode berikut ini:

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

Pertimbangan saat mengakses konten media

Saat mengakses konten media, perhatikan pertimbangan yang dibahas di bagian berikut ini.

Volume penyimpanan

Aplikasi yang menargetkan Android 10 atau yang lebih tinggi dapat mengakses nama unik yang ditetapkan oleh sistem untuk setiap volume penyimpanan eksternal. Sistem penamaan ini membantu Anda mengatur dan mengindeks konten secara efisien, dan memberi Anda kontrol terhadap lokasi file media baru yang disimpan.

Volume penyimpanan bersama utama selalu bernama VOLUME_EXTERNAL_PRIMARY. Anda dapat menemukan volume lain dengan memanggil 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();

Informasi lokasi dalam foto

Beberapa foto berisi informasi lokasi di metadata Exif, yang memungkinkan pengguna melihat tempat diambilnya foto. Namun, karena informasi lokasi ini bersifat sensitif, Android 10 secara default menyembunyikannya dari aplikasi Anda jika aplikasi Anda menggunakan penyimpanan terbatas.

Jika aplikasi Anda memerlukan akses ke informasi lokasi foto, selesaikan langkah-langkah berikut:

  1. Minta izin ACCESS_MEDIA_LOCATION dalam manifes aplikasi Anda.
  2. Dari objek MediaStore Anda, dapatkan byte foto yang tepat dengan memanggil setRequireOriginal() dan teruskan URI foto, seperti yang ditunjukkan dalam cuplikan kode berikut:

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

Berbagi media

Beberapa aplikasi memungkinkan pengguna saling berbagi file media. Misalnya, aplikasi media sosial memberi pengguna kemampuan untuk membagikan foto dan video kepada teman.

Untuk berbagi file media, gunakan URI content://, seperti yang disarankan dalam panduan untuk membuat penyedia konten.

Akses konten menggunakan jalur file raw

Jika tidak memiliki izin terkait penyimpanan, Anda dapat mengakses file di direktori khusus aplikasi, serta file media yang terkait dengan aplikasi, menggunakan File API.

Jika aplikasi Anda mencoba mengakses file menggunakan File API, dan tidak memiliki izin yang diperlukan, FileNotFoundException akan terjadi.

Untuk mengakses file lain dalam penyimpanan bersama di perangkat yang menjalankan Android 10, sebaiknya Anda memilih tidak ikut menggunakan penyimpanan terbatas dengan menetapkan requestLegacyExternalStorage ke true dalam file manifes aplikasi Anda.

Akses konten dari kode native

Anda mungkin mengalami situasi ketika aplikasi Anda perlu menangani file media tertentu dalam kode native, seperti file yang telah dibagikan aplikasi lain kepada aplikasi Anda, atau file media dari koleksi media pengguna.

Sebelum aplikasi Anda dapat membaca file media menggunakan metode file native seperti fopen(), Anda harus melakukan hal berikut:

  1. Tetapkan requestLegacyExternalStorage ke true di file manifes aplikasi Anda.
  2. Minta izin READ_EXTERNAL_STORAGE.

Jika Anda perlu menulis ke file media ini, teruskan deskriptor file terkait file dari kode berbasis Java atau Koltin ke kode native. Cuplikan kode berikut ini menunjukkan cara meneruskan deskriptor file objek media ke kode native aplikasi Anda:

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

Untuk mempelajari lebih lanjut cara mengakses file dalam kode native, lihat presentasi Files for Miles dari Android Dev Summit '18, mulai pukul 15:20.

Atribusi aplikasi dari file media

Saat penyimpanan terbatas diaktifkan untuk aplikasi yang menargetkan Android 10 atau yang lebih tinggi, sistem menghubungkan aplikasi ke setiap file media, yang menentukan file yang dapat diakses oleh aplikasi Anda saat aplikasi Anda belum meminta izin penyimpanan apa pun. Setiap file hanya dapat dihubungkan dengan satu aplikasi. Oleh karena itu, jika aplikasi Anda membuat file media yang disimpan dalam koleksi media file foto, video, atau audio, aplikasi Anda memiliki akses ke file tersebut.

Namun, jika pengguna meng-uninstal dan menginstal ulang aplikasi Anda, Anda harus meminta READ_EXTERNAL_STORAGE untuk mengakses file yang awalnya dibuat oleh aplikasi Anda. Permintaan izin ini diperlukan karena sistem menganggap file tersebut terkait dengan versi aplikasi yang diinstal sebelumnya, bukan versi yang baru diinstal.

Menambahkan item

Untuk menambahkan item media ke koleksi yang ada, panggil kode yang mirip berikut ini:

Kotlin

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

// Find all audio files on the primary external storage device.
// On API <= 28, use VOLUME_EXTERNAL instead.
val audioCollection = MediaStore.Audio.Media.getContentUri(
        MediaStore.VOLUME_EXTERNAL_PRIMARY)

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

// Keeps a handle to the new song's URI in case we 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.
// On API <= 28, use VOLUME_EXTERNAL instead.
Uri audioCollection = MediaStore.Audio.Media.getContentUri(
        MediaStore.VOLUME_EXTERNAL_PRIMARY);

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

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

Mengaktifkan atau menonaktifkan status tertunda untuk file media

Jika aplikasi Anda menjalankan operasi yang perlu waktu lebih banyak, seperti menulis ke file media, ada gunanya untuk memiliki akses eksklusif ke file saat sedang diproses. Pada perangkat yang menjalankan Android 10 atau yang lebih tinggi, aplikasi Anda bisa mendapatkan akses eksklusif ini dengan menyetel nilai flag IS_PENDING ke 1. Hanya aplikasi Anda yang dapat menampilkan file sampai aplikasi Anda mengubah nilai IS_PENDING kembali ke 0.

Cuplikan kode berikut ini dibuat berdasarkan cuplikan kode sebelumnya. Cuplikan ini menunjukkan cara menggunakan flag IS_PENDING saat menyimpan lagu panjang di dalam direktori yang sesuai dengan koleksi MediaStore.Audio:

Kotlin

// Add a media item that other apps shouldn'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.
// On API <= 28, use VOLUME_EXTERNAL instead.
val audioCollection = MediaStore.Audio.Media
        .getContentUri(MediaStore.VOLUME_EXTERNAL_PRIMARY)

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)

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

// Now that we're finished, release the "pending" status, and allow other apps
// to 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 shouldn'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.
// On API <= 28, use VOLUME_EXTERNAL instead.
Uri audioCollection = MediaStore.Audio.Media.getContentUri(
        MediaStore.VOLUME_EXTERNAL_PRIMARY);

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

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

try (ParcelableFileDescriptor pfd =
        resolver.openFileDescriptor(longSongContentUri, "w", null)) {
    // Write data into the pending audio file.
}

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

Memberikan petunjuk untuk lokasi file

Saat aplikasi Anda menyimpan media di perangkat yang menjalankan Android 10, media akan diatur berdasarkan jenisnya secara default. Misalnya, file gambar baru ditempatkan secara default di dalam direktori Environment.DIRECTORY_PICTURES, yang sesuai dengan koleksi MediaStore.Images.

Jika aplikasi Anda mengetahui lokasi spesifik tempat file seharusnya disimpan, seperti album bernama Pictures/MyVacationPictures, Anda dapat menyetel MediaColumns.RELATIVE_PATH untuk memberikan petunjuk kepada sistem tempat penyimpanan file yang baru ditulis.

Memperbarui item

Untuk memperbarui file media yang dimiliki aplikasi Anda, jalankan kode yang mirip dengan hal berikut ini:

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

Jika penyimpanan terbatas tidak tersedia atau tidak diaktifkan, proses yang ditunjukkan dalam kode cuplikan sebelumnya juga berfungsi untuk file yang tidak dimiliki oleh aplikasi Anda.

Memperbarui file media aplikasi lain

Jika aplikasi Anda menggunakan penyimpanan terbatas, biasanya aplikasi Anda tidak dapat memperbarui file media yang dikontribusikan ke penyimpanan media oleh aplikasi yang berbeda.

Anda masih bisa mendapatkan izin pengguna untuk mengubah file, tetapi, dengan menangkap RecoverableSecurityException yang dilempar oleh platform. Selanjutnya, Anda dapat meminta agar pengguna memberi aplikasi Anda akses tulis ke item spesifik, seperti yang ditunjukkan dalam cuplikan kode berikut ini:

Kotlin

// Apply a grayscale filter to the image at the given content URI.
try {
    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 {
    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);
    }
}

Selesaikan proses ini setiap kali aplikasi Anda perlu mengubah file media yang tidak dibuatnya.

Jika aplikasi Anda memiliki kasus penggunaan lain yang tidak tercakup oleh penyimpanan terbatas, ajukan permintaan fitur dan gunakan fitur kompatibilitas aplikasi yang disediakan oleh platform.

Menghapus item

Untuk menghapus item yang tidak lagi diperlukan aplikasi Anda dalam penyimpanan media, gunakan logika yang mirip dengan yang ditunjukkan dalam cuplikan kode berikut:

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

Jika penyimpanan terbatas tidak tersedia atau tidak diaktifkan, Anda dapat menggunakan cuplikan kode sebelumnya untuk menghapus file yang dimiliki oleh aplikasi lain. Namun, jika penyimpanan terbatas diaktifkan, Anda perlu menangkap RecoverableSecurityException untuk setiap file yang ingin dihapus oleh aplikasi Anda, seperti yang dideskripsikan di bagian mengenai memperbarui item media.

Jika aplikasi Anda memiliki kasus penggunaan lain yang tidak tercakup oleh penyimpanan terbatas, ajukan permintaan fitur dan gunakan fitur kompatibilitas aplikasi yang disediakan oleh platform.

Kasus penggunaan yang memerlukan alternatif penyimpanan media

Jika sebagian besar aplikasi Anda menjalankan salah satu peran berikut ini, pertimbangkan alternatif untuk MediaStore API.

Mengelola grup file media

Aplikasi pembuatan media biasanya mengelola grup file menggunakan hierarki direktori. Untuk menyediakan kemampuan ini di aplikasi Anda, gunakan tindakan intent ACTION_OPEN_DOCUMENT_TREE, seperti yang dideskripsikan dalam panduan mengenai petunjuk menyimpan dan mengakses dokumen serta file lainnya.

Menangani jenis file lain

Jika aplikasi Anda menangani dokumen dan file yang tidak berisi konten media secara eksklusif, seperti file yang menggunakan ekstensi file EPUB atau PDF, gunakan tindakan intent ACTION_OPEN_DOCUMENT, seperti yang dideskripsikan dalam panduan mengenai petunjuk menyimpan dan mengakses dokumen serta file lainnya.

Berbagi file di aplikasi pendamping

Apabila Anda menyediakan serangkaian aplikasi pendamping—seperti aplikasi pesan dan aplikasi profil—siapkan berbagi file menggunakan URI content://. Kami juga merekomendasikan alur kerja ini sebagai praktik keamanan terbaik.

Referensi lainnya

Untuk informasi lebih lanjut mengenai petunjuk menyimpan dan mengakses media, lihat referensi berikut ini.

Contoh

Video