【Android】SDカードに保存する


この記事は

AndroidでSDカードに何かを保存したい


使うものは


使わないものは


  • getExternalDirectoryとか


まずコード


SDカードへのアクセス権限をユーザに求める

    private fun requestSecondaryStorageAccess() {

val sm = getSystemService(STORAGE_SERVICE) as StorageManager
sm.storageVolumes.find { storageVolume -> storageVolume.isRemovable }.let {
val intent = it.createAccessIntent(null)
startActivityForResult(intent, REQUEST_SDCARD_ACCESS)
}
}


権限付与されたURIをごにょごにょする

    override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {

super.onActivityResult(requestCode, resultCode, data)

if (requestCode == REQUEST_SDCARD_ACCESS && resultCode == RESULT_OK) {
data?.data?.let {
contentResolver.takePersistableUriPermission(
it,
Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_GRANT_WRITE_URI_PERMISSION
)

val outUri = DocumentFile.fromTreeUri(this, it)?.createFile("text/plain", "sample")?.uri
outUri?.let{
contentResolver.openOutputStream(it)?.use{
it.write("hoge".toByteArray())
}
}
}
}



細かく

    val sm = getSystemService(STORAGE_SERVICE) as StorageManager

sm.storageVolumes.find { storageVolume -> storageVolume.isRemovable }.let {
val intent = it.createAccessIntent(null)
startActivityForResult(intent, REQUEST_SDCARD_ACCESS)
}


  • StorageManagerのgetStorageVolumes()で、利用可能な全てのStorageVolumeを取得し、isRemovable()で取り外し可能であることをチェックする(=SDカードである)

  • SDカードであるStorageVolumeに対してcreateAccessIntent(null)でディレクトリアクセス権を要求するIntentを生成し、発行する



    data?.data?.let {

contentResolver.takePersistableUriPermission(
it,
Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_GRANT_WRITE_URI_PERMISSION
)


  • ユーザがアクセス権限を与えたディレクトリのUriがdataに格納される

  • このUriはそのままだと揮発するが、ContentResolverのtakePersistableUriPermissionでシステムにUriを覚えさせておくことが可能


    • 端末を再起動するまで有効。一度覚えると再度ユーザに権限許可を求めずRESULT_OKが返される。



    val outUri = DocumentFile.fromTreeUri(this, it)?.createFile("text/plain", "sample")?.uri

outUri?.let{
contentResolver.openOutputStream(it)?.use{
it.write("hoge".toByteArray())
}
}


  • DocumentFileのfromTreeUriで「アクセス許可されたUri」からDocumentFileオブジェクトを生成


    • この場合、SDカードのルートディレクトリを指す



  • DocumentFileのcreateFileでmimeTypeとfilenameを指定してDocumentFileを生成し、Uriを取得

  • 新ファイルのoutputStreamを生成し、hogeと書き込んだ


Android Q

createAccessIntentがDeprecatedとなった。余計なことしやがって!


ディレクトリピッカーを開いてユーザに権限を付けてもらう

    val intent = Intent(Intent.ACTION_OPEN_DOCUMENT_TREE)

startActivityForResult(intent, REQUEST_OPEN_DOCUMENT)


  • SDカードの権限を付けますよ~という方法が封じられ、ユーザがディレクトリを指定することが必要になった。

  • 因みにAndroidQでは見送られたが、来年(=2020?)にはSAFに完全移行しないとファイル周りは使えなくなるらしい。どこかに書いてあったけどソースは忘れました。ごめんなさい。