Daha zengin bir kullanıcı deneyimi sunmak için birçok uygulama, kullanıcıların harici depolama biriminde bulunan medyaya erişmesine ve bu medyaya katkıda bulunmasına olanak tanır. Bu çerçeve, medya koleksiyonları için medya deposu adı verilen optimize edilmiş bir dizin sağlar. Bu dizin, kullanıcıların medya dosyalarını daha kolay alıp güncellemesine olanak tanır. Bu dosyalar, uygulamanızın yüklemesi kaldırıldıktan sonra bile kullanıcının cihazında kalır.
Fotoğraf seçici
Android fotoğraf seçici aracı, medya deposunu kullanmaya alternatif olarak kullanıcılara medya dosyalarını seçmek için güvenli ve yerleşik bir yol sunar. Bu sayede, uygulamanızın tüm medya kitaplıklarına erişmesine izin vermeleri gerekmez. Bu özellik yalnızca desteklenen cihazlarda kullanılabilir. Daha fazla bilgi için fotoğraf seçici kılavuzuna bakın.
Medya deposu
Medya mağazası soyutlamasıyla etkileşimde bulunmak için uygulamanızın bağlamından aldığınız bir ContentResolver
nesnesi kullanın:
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, harici depolama birimini otomatik olarak tarar ve medya dosyalarını aşağıdaki iyi tanımlanmış koleksiyonlara ekler:
DCIM/
vePictures/
dizinlerinde depolanan fotoğraflar ve ekran görüntüleri dahil resimler. Sistem bu dosyalarıMediaStore.Images
tablosuna ekler.DCIM/
,Movies/
vePictures/
dizinlerinde depolanan videolar Sistem bu dosyalarıMediaStore.Video
tablosuna ekler.Alarms/
,Audiobooks/
,Music/
,Notifications/
,Podcasts/
veRingtones/
dizinlerinde depolanan ses dosyaları Ayrıca sistem,Music/
veyaMovies/
dizinlerindeki ses oynatma listelerini veRecordings/
dizinindeki ses kayıtlarını da tanır. Sistem bu dosyalarıMediaStore.Audio
tablosuna ekler.Recordings/
dizini, Android 11 (API düzeyi 30) ve önceki sürümlerde kullanılamaz.Download/
dizininde depolanan indirilen dosyalar Android 10 (API düzeyi 29) ve sonraki sürümleri çalıştıran cihazlarda bu dosyalarMediaStore.Downloads
tablosunda depolanır. Bu tablo, Android 9 (API düzeyi 28) ve önceki sürümlerde kullanılamaz.
Medya mağazasında MediaStore.Files
adlı bir koleksiyon da bulunur. İçeriği, uygulamanızın Android 10 veya sonraki sürümleri hedefleyen uygulamalarda kullanılabilen kapsamlı depolama kullanıp kullanmadığına bağlıdır.
- Kapsamlı depolama alanı etkinse koleksiyonda yalnızca uygulamanızın oluşturduğu fotoğraflar, videolar ve ses dosyaları gösterilir. Çoğu geliştiricinin diğer uygulamalardaki medya dosyalarını görüntülemek için
MediaStore.Files
kullanması gerekmez. Ancak bunu yapmanız için belirli bir gereksiniminiz varsaMediaStore.Files
iznini beyan edebilirsiniz.READ_EXTERNAL_STORAGE
Ancak uygulamanızın oluşturmadığıMediaStore
dosyaları açmak için API'leri kullanmanızı öneririz. - Kapsamlı depolama alanı kullanılamıyorsa veya kullanılmıyorsa koleksiyonda tüm medya dosyası türleri gösterilir.
Gerekli izinleri isteme
Medya dosyaları üzerinde işlem yapmadan önce uygulamanızın bu dosyalara erişmek için gereken izinleri bildirdiğinden emin olun. Ancak uygulamanızın ihtiyaç duymadığı veya kullanmadığı izinleri belirtmemeye dikkat edin.
Depolama izinleri
Uygulamanızın depolama alanına erişmek için izinlere ihtiyacı olup olmadığı, yalnızca kendi medya dosyalarına mı yoksa diğer uygulamalar tarafından oluşturulan dosyalara mı eriştiğine bağlıdır.
Kendi medya dosyalarınıza erişme
Android 10 veya sonraki sürümlerin yüklü olduğu cihazlarda, MediaStore.Downloads
koleksiyonundaki dosyalar da dahil olmak üzere uygulamanızın sahip olduğu medya dosyalarına erişmek ve bunları değiştirmek için depolamayla ilgili izinler vermeniz gerekmez. Örneğin, bir kamera uygulaması geliştiriyorsanız çektiği fotoğraflara erişmek için depolamayla ilgili izinler istemeniz gerekmez. Çünkü uygulamanız, medya deposuna yazdığınız resimlerin sahibidir.
Diğer uygulamaların medya dosyalarına erişme
Diğer uygulamaların oluşturduğu medya dosyalarına erişmek için depolamayla ilgili uygun izinleri bildirmeniz ve dosyaların aşağıdaki medya koleksiyonlarından birinde bulunması gerekir:
Bir dosya MediaStore.Images
, MediaStore.Video
veya MediaStore.Audio
sorgularından görüntülenebildiği sürece MediaStore.Files
sorgusu kullanılarak da görüntülenebilir.
Aşağıdaki kod snippet'inde, uygun depolama izinlerinin nasıl bildirileceği gösterilmektedir:
<!-- 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" />
Eski cihazlarda çalışan uygulamalar için gereken ek izinler
Uygulamanız Android 9 veya daha eski bir sürümün yüklü olduğu bir cihazda kullanılıyorsa ya da uygulamanız geçici olarak kapsamlı depolama alanını devre dışı bıraktıysa herhangi bir medya dosyasına erişmek için READ_EXTERNAL_STORAGE
iznini istemeniz gerekir. Medya dosyalarını değiştirmek istiyorsanız WRITE_EXTERNAL_STORAGE
izni de istemeniz gerekir.
Diğer uygulamaların indirmelerine erişmek için Depolama Alanı Erişim Çerçevesi gerekir
Uygulamanız, MediaStore.Downloads
koleksiyonunda
uygulamanızın oluşturmadığı bir dosyaya erişmek istiyorsa Depolama Erişimi Çerçevesi'ni kullanmanız gerekir. Bu çerçeveyi kullanma hakkında daha fazla bilgi edinmek için Paylaşılan depolama alanındaki dokümanlara ve diğer dosyalara erişme başlıklı makaleyi inceleyin.
Medya konum izni
Uygulamanız Android 10'u (API düzeyi 29) veya sonraki sürümleri hedefliyorsa ve fotoğraflardan düzenlenmemiş EXIF meta verilerini alması gerekiyorsa uygulamanızın manifest dosyasında ACCESS_MEDIA_LOCATION
iznini beyan etmeniz, ardından bu izni çalışma zamanında istemeniz gerekir.
Medya mağazasındaki güncellemeleri kontrol etme
Özellikle uygulamanız URI'leri veya medya mağazasındaki verileri önbelleğe alıyorsa medya dosyalarına daha güvenilir bir şekilde erişmek için medya mağazası sürümünün, medya verilerinizi son senkronize ettiğiniz zamana kıyasla değişip değişmediğini kontrol edin. Güncelleme olup olmadığını kontrol etmek için getVersion()
numaralı telefonu arayın.
Döndürülen sürüm, medya deposu önemli ölçüde değiştiğinde değişen benzersiz bir dizedir. Döndürülen sürüm, son senkronize edilen sürümden farklıysa uygulamanızın medya önbelleğini yeniden tarayıp yeniden senkronize edin.
Bu kontrolü uygulama süreci başlatılırken tamamlayın. Medya mağazasına her sorgu gönderdiğinizde sürümü kontrol etmeniz gerekmez.
Sürüm numarasıyla ilgili herhangi bir uygulama ayrıntısı varsaymayın.
Medya koleksiyonuna sorgu gönderme
Belirli bir koşul grubunu (ör. 5 dakika veya daha uzun süre) karşılayan medyaları bulmak için aşağıdaki kod snippet'inde gösterilene benzer bir SQL benzeri seçim ifadesi kullanın:
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)); } }
Uygulamanızda bu tür bir sorgu gerçekleştirirken aşağıdakileri göz önünde bulundurun:
- Bir çalışan iş parçacığında
query()
yöntemini çağırın. - Sorgu sonucundan bir satırı her işlediğinizde
getColumnIndexOrThrow()
işlevini çağırmanız gerekmemesi için sütun dizinlerini önbelleğe alın. - Kimliği, bu örnekte gösterildiği gibi içerik URI'sine ekleyin.
- Android 10 ve sonraki sürümlerin yüklü olduğu cihazlarda
MediaStore
API'sinde tanımlanan sütun adları gerekir. Uygulamanızdaki bağımlı bir kitaplık, API'de tanımlanmamış bir sütun adı (ör."MimeType"
) bekliyorsa uygulamanızın sürecindeki sütun adını dinamik olarak çevirmek içinCursorWrapper
kullanın.
Dosya küçük resimlerini yükleme
Uygulamanız birden fazla medya dosyası gösteriyorsa ve kullanıcının bu dosyalardan birini seçmesini istiyorsa dosyaların kendisi yerine dosyaların önizleme sürümlerini veya küçük resimlerini yüklemek daha verimlidir.
Belirli bir medya dosyasının küçük resmini yüklemek için
loadThumbnail()
kullanın ve yüklemek istediğiniz küçük resmin boyutunu aşağıdaki kod snippet'inde gösterildiği gibi iletin:
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);
Medya dosyası açma
Bir medya dosyasını açmak için kullandığınız belirli mantık, medya içeriğinin en iyi şekilde bir dosya tanımlayıcısı, bir dosya akışı veya doğrudan bir dosya yolu olarak gösterilip gösterilmediğine bağlıdır.
Dosya tanımlayıcısı
Bir medya dosyasını dosya tanımlayıcısı kullanarak açmak için aşağıdaki kod snippet'inde gösterilene benzer bir mantık kullanın:
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(); }
Dosya akışı
Bir dosya akışı kullanarak medya dosyası açmak için aşağıdaki kod snippet'inde gösterilene benzer bir mantık kullanın:
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". }
Doğrudan dosya yolları
Uygulamanızın üçüncü taraf medya kitaplıklarıyla daha sorunsuz çalışmasına yardımcı olmak için Android 11 (API düzeyi 30) ve sonraki sürümlerde, paylaşılan depolama alanındaki medya dosyalarına erişmek için MediaStore
API'si dışında API'ler kullanabilirsiniz. Bunun yerine, aşağıdaki API'lerden birini kullanarak medya dosyalarına doğrudan erişebilirsiniz:
File
API'sifopen()
gibi yerel kitaplıklar
Depolamayla ilgili herhangi bir izniniz yoksa File
API'yi kullanarak uygulamaya özel dizininizdeki dosyalara ve uygulamanızla ilişkilendirilmiş medya dosyalarına erişebilirsiniz.
Uygulamanız File
API'sini kullanarak bir dosyaya erişmeye çalışırsa ve gerekli izinlere sahip değilse FileNotFoundException
oluşur.
Android 10 (API düzeyi 29) çalıştıran bir cihazda paylaşılan depolama alanındaki diğer dosyalara erişmek için uygulamanızın manifest dosyasında requestLegacyExternalStorage
'i true
olarak ayarlayarak kapsamlı depolamayı geçici olarak devre dışı bırakmanızı öneririz. Android 10'da yerel dosya yöntemlerini kullanarak medya dosyalarına erişmek için READ_EXTERNAL_STORAGE
iznini de istemeniz gerekir.
Medya içeriğine erişirken dikkat edilmesi gereken noktalar
Medya içeriğine erişirken aşağıdaki bölümlerde ele alınan hususları göz önünde bulundurun.
Önbelleğe alınan veriler
Uygulamanız medya mağazasındaki URI'leri veya verileri önbelleğe alıyorsa medya mağazasında güncellemeleri düzenli olarak kontrol edin. Bu kontrol, uygulamanızda önbelleğe alınan verilerin sistem tarafındaki sağlayıcı verileriyle senkronize kalmasını sağlar.
Performans
Doğrudan dosya yollarını kullanarak medya dosyalarını sıralı okuduğunuzda performans, MediaStore
API'ninkine benzer.
Ancak doğrudan dosya yollarını kullanarak medya dosyalarında rastgele okuma ve yazma işlemleri yaptığınızda bu işlem iki kat daha yavaş olabilir. Bu durumlarda, bunun yerine MediaStore
API'yi kullanmanızı öneririz.
DATA sütunu
Mevcut bir medya dosyasına eriştiğinizde mantığınızda DATA
sütununun değerini kullanabilirsiniz. Bunun nedeni, bu değerin geçerli bir dosya yoluna sahip olmasıdır. Ancak dosyanın her zaman kullanılabileceğini varsaymayın. Dosya tabanlı G/Ç hatalarını işlemeye hazır olun.
Öte yandan, medya dosyası oluşturmak veya güncellemek için DATA
sütununun değerini kullanmayın. Bunun yerine, DISPLAY_NAME
ve RELATIVE_PATH
sütunlarının değerlerini kullanın.
Depolama birimleri
Android 10 veya sonraki sürümleri hedefleyen uygulamalar, sistemin her harici depolama birimine atadığı benzersiz ada erişebilir. Bu adlandırma sistemi, içerikleri verimli bir şekilde düzenlemenize ve dizine eklemenize yardımcı olur. Ayrıca, yeni medya dosyalarının nerede depolanacağı konusunda kontrol sahibi olmanızı sağlar.
Aşağıdaki hacimleri göz önünde bulundurmak özellikle yararlıdır:
VOLUME_EXTERNAL
birimi, cihazdaki tüm paylaşılan depolama birimlerinin görünümünü sağlar. Bu sentetik birimin içeriğini okuyabilirsiniz ancak içeriği değiştiremezsiniz.VOLUME_EXTERNAL_PRIMARY
birimi, cihazdaki birincil paylaşılan depolama birimini temsil eder. Bu birimin içeriğini okuyabilir ve değiştirebilirsiniz.
Diğer ciltleri keşfetmek için
MediaStore.getExternalVolumeNames()
numaralı telefonu arayabilirsiniz:
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();
Medyanın çekildiği konum
Bazı fotoğraf ve videoların meta verilerinde, fotoğrafın çekildiği veya videonun kaydedildiği yeri gösteren konum bilgileri bulunur.
Uygulamanızda bu konum bilgilerine nasıl erişeceğiniz, bir fotoğrafın mı yoksa bir videonun konum bilgilerine erişmeniz gerektiğine bağlıdır.
Fotoğraflar
Uygulamanız kapsamlı depolama kullanıyorsa sistem, konum bilgilerini varsayılan olarak gizler. Bu bilgilere erişmek için aşağıdaki adımları uygulayın:
- Uygulamanızın manifest dosyasında
ACCESS_MEDIA_LOCATION
iznini isteyin. MediaStore
nesnenizden,setRequireOriginal()
işlevini çağırıp aşağıdaki kod snippet'inde gösterildiği gibi fotoğrafın URI'sini ileterek fotoğrafın tam baytlarını alın: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]; }
Videolar
Bir videonun meta verilerindeki konum bilgilerine erişmek için aşağıdaki kod snippet'inde gösterildiği gibi MediaMetadataRetriever
sınıfını kullanın. Uygulamanızın bu sınıfı kullanmak için ek izin istemesi gerekmez.
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); }
Paylaşım
Bazı uygulamalar, kullanıcıların birbirleriyle medya dosyaları paylaşmasına olanak tanır. Örneğin, sosyal medya uygulamaları kullanıcıların arkadaşlarıyla fotoğraf ve video paylaşmasına olanak tanır.
Medya dosyalarını paylaşmak için İçerik sağlayıcı oluşturma kılavuzunda önerildiği gibi content://
URI'si kullanın.
Medya dosyalarının uygulama ilişkilendirmesi
Android 10 veya sonraki sürümleri hedefleyen bir uygulamada kapsamlı depolama etkinleştirildiğinde sistem, uygulamayı her medya dosyasıyla ilişkilendirir. Bu ilişkilendirme, uygulamanız herhangi bir depolama izni istemediğinde erişebileceği dosyaları belirler. Her dosya yalnızca bir uygulamaya atanabilir. Bu nedenle, uygulamanız fotoğraflar, videolar veya ses dosyaları medya koleksiyonunda depolanan bir medya dosyası oluşturursa uygulamanız bu dosyaya erişebilir.
Ancak kullanıcı uygulamanızı kaldırıp yeniden yüklerse uygulamanızın başlangıçta oluşturduğu dosyalara erişmek için
READ_EXTERNAL_STORAGE
istek göndermeniz gerekir. Bu izin isteği, sistem dosyayı yeni yüklenen uygulama sürümüne değil, daha önce yüklenen uygulama sürümüne atfedilmiş olarak değerlendirdiği için gereklidir.
Android 16 veya sonraki sürümlerin yüklü olduğu cihazlarda SDK 36 veya sonraki sürümleri hedefleyen bir uygulama tarafından fotoğraf ve video izinleri istendiğinde, erişimi seçili medyayla sınırlamayı tercih eden kullanıcılar, fotoğraf seçicide uygulamaya ait tüm fotoğrafların önceden seçilmiş olduğunu görür. Kullanıcılar, önceden seçilmiş bu öğelerin seçimini kaldırabilir. Bu durumda, uygulamanın söz konusu fotoğraf ve videolara erişimi iptal edilir.
Öğe ekleyin
Mevcut bir koleksiyona medya öğesi eklemek için aşağıdakine benzer bir kod kullanın. Bu kod snippet'i, Android 10 veya sonraki sürümlerin yüklü olduğu cihazlarda VOLUME_EXTERNAL_PRIMARY
ses düzeyine
erişir. Bunun nedeni, bu cihazlarda bir birimin içeriğini yalnızca Depolama birimleri bölümünde açıklandığı gibi birincil birimse değiştirebilmenizdir.
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);
Medya dosyaları için bekleme durumunu açma/kapatma
Uygulamanız, medya dosyalarına yazma gibi zaman alabilecek işlemler gerçekleştiriyorsa dosya işlenirken dosyaya özel erişiminizin olması yararlı olur. Android 10 veya sonraki sürümlerin yüklü olduğu cihazlarda uygulamanız, IS_PENDING
işaretinin değerini 1 olarak ayarlayarak bu özel erişimi elde edebilir. Uygulamanız, IS_PENDING
değerini tekrar 0 olarak değiştirene kadar dosyayı yalnızca uygulamanız görüntüleyebilir.
Aşağıdaki kod snippet'i, önceki kod snippet'i üzerine kurulmuştur. Bu snippet'te, uzun bir şarkıyı MediaStore.Audio
koleksiyonuna karşılık gelen dizinde saklarken IS_PENDING
işaretinin nasıl kullanılacağı gösterilmektedir:
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);
Dosya konumuyla ilgili ipucu verme
Uygulamanız, Android 10 çalıştıran bir cihazda medya depoladığında medya varsayılan olarak türüne göre düzenlenir. Örneğin, yeni resim dosyaları varsayılan olarak MediaStore.Images
koleksiyonuna karşılık gelen Environment.DIRECTORY_PICTURES
dizinine yerleştirilir.
Uygulamanız, dosyaların depolanabileceği belirli bir konumun (ör. Pictures/MyVacationPictures
adlı bir fotoğraf albümü) farkındaysa MediaColumns.RELATIVE_PATH
'ı ayarlayarak sisteme yeni yazılan dosyaların nereye depolanacağı konusunda ipucu verebilirsiniz.
Öğe güncelleme
Uygulamanızın sahibi olduğu bir medya dosyasını güncellemek için aşağıdakine benzer bir kod kullanın:
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);
Kapsamlı depolama kullanılamıyorsa veya etkinleştirilmemişse önceki kod snippet'inde gösterilen işlem, uygulamanızın sahibi olmadığı dosyalar için de geçerlidir.
Yerel kodda güncelleme
Yerel kitaplıkları kullanarak medya dosyaları yazmanız gerekiyorsa dosyanın ilişkili dosya tanımlayıcısını Java veya Kotlin tabanlı kodunuzdan yerel kodunuza iletin.
Aşağıdaki kod snippet'inde, bir medya nesnesinin dosya tanımlayıcısının uygulamanızın yerel koduna nasıl aktarılacağı gösterilmektedir:
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. }
Diğer uygulamaların medya dosyalarını güncelleme
Uygulamanız kapsamlı depolama kullanıyorsa normalde farklı bir uygulamanın medya mağazasına katkıda bulunduğu bir medya dosyasını güncelleyemez.
Ancak platformun oluşturduğu RecoverableSecurityException
yakalayarak dosyayı değiştirmek için kullanıcı izni alabilirsiniz. Ardından, aşağıdaki kod snippet'inde gösterildiği gibi, kullanıcının uygulamanıza söz konusu öğeye yazma erişimi vermesini isteyebilirsiniz:
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); } }
Uygulamanızın oluşturmadığı bir medya dosyasını değiştirmesi gerektiğinde bu işlemi tamamlayın.
Alternatif olarak, uygulamanız Android 11 veya sonraki sürümlerde çalışıyorsa kullanıcıların uygulamanıza bir grup medya dosyasına yazma erişimi vermesine izin verebilirsiniz. Medya dosyası gruplarını yönetme bölümünde açıklandığı gibi
createWriteRequest()
yöntemini kullanın.
Uygulamanızın, kapsamlı depolama tarafından kapsanmayan başka bir kullanım alanı varsa özellik isteğinde bulunun ve kapsamlı depolamayı geçici olarak devre dışı bırakın.
Öğe kaldırma
Uygulamanızın artık medya mağazasında ihtiyaç duymadığı bir öğeyi kaldırmak için aşağıdaki kod snippet'inde gösterilene benzer bir mantık kullanın:
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);
Kapsamlı depolama alanı kullanılamıyorsa veya etkinleştirilmemişse diğer uygulamalara ait dosyaları kaldırmak için yukarıdaki kod snippet'ini kullanabilirsiniz. Ancak kapsamlı depolama etkinse medya öğelerini güncelleme bölümünde açıklandığı gibi, uygulamanızın kaldırmak istediği her dosya için bir RecoverableSecurityException
yakalamanız gerekir.
Uygulamanız Android 11 veya sonraki sürümlerde çalışıyorsa kullanıcıların kaldırılacak medya dosyaları grubunu seçmesine izin verebilirsiniz. Medya dosyası gruplarını yönetme bölümünde açıklandığı gibi createTrashRequest()
veya createDeleteRequest()
yöntemini kullanın.
Uygulamanızın, kapsamlı depolama tarafından kapsanmayan başka bir kullanım alanı varsa özellik isteğinde bulunun ve kapsamlı depolamayı geçici olarak devre dışı bırakın.
Medya dosyalarındaki güncellemeleri algılama
Uygulamanızın, uygulamaların eklediği veya değiştirdiği medya dosyalarını içeren depolama birimlerini önceki bir zamana göre tanımlaması gerekebilir. Bu değişiklikleri en güvenilir şekilde algılamak için ilgilendiğiniz depolama birimini getGeneration()
'e iletin.
Medya mağazası sürümü değişmediği sürece bu yöntemin döndürdüğü değer zaman içinde monoton bir şekilde artar.
Özellikle getGeneration()
, DATE_ADDED
ve DATE_MODIFIED
gibi medya sütunlarındaki tarihlerden daha sağlamdır.
Bunun nedeni, bir uygulama setLastModified()
işlevini çağırdığında veya kullanıcı sistem saatini değiştirdiğinde bu medya sütunu değerlerinin değişebilmesidir.
Medya dosyası gruplarını yönetme
Android 11 ve sonraki sürümlerde, kullanıcıdan bir grup medya dosyası seçmesini isteyebilir, ardından bu medya dosyalarını tek bir işlemde güncelleyebilirsiniz. Bu yöntemler, cihazlar arasında daha iyi tutarlılık sunar ve kullanıcıların medya koleksiyonlarını yönetmesini kolaylaştırır.
Bu "toplu güncelleme" işlevini sağlayan yöntemler şunlardır:
createWriteRequest()
- Kullanıcıdan, uygulamanıza belirtilen medya dosyaları grubuna yazma erişimi vermesini isteyin.
createFavoriteRequest()
- Kullanıcıdan belirtilen medya dosyalarını cihazdaki "favori" medyalarından biri olarak işaretlemesini isteyin. Bu dosyaya okuma erişimi olan tüm uygulamalar, kullanıcının dosyayı "favori" olarak işaretlediğini görebilir.
createTrashRequest()
Kullanıcıdan belirtilen medya dosyalarını cihazın çöp kutusuna yerleştirmesini isteyin. Çöp kutusundaki öğeler, sistem tarafından tanımlanan bir süre sonra kalıcı olarak silinir.
createDeleteRequest()
Kullanıcıdan, belirtilen medya dosyalarını önceden çöp kutusuna yerleştirmeden hemen kalıcı olarak silmesini isteyin.
Bu yöntemlerden herhangi biri çağrıldıktan sonra sistem bir PendingIntent
nesnesi oluşturur. Uygulamanız bu amaç filtresini çağırdıktan sonra kullanıcılara, uygulamanızın belirtilen medya dosyalarını güncellemesi veya silmesi için izinlerini isteyen bir iletişim kutusu gösterilir.
Örneğin, createWriteRequest()
ile ilgili bir çağrının nasıl yapılandırılacağı aşağıda açıklanmıştır:
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);
Kullanıcının yanıtını değerlendirin. Kullanıcı izin verdiyse medya işlemine devam edin. Aksi takdirde, uygulamanızın neden izne ihtiyacı olduğunu kullanıcıya açıklayın:
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. */ } } }
Aynı genel kalıbı createFavoriteRequest()
, createTrashRequest()
ve createDeleteRequest()
ile de kullanabilirsiniz.
Medya yönetimi izni
Kullanıcılar, medya dosyalarında sık sık düzenleme yapma gibi medya yönetimi işlemlerini gerçekleştirmek için belirli bir uygulamaya güvenebilir. Uygulamanız Android 11 veya sonraki sürümleri hedefliyorsa ve cihazın varsayılan galeri uygulaması değilse uygulamanız her dosya değiştirmeyi veya silmeyi denediğinde kullanıcıya bir onay iletişim kutusu göstermeniz gerekir.
Uygulamanız Android 12'yi (API düzeyi 31) veya sonraki sürümleri hedefliyorsa kullanıcılardan uygulamanıza medya yönetimi özel izni vermelerini isteyebilirsiniz. Bu izin, uygulamanızın her dosya işlemi için kullanıcıdan istemde bulunmasına gerek kalmadan aşağıdakilerin her birini yapmasına olanak tanır:
createWriteRequest()
kullanarak dosyaları değiştirin.createTrashRequest()
simgesini kullanarak dosyaları çöp kutusuna ve çöp kutusundan taşımacreateDeleteRequest()
simgesini kullanarak dosyaları silme
Bunun için aşağıdaki adımları uygulayın:
Uygulamanızın manifest dosyasında
MANAGE_MEDIA
iznini veREAD_EXTERNAL_STORAGE
iznini beyan edin.Onay iletişim kutusu göstermeden
createWriteRequest()
'ı aramak içinACCESS_MEDIA_LOCATION
iznini de bildirin.Uygulamanızda, kullanıcıya medya yönetimi erişimini neden uygulamanıza vermek isteyebileceğini açıklayan bir kullanıcı arayüzü gösterin.
ACTION_REQUEST_MANAGE_MEDIA
amaç işlemini çağırın. Bu işlem, kullanıcıları sistem ayarlarındaki Medya yönetimi uygulamaları ekranına yönlendirir. Kullanıcılar buradan özel uygulama erişimi verebilir.
Medya mağazasına alternatif gerektiren kullanım alanları
Uygulamanız temel olarak aşağıdaki rollerden birini yerine getiriyorsa MediaStore
API'lerine alternatif bir çözüm kullanmayı düşünebilirsiniz.
Diğer dosya türleriyle çalışma
Uygulamanız yalnızca medya içeriği içermeyen dokümanlar ve dosyalarla (ör. EPUB veya PDF dosya uzantısını kullanan dosyalar) çalışıyorsa dokümanları ve diğer dosyaları depolama ve bunlara erişme kılavuzunda açıklandığı gibi ACTION_OPEN_DOCUMENT
amaç işlemini kullanın.
Tamamlayıcı uygulamalarda dosya paylaşımı
Mesajlaşma uygulaması ve profil uygulaması gibi bir grup yardımcı uygulama sağladığınız durumlarda, content://
URI'lerini kullanarak dosya paylaşımını ayarlayın. Bu iş akışını güvenlik için en iyi uygulama olarak da öneririz.
Ek kaynaklar
Medya dosyalarını depolama ve bunlara erişme hakkında daha fazla bilgi için aşağıdaki kaynaklara göz atın.
Örnekler
- GitHub'da bulunan MediaStore