アプリ固有の外部ストレージに画像ファイルを保存する方法について見てみましょう。
2021.2.1
アプリ固有の外部ストレージ
こちらで アプリ固有の内部ストレージにファイルを保存 を試しましたが、画像ファイルなどの大きなファイルは内部ストレージが足りないので外部ストレージに保存します。
![[Android & Kotlin] アプリ固有の外部ストレージに画像ファイルを保存 1x1.trans - [Android & Kotlin] アプリ固有の外部ストレージに画像ファイルを保存](https://akira-watson.com/wp-content/themes/simplicity2/images/1x1.trans.gif)
アプリ固有の外部ストレージ:
- アプリで使う画像や動画等、大きなファイルを保存できる
- アプリがアンインストールされると削除される
- 使用可能状態か確認が必要
- アプリ自身からのアクセス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 |
//package your.package.name import android.content.Context import android.graphics.BitmapFactory import android.os.Bundle import android.os.Environment import android.util.Log import android.widget.Button import android.widget.ImageView import android.widget.TextView import androidx.appcompat.app.AppCompatActivity import java.io.File import java.io.FileInputStream import java.io.FileOutputStream import java.io.IOException 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) setContentView(R.layout.activity_main) 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 = findViewById<Button>(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) { e.printStackTrace() textView.setText(R.string.error) } } } val buttonRead: Button = findViewById(R.id.button_read) buttonRead.setOnClickListener { if (isExternalStorageReadable()) { try { FileInputStream(file).use { inputStream0 -> val bitmap = BitmapFactory.decodeStream(inputStream0) imageView.setImageBitmap(bitmap) } } catch (e: IOException) { e.printStackTrace() } } } } /* 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:layout_width="match_parent" android:layout_height="match_parent" android:background="#fea" 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">Your app name</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.testsavepicture/files/Pictures/sample_image.jpg |
実機で確認するとパスは、
1 |
/sdcard/Android/data/com.example.testprivateexternalstorage/files/Pictures/sample_image.jpg |
となっているのでターミナルで入ってみます。
1 2 3 4 5 |
$ adb shell $ cd /sdcard/Android/data/com.example.testprivateexternalstorage/files/Pictures $ ls sample_image.jpg $ |
sample_image.jpgがあることが確認できます。
また、Device File Explorer を使うと保存された画像を確認できました。
(API 31からは見えなくなったようですが存在は分かります)
関連記事:
References:
ファイルを保存する
ストレージ オプション | Android Developers