Android10以降の外部ファイルへのデータ保存方法の修正対応について

Android Robot Logo

概要

前回から他にも対応が必要な箇所が発生したので第二弾です。
Android 10 (API レベル29) 以上になると、外部ストレージへのデータ保存周りが厳しくなり、それまで利用できた getExternalStorageDirectory() がdeprecated になっています。
そしてAndroid 11(API レベル 30)になるとさらに厳しくなり、アプリで外部ストレージ上にアプリ固有のディレクトリをそもそも作成できなくなります。
自身の古いプロジェクトではアプリ内で写真を撮影・保存する機能があるのですが、 この撮った写真を保存する部分で deprecated になった getExternalStorageDirectory() を利用していました。
今回はこれを修正し、外部ストレージに保存させるように修正しました。
対応時のAndroid Studioバージョン : 4.0.1

外部ストレージへの画像保存方法の変更

自分のプロジェクトでは撮影した写真を保存する機能がありましたが、targetSDK 29 に変えたら保存ができなくなりました。
調べると、どうやらストレージへの保存方法で大きな変更があったようです。

developer.android.com

外部ストレージへ保存する処理が厳しくなり、これまで利用できたAPIがtargetSDK 29の時点でdeprecatedになりました。(対象範囲別ストレージというものに変更された)
とはいえ、targetSDK 28だったらAndroidManifestにてrequestLegacyExternalStorage="true"にすると今まで通りの機能が使えたのですが、
2021年以降のOSアップデートはでこの方法も使えなくなるみたいなので、一時的な延命にしか過ぎません。
よって今回はAndroidQ以上でも対象範囲別ストレージが動作するようにコードを修正する必要があります。

MediaStore APIを利用する

公式の「データ ストレージとファイル ストレージの概要」によると、外部ストレージへの画像保存は MediaStore API を利用して保存するのが推奨となっていますので、このAPIを利用することにします。
このAPIを利用することで、これまでに必要だった書き込み権限 WRITE_EXTERNAL_STORAGE が不要になります。
とはいえ、targetSDKVerison 28以下の場合はまだ必要なのですが、
29以上では付与する必要がないので、以下のように AndroidManifest.xml に記述します。

<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"
                 android:maxSdkVersion="28" />

今回は MediaStore.Image APIを利用するので保存場所も Pictures フォルダに限定されます。
とはいえ、そのPicturesフォルダ直下に階層構造を作成・保存することは問題なく可能です。(例: Pictures/Hogehofe/Fugafuga.png
この MediaStore は ContentResolver というAPIとセットで利用する必要があります。
ContentResolver とは何ぞや?という方は以下の記事が参考になると思いますが、先に記事のまとめを引用しますと、

ContentResolverは、QueryされたベースURI以下に存在するファイルのURIや名前などの情報まとめたデータテーブルへのアクセスを提供する。

ということで、ローカルDBへアクセスできるAPIみたいなものですね。
なので MediaStore へアクセスするには ContentResolver 経由で操作する必要があるという具合です。

blog.livedoor.jp

コード

下記コードの bmp には画像データが既に入っている前提です。

参考サイト:

Android ロボットは、Google が作成および提供している作品から複製または変更したものであり、クリエイティブ・コモンズ表示 3.0 ライセンスに記載された条件に従って使用しています