diadia

興味があることをやってみる。自分のメモを残しておきます。

Android カメラで撮った画像に関する知見をまとめる

フルサイズの写真を保存するには?

Android カメラアプリは、保存先のファイルを指定すると、フルサイズの写真を保存します。

と書いてあるが、これは( 画像データが入っていない空の )ファイルを渡すと、フルサイズの写真を保存することができる、ということだろう。
だからフルサイズの写真を保存するためには予め空のファイルを作成してからカメラを起動する流れとなる。

フルサイズの写真を保存することを説明した資料

写真を撮影する  |  Android デベロッパー  |  Android Developers

すべてのアプリから撮影した写真にアクセスするためには?

バイスのカメラから撮影した写真をすべてのアプリ(つまりギャラリーとか別のアプリ)からアクセスするためには何が必要になるのか?
それは、デバイスパブリック外部ストレージに保存することである。 これはプログラミング的には、getExternalStoragePublicDirectory()メソッドの返り値がそのディレクトリである。
一方、アプリ内で撮った写真をそのアプリのみでしかアクセスできないようにするためには何が必要になるのか?
それは、getExternalFilesDir()メソッドで返されたディレクトリに写真を保存することである。

このディレクトリの使い分けにはパーミッションの注意点がある。

ケース 必要なパーミッション
getExternalStorageFilesDirectory()メソッドを使う場合(すべてのアプリでアクセスできる場合) READ_EXTERNAL_STORAGE, WRITE_EXTERNAL_STORAGE
getExternalFilesDir()メソッドを使う場合(写真を撮ったアプリのみからアクセスする場合) なし

getExternalStorageFilesDirectory()の引数は何を使うか?

getExternalStorageFilesDirectory()やgetExternalFilesDir()は同じ引数をとる。メソッドによって引数が変わることはない。

getExternalStorageFilesDirectory(Environment.DIRECTORY_PICTURES)
getExternalFilesDir(Environment.DIRECTORY_PICTURES)
Environment.DIRECTORY_PICTURESのドキュメント資料

Environment  |  Android デベロッパー  |  Android Developers

ManifestFile内のproviderタグのauthoritiesの他との関係性

ManifestFileには画像を保存するためにproviderタグを例えば以下のように記載する。

<provider
            android:name="android.support.v4.content.FileProvider"
            android:authorities="com.example.android.fileprovider"
            android:exported="false"
            android:grantUriPermissions="true">
            <meta-data
                android:name="android.support.FILE_PROVIDER_PATHS"
                android:resource="@xml/file_paths"></meta-data>
</provider>

このタグ内で android:authoritiesの属性値は他とどのような関係があるのか?

  1. FileProvider.getUriForFile()の引数となる。
  2. FileProvider.getUriForFileの戻り値Uriオブジェクトの一部を構成する。

FileProvider.getUriForFile()の引数となる、とは以下のようなことだ。メソッドの使いかたを見てほしい。

FileProvider.getUriForFile( contextObj,
                            "com.example.android.fileprovider",
                            fileObj)

第2引数の値"com.example.android.fileprovider"は、provider内で android:authoritiesの属性値である。

FileProvider.getUriForFile()の使い方のドキュメント

FileProvider  |  Android デベロッパー  |  Android Developers

FileProvider.getUriForFileの戻り値Uriオブジェクトの一部を構成する、とは以下を示す。

//FileProvider.getUriForFileの戻り値
content://com.example.android.fileprovider/hoge/hoge.jpg

com.example.android.fileproviderがandroid:authoritiesの属性値である。

一体xml/file_pathsはなんの役割をするファイルなのだろうか?

一連の処理としてManifestFileにProviderタグを追加する。このタグ内にmeta-dataタグではあるが、android:resource="@xml/file_paths"を記述する。この意味がわかっていなかった。もちろんある情報を参照するならfiles_pathsを見てくださいってことなのだろうが、どんな場面で何を参照するのだろうか?

これについてandroidのガイドでは以下のように説明してある。

path コンポーネントは、Environment.DIRECTORY_PICTURES を指定して getExternalFilesDir() を呼び出したときに返されるパスと一致します。com.example.package.name はアプリの実際のパッケージ名で置き換えてください。 また、FileProvider のドキュメントで、external-path 以外に使用可能なパス指定子の詳細を確認してください。

なかなか意味がわからなかったが、つまりこう意味するのだろう。
getExternalFilesDir(Environment.DIRECTORY_PICTURES)の戻り値はxml/file_paths.xmlファイルのexternal-pathの値を返す。 つまりgetExternalFilesDir(Environment.DIRECTORY_PICTURES)の戻り値をxml/file_paths.xmlファイルで決めるてやることがxmlを作成する目的であり、戻り値であるパスが参照する値だってことだ。
また、後文は、FileProvider のドキュメントではgetExternalFilesDir()メソッド以外の返り値を定めることができるってことを意味している。

まとめると以下のようになる。

file_pathsの属性名 属性値を返すためのメソッド
external-path Environment.getExternalStorageDirectory()
files-path Context.getFilesDir()
cache-path getCacheDir()
external-files-path Context.getExternalFilesDir
external-cache-path Context.getExternalCacheDir()
external-madia-path Context.getExternalMediaDir()

全体的にまとめると、、、

端末のカメラを使って撮影する方法は暗黙的インテントによる方法とCameraXを使う方法がある。
外部ストレージに写真を保存するかアプリ内のプライベートな領域に保存するかは、画像Fileを保存する際に作成するFileのパスに関係する。暗黙的インテントかCameraXかは関係ない。 外部ストレージに保存する場合には、画像fileを保存する際にEnvironment#getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES)メソッドで返るfileオブジェクトをパスとする必要がある。
また、このfileオブジェクトは、 "本体/内部ストレージ/PICTURES/"を表している。したがってこの直後にfile名を追加するとファイルがPICTURES以下で四散してしまうので、アプリケーションネームのディレクトリを作り、そこに画像を保存すると良い。
また、外部ストレージに保存したからといってギャラリーのアプリケーションで外部ストレージに保存したファイルを閲覧できることを意味しない。
外部ストレージに保存したファイルをギャラリーに追加するコードを追加しなければならない。 それは以下のコードとなっている。

Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE).also { mediaScanIntent ->
                                val f = File(file.absolutePath)
                                mediaScanIntent.data = Uri.fromFile(f)
                                sendBroadcast(mediaScanIntent)