Android標準ギャラリーから画像を取得する

Androidアプリ開発で、画像一覧を表示し、ユーザーが選択した画像のデータを取得する方法はいくつかありますが、おそらく最も簡単な方法が、Android標準のギャラリーを使う方法です。この記事では、Android標準ギャラリーがどんなものかを説明し、実際のデータの取得方法も説明します。

標準ギャラリーってどんなの?

まずは標準ギャラリーがどんなものなのか、見た目や機能を確認していきたいと思います。

Pixel 4a (Android 11)で表示した標準ギャラリーは以下のようになります。システム設定が反映され、ダークモードになっています。ギャラリーを開くと、最近一週間の画像がタイル状に表示されます。

最近の画像
ギャラリーを開くと、最近一週間の画像が表示される。

ハンバーガーメニューを開くと、端末内のフォルダやGoogleドライブを選択できます。例として「画像」を選択すると、カメラフォルダや他のアプリのフォルダを参照できます。また、画像はファイル名や日付などで並べ替えることもできます。

メニューを開いたところ
メニューから、画像やダウンロード、Googleドライブなどを選択できる。
画像フォルダ一覧
カメラや他のアプリのフォルダを参照できる。
画像の並べ替え
画像を並べ替えられる。

また、ギャラリー起動時の指定方法によっては、複数の画像をまとめて選択することもできます。

複数の画像を選択
複数のファイルを選択できる。

他のデバイスでも見てみました。やや見た目は異なりますが、機能的にはどれも同じでした。

p10liteの画像ギャラリー
HUAWEI p10 lite (Android 8)のギャラリー。

このようにデバイスやAndroidバージョンによって多少見た目は違いますが、機能としてはほぼ同じで、デバイス内の画像ファイルだけでなく、Googleドライブの画像も取得できるようになっていました。

標準ギャラリーの使いどころ

標準ギャラリーを使うメリットは、なんといっても簡単ということでしょう。画像の並べ替え、フィルタリング、プレビュー、Googleドライブアクセス、複数選択などの高機能なギャラリーが簡単に使えます。

一方のデメリットは、見た目のカスタマイズができない、という点が大きいでしょう。アプリのデザインとギャラリーの見た目が大きく違うと、「別のアプリに切り替わった」ように見えてしまい、シームレスな使い勝手を提供しづらいかもしれません。

以上のメリット・デメリットを考えると、画像の選択機能がアプリの主要な機能であるなら、見た目や機能も含めて、そのアプリに最適化されたギャラリー機能を独自に実装するべきかもしれません。実際、SNSアプリなどで写真を投稿する際に表示されるギャラリーは、各アプリ独自のものが多いです。LINEでは画面の上半分にトークを表示しながら、下半分で写真を選ぶようなUIが実装されています。このように、アプリのUIに最適化された画像選択機能は、標準ギャラリーでは実現できないです。

逆に、画像選択がアプリの主要な機能ではなく、時々しか使わないようであれば、最小限の工数で画像選択機能を実現できる標準ギャラリーは魅力的だと思います。有名アプリで標準ギャラリーを使っている例としては、Slackのプロフィール画像設定に標準ギャラリーが使われていました。プロフィール画像の設定というのはそう頻繁に行うことでもないですし、そんなにカスタマイズが必要とも考えにくいので、標準ギャラリーの採用は妥当なのではないかと感じます。

標準ギャラリーの実装方法

実際のソースコードは次のようになります。

class MainActivity : AppCompatActivity() {
    private val launcher = registerForActivityResult(ActivityResultContracts.OpenDocument()) {
        imageView.setImageURI(it)
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        button.setOnClickListener {
            launcher.launch(arrayOf("image/*"))
        }
    }
}

標準ギャラリーから画像のURIを取得するには、まずregisterForActivityResult()ActivityResultLauncherを取得します。Developer Guideによると、この処理はフィールド初期化時に行うなど、ActivityやFragmentの状態にかかわらず必ず実行されるようにしなければならないとのことです。

This *must* be called unconditionally, as part of initialization path, typically as a field initializer of an Activity or Fragment.

https://developer.android.com/reference/kotlin/androidx/activity/result/ActivityResultCaller#registerforactivityresult

ギャラリー表示中に、呼び出し元のActivityやFragmentが破棄される可能性があるため、Resultを受け取るときに元の状態を復元できるように、このような実装が求められているのだと思います。

registerForActivityResult()の第一引数には、ActivityResultContractを渡します。contractを契約と訳すとなんだか大げさに感じますが、ここでは「取り決め」くらいのニュアンスかなと思っています。このcontractに、ActivityResultContracts.OpenDocument()を指定することによって、画像などファイルを開くためのIntentを呼び出すことができます。

第二引数は、コールバックです。選択したファイルのURIが引数として渡されます。

開くファイルの種類は、実際にギャラリーを開くときにActivityResultLauncher#launch()の引数としてMIMEタイプを指定します。ここでは"image/*"を指定しています。

ちなみに、アプリがアクセスするファイルやディレクトリはユーザーが指定するため、アプリ側に特に権限は必要ないとのことです。

複数の画像を選択するには

上で紹介したように複数の画像を選択できるようにするには、registerForActivityResult()の引数にActivityResultContracts.OpenMultipleDocuments()を指定します。こうすると、コールバック関数の引数にURIのListが返ってきます。

startActivityForResult()は非推奨

Developer GuideではstartActivityForResult()Intentを呼び出すような実装が紹介されていますが、androidX APIではDeprecatedになっておりActivity Result APIの使用が推奨されています

まとめ

標準ギャラリーを使うと、端末内やGoogleドライブの画像を簡単に取得できます。標準ギャラリーの機能や見た目がアプリの要求を満たすのであれば、積極的に使いたいところです。実装にはregisterForActivityResult()を使います。

サンプルはGitHubにも置いてあるのでお試しください。