アプリ固有の外部ストレージに画像ファイルを保存する方法について見てみましょう。
2024.1.1
アプリ固有の外部ストレージ
こちらで アプリ固有の内部ストレージにファイルを保存 を試しましたが、画像ファイルなどの大きなファイルは内部ストレージが足りないので外部ストレージに保存します。
![[Android] アプリ固有の外部ストレージに画像ファイルを保存する 1x1.trans - [Android] アプリ固有の外部ストレージに画像ファイルを保存する](https://akira-watson.com/wp-content/themes/simplicity2/images/1x1.trans.gif)
アプリ固有の外部ストレージ:
- アプリで使う画像や動画等、大きなファイルを保存できる
 - アプリがアンインストールされると削除される
 - 使用可能状態か確認が必要
 - アプリ自身からのアクセスPermissionは不要
 - 他のアプリも適切な権限を持っていればにアクセスでき、自分のコントロールの及ばない所で読み取られる可能性がある。データ ストレージとファイル ストレージの概要
 - アプリがアンインストールされても残った方がいい場合などでは、共有のメディアや他の外部ストレージに保存することも可能です。
 
getExternalFilesDir
以前は↓を使ってダイレクトのパスを取得できましたが
Environment.getExternalStorageDirectory().getPath()
API29からは使えません。代わりに、
- Context#getExternalFilesDir(String)
 
これを使ってアプリ固有の外部ストレージに保存するパスを設定します。
 
| 
					 1 2 3 4  | 
						Context context = getApplicationContext(); String fileName = "sample_image.jpg"; File file = new File(context.getExternalFilesDir(Environment.DIRECTORY_PICTURES), fileName);  | 
					
「DIRECTORY_PICTURES」で指定しましたが他にも「DIRECTORY_DCIM」などの選択肢はあります。
![[Android] アプリ固有の外部ストレージに画像ファイルを保存する 1x1.trans - [Android] アプリ固有の外部ストレージに画像ファイルを保存する](https://akira-watson.com/wp-content/themes/simplicity2/images/1x1.trans.gif)
サンプルコード
保存する画像 sample_image.jpg をassetsに置きます。
画像ファイルをassetsに置いて取り出す方法については以下を参考にしてください。
 
MainActivity.java
| 
					 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 110 111 112 113 114 115 116  | 
						//package com.example.testprivateexternalstorage; import android.os.Bundle; import androidx.activity.EdgeToEdge; import androidx.appcompat.app.AppCompatActivity; import androidx.core.graphics.Insets; import androidx.core.view.ViewCompat; import androidx.core.view.WindowInsetsCompat; import android.content.Context; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.os.Environment; import android.util.Log; import android.widget.Button; import android.widget.ImageView; import android.widget.TextView; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.nio.file.Files; public class MainActivity extends AppCompatActivity {     private TextView textView;     private ImageView imageView;     // asset の画像ファイル名     private final String fileName = "sample_image.jpg";     private File file;     @Override     protected void onCreate(Bundle savedInstanceState) {         super.onCreate(savedInstanceState);         EdgeToEdge.enable(this);         setContentView(R.layout.activity_main);         ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main), (v, insets) -> {             Insets systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars());             v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom);             return insets;         });         Context context = getApplicationContext();         // 画像を置く外部ストレージ         file = new File(context.getExternalFilesDir(Environment.DIRECTORY_PICTURES), fileName);         Log.d("log","path: " + file);         textView = findViewById(R.id.text_view);         String str = "image file: "+fileName;         textView.setText(str);         imageView = findViewById(R.id.image_view);         setUpWriteExternalStorage();     }     private void setUpWriteExternalStorage(){         Button buttonSave = findViewById(R.id.button_save);         // 外部ストレージに画像を保存する         buttonSave.setOnClickListener( v -> {             if(isExternalStorageWritable()){                 try(// assetsから画像ファイルを取り出し                     InputStream inputStream =                             getResources().getAssets().open(fileName);                     // 外部ストレージに画像を保存                     FileOutputStream output =                             new FileOutputStream(file)) {                     // バッファーを使って画像を書き出す                     int DEFAULT_BUFFER_SIZE = 10240 * 4;                     byte[] buf = new byte[DEFAULT_BUFFER_SIZE];                     int len;                     while((len=inputStream.read(buf))!=-1){                         output.write(buf,0, len);                     }                     output.flush();                     textView.setText(R.string.saved);                 } catch (IOException e) {                     throw new RuntimeException(e);                 }             }         });         Button buttonRead = findViewById(R.id.button_read);         buttonRead.setOnClickListener( v -> {             if(isExternalStorageReadable()){                 try(InputStream inputStream0 =                             Files.newInputStream(file.toPath())) {                     Bitmap bitmap = BitmapFactory.decodeStream(inputStream0);                     imageView.setImageBitmap(bitmap);                 } catch (IOException e) {                     throw new RuntimeException(e);                }             }         });     }     /* Checks if external storage is available for read and write */     public boolean isExternalStorageWritable() {         String state = Environment.getExternalStorageState();         return (Environment.MEDIA_MOUNTED.equals(state));     }     /* Checks if external storage is available to at least read */     public boolean isExternalStorageReadable() {         String state = Environment.getExternalStorageState();         return (Environment.MEDIA_MOUNTED.equals(state) ||                 Environment.MEDIA_MOUNTED_READ_ONLY.equals(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  | 
						<resources>     <string name="app_name">TestPrivateExternalStorage</string>     <string name="description">photo</string>     <string name="save">Button Save</string>     <string name="read">Button Read</string>     <string name="saved">image saved</string> </resources>  | 
					
 
これで画像を保存して読み出してみましょう。
 
保存された画像はどこにあるか
注)あくまでデバッグ段階でのファイル確認です
画像はどこに保存されているのでしょうか、アプリ固有の外部ストレージなので「フォト」を見ても画像はありません。
logcatにパスを出力させていたので、
| 
					 1  | 
						path: /storage/emulated/0/Android/data/com.example.testprivateexternalstorage/files/Pictures/sample_image.jpg  | 
					
実機で確認するとパスは、
| 
					 1  | 
						path: /storage/emulated/0/Android/data/com.example.testprivateexternalstorage/files/Pictures/sample_image.jpg  | 
					
となっているのでターミナルで入ってみます。
![[Android] アプリ固有の外部ストレージに画像ファイルを保存する 1x1.trans - [Android] アプリ固有の外部ストレージに画像ファイルを保存する](https://akira-watson.com/wp-content/themes/simplicity2/images/1x1.trans.gif)
sample_image.jpgがあることが確認できます。
Device Explorerで見ると、画像を確認できました
![[Android] アプリ固有の外部ストレージに画像ファイルを保存する 1x1.trans - [Android] アプリ固有の外部ストレージに画像ファイルを保存する](https://akira-watson.com/wp-content/themes/simplicity2/images/1x1.trans.gif)
 
 
関連記事:
 
References:
ファイルを保存する
ストレージ オプション | Android Developers