Media scanner for secondary storage on Android Q

前端 未结 1 1907
鱼传尺愫
鱼传尺愫 2021-02-01 19:57

With the newer Android Q many things changed, especially with scoped storage and gradual deprecation of file:/// URIs. The problem is the lack of documentation on h

1条回答
  •  挽巷
    挽巷 (楼主)
    2021-02-01 20:30

    I'm also currently struggling with that.

    I think what you want to do you cannot do any longer once you are on Android Q, because you are not allowed to access the Music directory on Q. You are only allowed to create and access files in directories you created. You did not create the music directory.

    Now every change to the Media has to happen threw the MediaStore. So you insert your Music file beforehand and then get an outputstream from the MediaStore to write to it. All the changes on Q on Media should be done threw the MediaStore hence you informing the MediaStore of changes cannot even occur anymore, because you never directly access the File.

    This has one giant caviat in that all the new things in MediaStore that make that possible do not exist in older versions of Android. So I do currently believe that you will need to implement everything twice, sadly. At least if you want to actively influences where your music is saved to that is.

    Those two MediaStore columns are new in Q and do not exist before Q, witch you'll probably need to use in Q

    • MediaStore.Audio.Media.RELATIVE_PATH with that you can influence the path where it's saved. So I put "Music/MyAppName/MyLibraryName" there and that will end up saving "song.mp3" into "Music/MyAppName/MyLibraryName/song.mp3"
    • MediaStore.Audio.Media.IS_PENDING this you should be setting to 1 while the song is still being written and then afterwards you can update it to 0.

    I've also now started to implement things twice with if checks for Android versions. It's annoying. I don't want to do it. But it seems like that's the only way.

    I'm just gonna put a bit of code here on how I managed inserting music on Android.Q and below. It's not perfect. I have to specify the MIME type for Q, because flacs would now become .flac.mp3 somehow, because it does not quite seem to get that.

    So, anyways this is a part that I have updated already to work with Q and before, it downloads a Music file from a music player on my NAS. The app is written in kotlin, not sure if that's a problem for you.

    override fun execute(library : Library, remoteApi: RemoteApi, ctx: Context) : Boolean {
    
        var success = false
    
        if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
            val values = ContentValues().apply {
                put(MediaStore.Audio.Media.RELATIVE_PATH, library.rootFolderRelativePath)
                put(MediaStore.Audio.Media.DISPLAY_NAME, remoteLibraryEntry.getFilename())
                put(MediaStore.Audio.Media.IS_PENDING, 1)
            }
    
            val collection = MediaStore.Audio.Media
                    .getContentUri(MediaStore.VOLUME_EXTERNAL_PRIMARY)
    
            val uri = ctx.contentResolver.insert(collection, values)
    
            ctx.contentResolver.openOutputStream(uri!!).use {
                success = remoteApi.downloadMusic(remoteLibraryEntry, it!!)
            }
    
            if(success) {
                values.clear()
                val songId = JDrop.mediaHelper.getSongId(uri)
                JDrop.db.music.insert(Music(mediaStoreId = songId, remoteId = remoteLibraryEntry.remoteId, libraryId = library.id))
                values.put(MediaStore.Audio.Media.IS_PENDING, 0)
                ctx.contentResolver.update(uri, values, null, null)
            } else {
                ctx.contentResolver.delete(uri, null, null)
            }
        } else {
    
            val file = File("${library.rootFolderPublicDirectory}/${remoteLibraryEntry.getFilename()}")
    
            if(file.exists()) file.delete()
    
            success = remoteApi.downloadMusic(remoteLibraryEntry, file.outputStream())
    
            if (success) {
                MediaScannerConnection.scanFile(ctx, arrayOf(file.path), arrayOf("audio/*")) { _, uri ->
                    val songId = JDrop.mediaHelper.getSongId(uri)
                    JDrop.db.music.insert(Music(mediaStoreId = songId, remoteId = remoteLibraryEntry.remoteId, libraryId = library.id))
                }
            }
        }
    
        return success
    }
    

    And the MediaStoreHelper Method being this here

        fun getSongId(uri : Uri) : Long {
    
        val cursor = resolver.query(uri, arrayOf(Media._ID), null, null, null)
    
        return if(cursor != null && cursor.moveToNext()) {
            val idIndex = cursor.getColumnIndex(Media._ID)
            val id = cursor.getLong(idIndex)
            cursor.close()
            id
        } else {
            cursor?.close()
            -1
        }
    }
    

    One thing when you do not specify the MIME type it seems to assume mp3 is the MIME type. So .flac files would get saved as name.flac.mp3, because it adds the mp3 file type if there is none and it thinks it's a mp3. It does not add another .mp3 for mp3 files. I don't currently have the MIME type anywhere... so I'm gonna go ahead and do this now, I guess.

    There is also a helpful google IO talk about scoped/shared storage https://youtu.be/3EtBw5s9iRY

    That probably won't answer all of your questions. It sure enough didn't for me. But it was a helpful start to have a rough idea what they even did change to begin with.


    For deleting and updating files its kinda the same on Q if you call delete on a mediastore entry, the file will be deleted. Before, Q you have to manually delete the file also. But if you do that on Q your app will crash. So again you have to check wether or not youre on Q or an older version of android and take appropriate actions.

    0 讨论(0)
提交回复
热议问题