アプリ固有の外部ストレージに画像ファイルを保存する方法について見てみましょう。
2024.1.1
アプリ固有の外部ストレージ
こちらで アプリ固有の内部ストレージにファイルを保存 を試しましたが、画像ファイルなどの大きなファイルは内部ストレージが足りないので外部ストレージに保存します。
アプリ固有の外部ストレージ:
- アプリで使う画像や動画等、大きなファイルを保存できる
- アプリがアンインストールされると削除される
- 使用可能状態か確認が必要
- アプリ自身からのアクセスPermissionは不要
- 他のアプリも適切な権限を持っていればにアクセスでき、読み取られる可能性がある。データ ストレージとファイル ストレージの概要
- アプリがアンインストールされても残った方がいい場合などでは、共有のメディアや他の外部ストレージに保存することも可能です。
getExternalFilesDir
以前は↓を使ってダイレクトのパスを取得できましたが
Environment.getExternalStorageDirectory().getPath()
API29からは使えません。代わりに、
- Context#getExternalFilesDir(String)
これを使ってアプリ固有の外部ストレージに保存するパスを設定します。
1 2 3 |
val context: Context = applicationContext val fileName = "sample_image.jpg" var file = File(context.getExternalFilesDir(Environment.DIRECTORY_PICTURES), fileName) |
「DIRECTORY_PICTURES」で指定しましたが他にも「DIRECTORY_DCIM」などの選択肢はあります。
サンプルコード
保存する画像 sample_image.jpg をassetsに置きます。
画像ファイルをassetsに置いて取り出す方法については以下を参考にしてください。
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 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 |
//package com.example.kotlinprivateexternalstrage import android.content.Context import android.graphics.BitmapFactory import android.os.Bundle import android.os.Environment import android.util.Log import android.view.View import android.widget.Button import android.widget.ImageView 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.File import java.io.FileOutputStream import java.io.IOException import java.nio.file.Files class MainActivity : AppCompatActivity() { private lateinit var textView: TextView private lateinit var imageView: ImageView private lateinit var file: File // asset の画像ファイル名 private val fileName = "sample_image.jpg" 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 // 画像を置く外部ストレージ file = File(context.getExternalFilesDir(Environment.DIRECTORY_PICTURES), fileName) Log.d("log", "path: $file") textView = findViewById(R.id.text_view) val str = "image file: $fileName" textView.text = str imageView = findViewById(R.id.image_view) setUpWriteExternalStorage() } private fun setUpWriteExternalStorage() { val buttonSave: Button = findViewById(R.id.button_save) // 外部ストレージに画像を保存する buttonSave.setOnClickListener { if (isExternalStorageWritable()) { try { resources.assets.open(fileName).use { inputStream -> FileOutputStream(file).use { output -> // バッファーを使って画像を書き出す val bufferSize = 10240 * 4 val buf = ByteArray(bufferSize) var len: Int while (inputStream.read(buf).also { len = it } != -1) { output.write(buf, 0, len) } output.flush() textView.setText(R.string.saved) } } } catch (e: IOException) { throw RuntimeException(e) } } val buttonRead: Button = findViewById(R.id.button_read) buttonRead.setOnClickListener { _: View? -> if (isExternalStorageReadable()) { try { Files.newInputStream(file.toPath()).use { inputStream0 -> val bitmap = BitmapFactory.decodeStream(inputStream0) imageView.setImageBitmap(bitmap) } } catch (e: IOException) { throw java.lang.RuntimeException(e) } } } } } /* Checks if external storage is available for read and write */ private fun isExternalStorageWritable(): Boolean { val state = Environment.getExternalStorageState() return Environment.MEDIA_MOUNTED == state } /* Checks if external storage is available to at least read */ private fun isExternalStorageReadable(): Boolean { val state = Environment.getExternalStorageState() return Environment.MEDIA_MOUNTED == state || Environment.MEDIA_MOUNTED_READ_ONLY == state } } |
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 |
<?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"> <TextView android:id="@+id/text_view" android:textSize="24sp" android:layout_width="wrap_content" android:layout_height="wrap_content" 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.3" /> <ImageView android:id="@+id/image_view" android:scaleType="fitCenter" android:layout_width="match_parent" android:layout_height="wrap_content" android:contentDescription="@string/description" app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintRight_toRightOf="parent" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintTop_toTopOf="parent" app:layout_constraintVertical_bias="0.2" /> <Button android:id="@+id/button_save" android:text="@string/save" android:layout_width="300dp" android:layout_height="wrap_content" 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.7" /> <Button android:id="@+id/button_read" android:text="@string/read" android:layout_width="300dp" android:layout_height="wrap_content" 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.8" /> </androidx.constraintlayout.widget.ConstraintLayout> |
リソースです
strings.xml
1 2 3 4 5 6 7 8 |
<resources> <string name="app_name">KotlinPrivateExternalStrage</string> <string name="description">photo</string> <string name="save">Button Save</string> <string name="read">Button Read</string> <string name="saved">image saved</string> <string name="error">error: FileOutputStream </string> </resources> |
これで画像を保存して読み出してみましょう。
保存された画像はどこにあるか
注)あくまでデバッグ段階でのファイル確認です
画像はどこに保存されているのでしょうか、外部ストレージなので「フォト」を見ても画像はありません。
logcatにパスを出力させていたので、
1 |
path: /storage/emulated/0/Android/data/com.example.kotlinprivateexternalstrage/files/Pictures/sample_image.jpg |
実機で確認するとパスは、
1 |
path: /storage/emulated/0/Android/data/com.example.kotlinprivateexternalstrage/files/Pictures/sample_image.jpg |
となっているのでターミナルで入ってみます。
1 2 3 4 5 |
$ adb shell $ cd /storage/emulated/0/Android/data/com.example.kotlinprivateexternalstrage/files/Pictures/ $ ls sample_image.jpg $ |
sample_image.jpgがあることが確認できます。
また、Device Explorer を使うと保存された画像を確認できました。
関連記事:
References:
ファイルを保存する
ストレージ オプション | Android Developers