アプリ開発備忘録

PlayStationMobile、Android、UWPの開発備忘録

Android Qからの画像保存

AndroidQからはファイルを保存してギャラリーで見るだけなら WRITE_EXTERNAL_STORAGE が必要ではなくなりました。

AndroidManifest

Q(29)からは必要でない -> 28までは必要ということで、 AndroidManifest.xml で以下を指定します。

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

https://developer.android.com/guide/topics/manifest/uses-permission-element

実装

AndroidQ未満は省きます。 contentResolver の取得部分は省いてあります。

val collection = MediaStore.Images.Media.getContentUri(MediaStore.VOLUME_EXTERNAL_PRIMARY)
// 書き込みたい画像の情報をInsertして、書き込むべきUriを取得します。
// この時IS_PENDINGを1にし、書き込みが終了したら `0` にします。
// 書き込み途中で他のアプリに参照されたらまずいですからね。  
val writeUri= contentResolver.insert(
        collection,
        ContentValues().also { values ->
            values.put(MediaStore.Images.Media.MIME_TYPE, "image/jpeg")
            values.put(MediaStore.Images.Media.TITLE, fileName)
            values.put(MediaStore.Images.Media.IS_PENDING, 1)
        }
)!!

// 書き込みを行います
contentResolver.openFileDescriptor(writeUri, "w").use {
    it!! // よしなに処理してください
    FileOutputStream(it.fileDescriptor).use { out ->
        URL(url).openStream().copyTo(out)
    }
}

// IS_PENDINGを0にして書き込みを終わらせます
contentResolver.update(
        writeUri,
        ContentValues().also { values ->
            values.put(MediaStore.Images.Media.IS_PENDING, 0)
        }, null, null
)

おまけ

IS_PENDINGを1、IS_PENDINGを0にするというペアが存在するので、これは処理をまとめたくなってしまいます。

@RequiresApi(Build.VERSION_CODES.Q)
inline fun writeImage(fileName: String, block: (Uri) -> Unit) {
    contract {
        callsInPlace(block, InvocationKind.EXACTLY_ONCE)
    }

    val collection = MediaStore.Images.Media.getContentUri(MediaStore.VOLUME_EXTERNAL_PRIMARY)
    val uri = contentResolver.insert(
            collection,
            ContentValues().also { values ->
                values.put(MediaStore.Images.Media.MIME_TYPE, "image/jpeg")
                values.put(MediaStore.Images.Media.TITLE, fileName)
                values.put(MediaStore.Images.Media.IS_PENDING, 1)
            }
    )!!

    block(uri)

    contentResolver.update(
            uri,
            ContentValues().also { values ->
                values.put(MediaStore.Images.Media.IS_PENDING, 0)
            }, null, null
    )
}