LoginSignup
8
10

More than 3 years have passed since last update.

Android: SDカード上のファイルへの読み書き

Posted at
  • Android Studio 4.0.1
  • Kotlin 1.4.0
  • Minimum SDK: API 23

AndroidアプリでSDカード上にファイルを書き込み、またそれを読みだす手順をまとめます。サンドボックスの中に作成する場合はもう少し簡単です。共有フォルダなどに保存して、パソコンなどからもアクセスできるようにすることが目的の方法です。

準備

Manifestへ権限追加

※権限の追加は不要なようです。
※権限の確認は実行時に行われます。

AndroidManifest.xml
<!-- 必要ない -->
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />

DocumentFileクラス取り込み

Intent.ACTION_OPEN_DOCUMENT_TREEでのアクセスで使用します。
Intent.ACTION_CREATE_DOCUMENTIntent.ACTION_OPEN_DOCUMENTで一つづつアクセスする場合はいりいません。

build.gradle(Module
dependencies {
    implementation "androidx.documentfile:documentfile:1.0.1"
}

権限の許可を取得

ユーザーに権限の確認を求めます。
※この処理は、必要ないそうです。が、備忘のため、いちおう残しておきます。

MainActivity
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        ActivityCompat.requestPermissions(this, arrayOf(
                Manifest.permission.WRITE_EXTERNAL_STORAGE
        ), 1)
    }

    override fun onRequestPermissionsResult(
            requestCode: Int,
            permissions: Array<out String>,
            grantResults: IntArray
    ) {
        if (requestCode == 1) {
            var allowed = true
            for (result in grantResults) {
                if (result == PackageManager.PERMISSION_DENIED) {
                    allowed = false
                }
            }
            if (!allowed) {
                finish()
            }
        }
    }
}

ファイルを書き込む、読み込む

テキストファイルを出力

出力するフォルダ、ファイル名をユーザーに選択させます。
アクセス権限がないフォルダを選択した場合などは、失敗します。

MainActivity
    fun openWriteFile() {
        val intent = Intent(Intent.ACTION_CREATE_DOCUMENT)
        intent.setType("text/plain")
        intent.putExtra(Intent.EXTRA_TITLE, "sample1.txt")
        startActivityForResult(intent, 101);
    }

    override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
        if (requestCode == 101 && resultCode == RESULT_OK) {
            if (data?.getData() != null) {
                val uri: Uri = data.getData() as Uri
                val out = contentResolver.openOutputStream(uri)
                val writer = OutputStreamWriter(out)
                writer.write("サンプルテキスト\n2行目")
                writer.close()
            }
        }
        super.onActivityResult(requestCode, resultCode, data)
    }

読み込むファイルをユーザーに選択させます。

MainActivity
    fun openReadFile() {
        val intent = Intent(Intent.ACTION_OPEN_DOCUMENT)
        intent.setType("text/plain")
        startActivityForResult(intent, 102);
    }

    override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
        if (requestCode == 102 && resultCode == RESULT_OK) {
            if (data?.getData() != null) {
                val uri: Uri = data.getData() as Uri
                val input = contentResolver.openInputStream(uri)
                val reader = InputStreamReader(input)
                Log.d("nozaki", reader.readText())
            }
        }
        super.onActivityResult(requestCode, resultCode, data)
    }

フォルダ単位でアクセスする

基準となるフォルダをユーザーに選択させます。
そこにサブフォルダを作り、サブフォルダにファイルを作成します。

MainActivity
    fun openFolder() {
        val intent = Intent(Intent.ACTION_OPEN_DOCUMENT_TREE)
        startActivityForResult(intent, 103);
    }

    override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
        if (requestCode == 103 && resultCode == RESULT_OK) {
            if (data?.getData() != null) {
                // ファイルアクセスのためのオブジェクトを取得する
                val treeUri: Uri = data.getData() as Uri
                val document = DocumentFile.fromTreeUri(this, treeUri)

                // 作成するフォルダが既にあれば、そのオブジェクトを取得、なければ作成
                var dir = document?.findFile("SampleFolder")
                if (dir == null) {
                    dir = document?.createDirectory("SampleFolder")
                }
                if (dir == null || !dir.isDirectory()) {
                    return
                }

                // フォルダにファイルを作成する
                val file = dir.createFile("text/plain", "sample2.txt")
                if (file != null) {
                    val out = contentResolver.openOutputStream(file.uri)
                    val writer = OutputStreamWriter(out)
                    writer.write("サブフォルダへのサンプルテキスト\n2行目")
                    writer.close()
                }
            }
        }
        super.onActivityResult(requestCode, resultCode, data)
    }

ユーザーが選択したフォルダのファイル一覧を取得します。

MainActivity

    fun openFolder() {
        val intent = Intent(Intent.ACTION_OPEN_DOCUMENT_TREE)
        startActivityForResult(intent, 104);
    }

    override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
        if (requestCode == 104 && resultCode == RESULT_OK) {
            if (data?.getData() != null) {
                // ファイルアクセスのためのオブジェクトを取得する
                val treeUri: Uri = data.getData() as Uri
                val document = DocumentFile.fromTreeUri(this, treeUri)
                if (document == null) {
                    return
                }

                // フォルダのファイル一覧をログに出力する
                for (file in document.listFiles()) {
                    Log.d("nozaki", file.name)
                }
            }
        }
        super.onActivityResult(requestCode, resultCode, data)
    }

アクセス権限の保存

一度ユーザーにフォルダを選択させ、その権限を保存しておくと、次回からはユーザーの確認を待つことなくそのフォルダにアクセスできるようになります。

権限と対象フォルダのUriを保存

MainActivity
getContentResolver().takePersistableUriPermission(treeUri, Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_GRANT_WRITE_URI_PERMISSION)
val pref = getSharedPreferences("app", Context.MODE_PRIVATE)
pref.edit().putString("treeUri", treeUri.toString()).commit()

保存したフォルダからファイル一覧を取得

ユーザーへの確認は行われません

MainActivity
    fun readFolder() {
        val pref = getSharedPreferences("app", Context.MODE_PRIVATE)
        val treeUri = Uri.parse(pref.getString("treeUri", null))
        val document = DocumentFile.fromTreeUri(this, treeUri)
        if (document == null) {
            return
        }
        for (file in document.listFiles()) {
            Log.d("nozaki", file.name)
        }
    }
8
10
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
8
10