写真を外部ストレージ領域に保存
ある程度大きい画像で表示したい場合は画像をファイルとしてアプリ固有の外部ストレージに保存し、その画像を表示させます。
外部ストレージはSDカードや端末内部のメモリ領域を指しますが今回は、アプリ固有のプライベートなストレージを使います。フォトやギャラリーなどのユーザーが撮影した写真が保存される領域とは異なります。
また、アプリ固有の外部ストレージ領域を使いますので「WRITE_EXTERNAL_STORAGE」のPermissionは必要ありません。
API24からはUriをfile://xxx という使い方ができなくなったためFileProviderを使います。
https://inthecheesefactory.com/blog/how-to-share-access-to-file-with-fileprovider-on-android-nougat
FileProvider:
アプリ間でのデータ共有を可能にしたContentProviderがありますが、それを簡単にしたサブクラスです。
このアプリとフォトアプリのデータ共有です(デフォルトのカメラアプリが撮影画像をフォトアプリで引き出せるメモリ領域に保存している)
FileProvider tag を AndroidManifestに追加します。
AndroidManifest.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 |
<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" ... <application android:allowBackup="true" android:icon="@mipmap/ic_launcher" android:label="@string/app_name" android:roundIcon="@mipmap/ic_launcher_round" android:supportsRtl="true" android:theme="@style/AppTheme"> <provider android:name="androidx.core.content.FileProvider" android:authorities="${applicationId}.fileprovider" android:exported="false" android:grantUriPermissions="true"> <meta-data android:name="android.support.FILE_PROVIDER_PATHS" android:resource="@xml/provider_path"/> </provider> ... </application> </manifest> |
android:authoritiesはアプリのパッケージ名+fileprovider
android:resource=”@xml/provider_path”
ではファイルを渡すための共有ディレクトリーを設定します。
xmlフォルダーを作成してその中に共有するファイルを入れます。例えば、
res¥xml¥provider_path.xml ファイルを作って以下のように記述します。
provider_path.xml
1 2 3 4 |
<?xml version="1.0" encoding="utf-8"?> <paths xmlns:android="http://schemas.android.com/apk/res/android"> <external-path name="external_files" path="."/> </paths> |
あとは、Uri.fromFileの変更です。getUriForFile()からUriを取得します。
getUriForFile(Context context, String authority, File file)
authority はAndroidManifestで設定した
android:authorities=”${applicationId}.fileprovider”
になります。
1 2 3 4 5 6 7 8 9 |
cameraFile = new File(filePath); Uri uri = Uri.fromFile(cameraFile); を以下のように変更 Uri uri = FileProvider.getUriForFile( MainActivity.this, getApplicationContext().getPackageName() + ".fileprovider", cameraFile); |
サンプルコード
これでまとめると
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 |
//package your.package.name; package com.example.testintentcamera; import androidx.activity.result.ActivityResultLauncher; import androidx.activity.result.contract.ActivityResultContracts; import androidx.core.content.FileProvider; import androidx.appcompat.app.AppCompatActivity; import android.app.Activity; import android.content.Context; import android.content.Intent; import android.net.Uri; import android.os.Bundle; import android.os.Environment; import android.provider.MediaStore; import android.util.Log; import android.widget.Button; import android.widget.ImageView; import java.io.File; import java.text.SimpleDateFormat; import java.util.Date; import java.util.Locale; public class MainActivity extends AppCompatActivity { private ImageView imageView; private Uri cameraUri; ActivityResultLauncher resultLauncher = registerForActivityResult( new ActivityResultContracts.StartActivityForResult(), result -> { if (result.getResultCode() == Activity.RESULT_OK) { Intent data = result.getData(); if(data != null) { if(cameraUri != null && isExternalStorageReadable()){ imageView.setImageURI(cameraUri); } else{ Log.d("debug","cameraUri == null"); } } } }); @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); Log.d("debug","onCreate()"); setContentView(R.layout.activity_main); imageView = findViewById(R.id.image_view); Button cameraButton = findViewById(R.id.camera_button); cameraButton.setOnClickListener( v -> { if(isExternalStorageWritable()){ cameraIntent(); } }); } private void cameraIntent(){ Context context = getApplicationContext(); // 保存先のフォルダー File cFolder = context.getExternalFilesDir(Environment.DIRECTORY_DCIM); Log.d("log","path: " + cFolder); String fileDate = new SimpleDateFormat( "ddHHmmss", Locale.US).format(new Date()); // ファイル名 String fileName = String.format("CameraIntent_%s.jpg", fileDate); File cameraFile = new File(cFolder, fileName); cameraUri = FileProvider.getUriForFile( MainActivity.this, context.getPackageName() + ".fileprovider", cameraFile); Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE); intent.putExtra(MediaStore.EXTRA_OUTPUT, cameraUri); resultLauncher.launch(intent); Log.d("debug","startActivityForResult()"); } /* 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)); } } |
provide_path.xml
1 2 3 4 |
<?xml version="1.0" encoding="utf-8"?> <paths xmlns:android="http://schemas.android.com/apk/res/android"> <external-path name="external_files" path="."/> </paths> |
AndroidManifest.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 |
<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" ... <queries> <intent> <action android:name="android.media.action.IMAGE_CAPTURE" /> </intent> </queries> <application android:allowBackup="true" android:icon="@mipmap/ic_launcher" android:label="@string/app_name" android:roundIcon="@mipmap/ic_launcher_round" android:supportsRtl="true" android:theme="@style/Theme.TestIntentCameraStorage"> <provider android:name="androidx.core.content.FileProvider" android:authorities="${applicationId}.fileprovider" android:exported="false" android:grantUriPermissions="true"> <meta-data android:name="android.support.FILE_PROVIDER_PATHS" android:resource="@xml/provider_path"/> </provider> <activity android:name=".MainActivity"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> </application> </manifest> |
レイアウト、リソースは前と同じです。
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 |
<?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" tools:context=".MainActivity"> <Button android:id="@+id/camera_button" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_margin="20dp" android:text="@string/button" app:layout_constraintVertical_bias="0.2" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintRight_toRightOf="parent" app:layout_constraintTop_toTopOf="parent" /> <ImageView android:id="@+id/image_view" android:scaleType="fitCenter" android:layout_width="300dp" android:layout_height="300dp" android:contentDescription="@string/description" app:layout_constraintVertical_bias="0.7" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintRight_toRightOf="parent" app:layout_constraintTop_toTopOf="parent" /> </androidx.constraintlayout.widget.ConstraintLayout> |
strings.xml
1 2 3 4 5 |
<resources> <string name="app_name">Your App Name</string> <string name="button">Button</string> <string name="description">camera picture</string> </resources> |
References:
Camera API | Android Developers
実行時のパーミッション リクエスト | Android Developers
FileProvider | Android Developers
Setting Up File Sharing | Android Developers
Privacy changes in Android 10 | Android Developers
一般的なインテント | Android Developers
Android 11 でのパッケージへのアクセス
動作の変更点: Android 11 をターゲットとするアプリ