【Android】Intentで渡すためのUriを作成する

Intent Filter編第二回です。今決めました。

前回はIntent Filterの設定方法と、暗黙的Intentを飛ばすときのお作法をやったので、実際にデータを送受信するUriの扱い方をまとめます。

URIのしくみ

URIってそもそも何?とかそんな説明は面倒なんでしません。

URIRFC 2396で定められた仕様によってグルーピングを行うことが出来ます。

例えば「http://outofmem.hatenablog.com/entry/2014/05/23/030837」と言うURIであれば、「http」はScheme、「outofmem.tumblr.com」はAuthority、「/post/86576135159/android-intent-intent-filter」はPathです。もっと細かな、あるいはもっと大きな粒度でグルーピングすることも出来ます。

Uriクラスからデータを取得するにはこのグルーピングの単位をちゃんと理解してないといけません。いやまぁ正規表現書けば取れるし、正規表現を書くのは結構好きなんですが、同じぐらい好きな言葉があるんですよ。「『この問題を解決するには正規表現を使えばいい。』こうして問題は二つになった。」

java.net.URIandroid.net.Uri

android.net.Uriとは別にjava.net.URIがあります。

どちらも似たようなメソッドを持っていますが、当然のことながらandroidで使う分にはandroid.net.Uriの方が便利です。(って言うか、SDKAPIjava.net.URIを受け取ってくれるシグネチャがそもそもあるのかって言う…。)

ただ、java.net.URIのドキュメントではURIの仕様自体にも触れており、android.net.Uriで守らなければならないルールも当然含まれているので一読しておくに越したことはないです。日本語だし。

また、java.net.URIに対して実例を交えながら解説している資料もあり、これまたandroid.net.Uriにそのまま適用できる解説が含まれているのでそれも読んでおくといいと思います。

URIコンポーネント

さて、java.net.URIの資料によると、URIは以下の9つのコンポーネントに分類できるらしいですね。

  • スキーマ(scheme)
  • スキーマ固有部分(scheme-specific-part)
  • 機関(authority)
  • ユーザー情報(user-info)
  • ホスト(host)
  • ポート(port)
  • パス(path)
  • クエリー(query)
  • フラグメント(fragment)

分類できるからと言って、URIが必ずそれを持っているとは限りません。まァそりゃそうです。

とりあえずサンプルとして9つすべてを持っているURIを用意しましょう。「scheme://user-info@host:8000/path?query1=v1&query2=v2#fragment」と言うURIがあったとして、それぞれどこに対応するかは以下の通りです。

コンポーネント
スキーマ(scheme) scheme
スキーマ固有部分(scheme-specific-part) user-info@host:8000/path?query1=v1&query2=v2
機関(authority) user-info@host:8000
ユーザー情報(user-info) user-info
ホスト(host) host
ポート(port) 8000
パス(path) /path
クエリー(query) query1=v1&query2=v2
フラグメント(fragment) fragment

android.net.UriUri.Builderのメソッド

さてさて、ここまでわかってしまえば後は大したことはありません。UriクラスUri.Builderクラスメソッド名とURIそのもののコンポーネント名から必要なものを割り出していくだけです。

Intentを送信する側も受信する側も、URIのどのコンポーネントにデータを設定 / どのコンポーネントからデータを取得するのかをちゃんと考えないといけません。そこの認識に齟齬があると正しいデータを送れなくなってしまいます。

実際にデータを送受信する場合はURIだけでなくIntent#putExtraメソッドの使用も検討したほうがいいと思います。そっちの方が圧倒的に簡単ですしね。(とは言え、カメラアプリに対して暗黙的Intentを発行した時の挙動を考えると、何でもかんでもputExtraするのも考え物なんでしょうが。)

閑話休題android.net.Urijava.net.URIと違ってコンストラクタがありません。代わりにいくつかのpublic staticなメソッドと、専用のBuilderが用意されているので、そこから組み立てていきます。また、既存のUriからbuildUponメソッドを呼び出すことでBuilderに変換することができます。

Uriを作成する(直接Uriを返す)メソッドは以下の通りです。

他にもnormalizeScheme()withAppendedPath(Uri baseUri, String pathSegment)Uriを返しますが、ちょっと意味合いが違います。

normalizeに関してはjava.net.URIの解説の方がわかりやすいです。ただしnormalizeSchemeなので、schemeしか正規化してくれません。何の意味があるんだ?

withAppendedPathはメソッド名と引数から自明だと思うので割愛。

Uri.Builderの方は普通にコンストラクタがあるのでnewできます。普通のBuilderパターンなのでメソッドチェインでガシガシ書けますが、上記のUriメソッドで作れるならそっちで作り、更に情報が必要ならUri#buildUponするのが一番早くてわかりやすいかなと思います。

// Uri.parseを使用するパターン
Uri uri = Uri.parse("scheme://user-info@host:8000/path?query1=v1&query2=v2#fragment");

// Uri#fromPartsを使用するパターン
uri = Uri.fromParts("scheme", "user-info@host:8000/path?query1=v1&query2=v2", "fragment");

// Uri.Builderで1から作成するパターン
uri = new Uri.Builder()
 .scheme("scheme")
 // 既にencodeされている文字列を渡すなら「encodedAuthority」メソッドを使う
 .authority("user-info@host:8000")
 // '/'がなくても勝手に入れてくれるとのこと(javaDoc参照)
 .path("path")
 .appendQueryParameter("query1", "v1")
 .appendQueryParameter("query2", "v2")
 .fragment("fragment")
 .build();

逆に各コンポーネントのデータを取得する場合はandroid.net.Uriメソッド一覧を見るだけでも十分でしょう。getからコード補完使うだけでもいけると思います。java.net.URIよりquery絡みの便利メソッドが増えているので、大いに活用しましょう。

後、前回も言いましたが、MIME TypeはIntent経由で設定 / 取得する必要があります。RFC 2396を読んでも「MIMEは多分headerについていると思うんですけど(名推理)」と書かれています。URI自体にその情報を持たせないことになってるので仕方ないとは思うんですが、若干面倒臭いですね。だからこそのIntent#setDataAndType(Uri data, String type) なんでしょうが。

まとめ

前回でIntent Filterと暗黙的Intentの発行方法、今回の記事でIntentに付与するURIの作り方の話をしました。

次回は「Android特有のscheme」の話をしようと思います。なんでか次回の話をすると中々書かない傾向は確かにあります。

参考