Q Android 10/11(R) 分区存储适配( 二 )

Sample

  • 使用 MediaStore 增删改查媒体集
  • 使用 Storage Access Framework 访问文件集
1. 媒体集
1) 查询媒体集(需要 READ_EXTERNAL_STORAGE 权限)
实际上 MediaStore 是以前就有的 API  , 不同的是过去主要通过 MediaStore.Video.Media._DATA这个 colum 请求原始数据 , 可以得到绝对Uri  , 现在需要请求MediaStore.Video.Media._ID来得到相对Uri再进行处理 。
// 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)}}2)插入媒体集(无需权限)
// 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)3)更新自己创建的媒体集(无需权限)
删除类似
// 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 IDval 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)4)更新/删除其它媒体创建的媒体集
若已经开启分区存储则会抛出 RecoverableSecurityException , 捕获并通过SAF请求权限
// 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.intentSenderintentSender?.let {startIntentSenderForResult(intentSender, image-request-code,null, 0, 0, 0, null)}} else {throw RuntimeException(securityException.message, securityException)}}2. 文件集 (通过 SAF)
1)创建文档
注:创建操作若重名的话不会覆盖原文档 , 会添加 (1) 最为后缀 , 如 document.pdf -> document(1).pdf


推荐阅读