ファイルを保存したい場合、アプリのプライベートなローカルストレージに保存したり読み出したりできます。
2024.1.1
アプリ固有の内部ストレージ領域
ストレージには内部ストレージ(Internal Storage)と外部ストレージ(External Storage)があります。アプリには固有のストレージ領域があり、ここに起動時に必要な設定データなどを保存させることができます。
- 内部ストレージ:
- 常に使用できる
- ここに保存されたファイルは、自分のアプリからのみアクセスできる
- アプリがアンインストールされると削除される
- ユーザーからも他のアプリからも、自分のファイルにアクセスできないようにしたい場合に適する
- アクセスPermissionは不要
- ストレージ領域は限られているので大きなファイルは保存できない
- アプリ固有の外部ストレージ:
アプリがアンインストールされても残った方がいい場合などでは、共有のメディアや他の外部ストレージに保存することも可能です。
getFilesDir()
を使ってアプリ固有の内部ストレージにアクセスできます。
1 |
var file = File(context.getFilesDir(), fileName) |
getFilesDir()はパスを返します。
/data/data/[package_name]/files/
adb shell コマンドではセキュリティーでブロックされて見えませんが、run-as を使えばデバック状態であれば見ることはできます。
サンプルコード
EditText で入力した文字列をTestFile.txt というファイルに入れて保存し、そのファイルから読み出す例です
MainActivity.kt
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 |
//package com.example.kotlinprivateinternalstorage import android.content.Context import android.os.Bundle import android.widget.Button import android.widget.EditText import android.widget.TextView import androidx.activity.enableEdgeToEdge import androidx.appcompat.app.AppCompatActivity import androidx.core.view.ViewCompat import androidx.core.view.WindowInsetsCompat import java.io.BufferedReader import java.io.File import java.io.FileReader import java.io.FileWriter import java.io.IOException class MainActivity : AppCompatActivity() { private lateinit var file: File override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) enableEdgeToEdge() setContentView(R.layout.activity_main) ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main)) { v, insets -> val systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars()) v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom) insets } val context: Context = applicationContext val fileName = "TestFile.txt" file = File(context.filesDir, fileName) val textView : TextView = findViewById(R.id.text_view) val editText : EditText = findViewById(R.id.edit_text) val buttonSave : Button = findViewById(R.id.button_save) buttonSave.setOnClickListener { // エディットテキストのテキストを取得 val text = editText.text.toString() saveFile(text) if (text.isEmpty()) textView.setText(R.string.no_text) else textView.setText(R.string.saved) } val buttonRead: Button = findViewById(R.id.button_read) buttonRead.setOnClickListener { val str = readFile() if (str != null) { textView.text = str } else { textView.setText(R.string.read_error) } } } // ファイルを保存 private fun saveFile(str: String?) { try { FileWriter(file).use { writer -> writer.write(str) } } catch (e: IOException) { throw RuntimeException(e) } } // ファイルを読み出し private fun readFile(): String? { var text: String? = null try { BufferedReader(FileReader(file)).use { br -> text = br.readLine() } } catch (e: IOException) { throw RuntimeException(e) } return text } } |
activity_main.xml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 |
<?xml version="1.0" encoding="utf-8"?> <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:id="@+id/main" android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".MainActivity"> <EditText android:id="@+id/edit_text" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_margin="50dp" android:autofillHints="@string/hint" android:background="#fff" android:hint="@string/hint" android:inputType="text" android:textSize="30sp" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintRight_toRightOf="parent" app:layout_constraintTop_toTopOf="parent" app:layout_constraintVertical_bias="0.05" /> <Button android:id="@+id/button_save" android:layout_width="0dp" android:layout_height="wrap_content" android:text="@string/save_file" android:textSize="20sp" android:layout_marginStart="20dp" android:layout_marginTop="20dp" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintRight_toLeftOf="@+id/button_read" app:layout_constraintTop_toBottomOf="@+id/edit_text" app:layout_constraintVertical_bias="0.0" /> <Button android:id="@+id/button_read" android:layout_width="0dp" android:layout_height="wrap_content" android:text="@string/read_file" android:textSize="20sp" android:layout_marginEnd="20dp" android:layout_marginTop="20dp" app:layout_constraintLeft_toRightOf="@+id/button_save" app:layout_constraintRight_toRightOf="parent" app:layout_constraintTop_toBottomOf="@+id/edit_text" /> <TextView android:id="@+id/text_view" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/app_name" android:textColor="#000" android:textSize="30sp" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintRight_toRightOf="parent" app:layout_constraintTop_toTopOf="parent" app:layout_constraintVertical_bias="0.4" /> </androidx.constraintlayout.widget.ConstraintLayout> |
リソースファイル
strings.xml
1 2 3 4 5 6 7 8 9 |
<resources> <string name="app_name">KotlinPrivateInternalStorage</string> <string name="no_text">"No text !"</string> <string name="saved">Saved</string> <string name="read_error">Read file error !</string> <string name="hint">文字を入力</string> <string name="save_file">Save file</string> <string name="read_file">Read file</string> </resources> |
実際の動きをこのような感じです
実際に保存したファイルを確認するときは
Device Explorer
を使うと確認できます。昔はADMともDevice File Explorerとも呼ばれていました、もっと昔にはDDMSと呼ばれていましたね…
Android Device Monitor, DDMS で確認
注)あくまでデバッグ段階でのファイル確認です
Android Studio にアイコンが見当たらない場合は
「View」「Tool Windows」「Device Explorer」を選択
/data/data/[package_name]/files/以下に TestFile.txt を作るようにコーディングしたつもりですが、ありました。クリックすると中身も見えて保存した内容が確認できました。
実機では
run-as [package name] をコマンドで打ち込んで確認できます
adb, run-as でローカルストレージ内をのぞく
openfileOutput , openFileInput
File API の代わりに、openFileOutput() を呼び出して、filesDir ディレクトリ内のファイルへの書き込みを行う FileOutputStream を取得することもできます。
ストリームを使用してファイルを保存する
1 |
val fileOutputstream = openFileOutput("test.txt", MODE_PRIVATE) |
第一引数はファイル名のみの指定です。
第二引数のモードは
- MODE_APPEND
- 追加で書き込み
- MODE_PRIVATE
- このアプリのみアクセス許可
書き込みは write()メソッドを使います。
1 |
fileOutputstream.write(str.getBytes()) |
strは書き込む文字列です。
読み出しでは
FileInputStream のインスタンスを openFileInput()メソッドで取得します。
1 |
val fileInputStream = openFileInput("test.txt") |
InputStreamReaderでバイトを読み込み、指定された文字にデコードし、それをBufferedReaderに入れて、行毎に読み出します。変換効率を上げるため、BufferedReaderの内部にInputStreamReaderをラップするのがいいようです。
1 2 3 4 5 6 7 |
val reader = BufferedReader(InputStreamReader(fileInputStream, StandardCharsets.UTF_8)) var lineBuffer: String while (reader.readLine().also { lineBuffer = it } != null) { var text = lineBuffer } |
サンプルコード2
同様にEditTextから取得した文字列をファイルに入れて保存、読み出しをやってみます。
MainActivity.kt
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 |
//package com.example.kotlinprivateinternalstorage import android.content.Context import android.os.Bundle import android.widget.Button import android.widget.EditText import android.widget.TextView import androidx.activity.enableEdgeToEdge import androidx.appcompat.app.AppCompatActivity import androidx.core.view.ViewCompat import androidx.core.view.WindowInsetsCompat import java.io.BufferedReader import java.io.IOException import java.io.InputStreamReader import java.nio.charset.StandardCharsets class MainActivity : AppCompatActivity() { private val fileName = "TestFileReadWrite.txt" override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) enableEdgeToEdge() setContentView(R.layout.activity_main) ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main)) { v, insets -> val systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars()) v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom) insets } val textView = findViewById<TextView>(R.id.text_view) val editText = findViewById<EditText>(R.id.edit_text) val buttonSave = findViewById<Button>(R.id.button_save) buttonSave.setOnClickListener { // エディットテキストのテキストを取得 val text = editText.text.toString() saveFile(text) if (text.isEmpty()) { textView.setText(R.string.no_text) } else { textView.setText(R.string.saved) } } val buttonRead: Button = findViewById(R.id.button_read) buttonRead.setOnClickListener { val str = readFile() if (str != null) { textView.text = str } else { textView.setText(R.string.read_error) } } } // ファイルを保存 private fun saveFile(str: String) { try { openFileOutput( fileName, Context.MODE_PRIVATE).use { fileOutputstream -> fileOutputstream.write(str.toByteArray()) } } catch (e: IOException) { throw RuntimeException(e) } } // ファイルを読み出し private fun readFile(): String? { var text: String? = null try { openFileInput(fileName).use { fileInputStream -> BufferedReader( InputStreamReader(fileInputStream, StandardCharsets.UTF_8) ).use { reader -> var lineBuffer: String? while (reader.readLine().also { lineBuffer = it } != null) { text = lineBuffer } } } } catch (e: IOException) { throw RuntimeException(e) } return text } } |
レイアウトなどの他のリソースは前のサンプルコードと同じです。
References:
Context
View On-Device Files with Device File Explorer | Android Studio