Slicesの仕組みと実装方法

もり氏(以下、もり):こんばんは。Slicesの内部実装を見ながら自分でこういうの作るにはどうやって作るだろう的な、ちょっと益体のない話をしようと思っています。

自己紹介させてください。森と言います。ヤフーから来ました。

ヤフーには黒帯というこういう場でお話ししたりする役割があるんですが、私はAndroid分野で黒帯をしております。そのほかヤフオクもやっております。

いきなり宣伝になってしまいますが、最近本を出しました。

ところで今我々はこちらヒカリエにいるんですけれども、なんとここに啓文堂書店があります。啓文堂書店、よろしくお願いします。

(会場笑)

基本からしっかり身につくAndroidアプリ開発入門 Android Studio 3対応 (「黒帯エンジニア」シリーズ)

では始めていきたいと思います。Slicesについてです。

まずSlicesの仕組みなんですけれども。Slicesを返すSliceProvider、我々はこれを作ります。

このSliceProviderはContentProviderを継承していて、ContentProviderなのでSlicesを表示する側はURIをもとにデータ、ここではSlicesを要求するという感じで動いています。

Slices自体がこんな感じで構造化されたフォーマットのデータになっています。

ほかのSlicesとか表示フォーマットだとかアイコンなんかが含まれています。

SliceProviderを実装するにはAndroid Studio 3.2を入れて右クリックして、NewからOther、SliceProviderを押せば終わりなんすけれども。

それだとあんまりなので少し説明を入れると、SliceProviderを継承したクラスを作ってonBindSliceメソッドの中でSlicesを作って返すという感じで作ります。

これは結局ContentProviderなのでマニフェストファイルにAuthorityとともに追加するという感じで使います。

Slicesはどこで使われるか?

こうして我々サードパーティーはSliceProviderを作るわけなんですが、作ったとしてどこで使ってもらえるのか? 最初はGoogle検索アプリで使用されます。

Google検索アプリでユーザーがクエリを入力するときのサジェストの中に、自分のアプリのSlicesを表示してもらえる可能性があります。

そのための方法が2通りあって、まずユーザーがアプリ名を入力したときに表示する方法。この方法は簡単で、マニフェストファイルのメインアクティビティにメタデータを追加します。

このメタデータにSlicesに対応するURIを設定して、アクティビティの中でGoogle Play ServicesとGoogle検索のパーミッションを入れれば終わりです。

でもGoogle検索アプリでアプリ名で検索するユーザーって……そんなユースケースあるかな? って感じなので, 一般的な単語での検索に反応したいなっていうところです。

これのやり方なんですけれども、FirebaseAppIndexingにIndex登録するときにメタデータとしてSlicesのURIを追加する。こうするとAppIndexingが使用されるタイミングでSlicesが使われる可能性があるという感じになります。

そのあとマニフェストファイルのSliceProviderのエレメントにIndexしたURLを受け取るintent-filterを追加します。SlicesのURIではなくてIndexしたURLを設定します。

最後にSlicesプロバイダーのonMapIntentToUriというところで、AppIndexingが渡してくるディープリンクのURLをSlicesのURIに変換して返すという感じです。

以上がGoogle検索アプリで表示してもらう場合の方法です。

サードパーティーでSlicesを使うには?

ところでサードパーティーは表示できないのか? というところです。Google検索アプリで表示してもらえるだけでもけっこう嬉しいんですが、サードパーティーのアプリ間で連携できればもうちょっと便利そうな感じがします。

特にヤフーにはたくさんのサービスがあって、いろんなアプリを世に出しています。これらで連携できるようになると、なんか便利そうだなって思うわけですね。ヤフオクのケースだと、例えば決済アプリでネイティブ化するということもできそうです。

こういう連携をどうすれば実現できるかなんですけども。ステップ1、待つ。

(会場笑)

というのもGoogle I/Oから帰ってきたときにGoogleの人と話してきたんですが、Slicesの表示をサードパーティーに解放するかどうかはまだ決まっていない。

今、Slice Viewerというものが公開されていて、SliceViewというSlicesを表示する用のViewもJetpackの中に入ってるんですけども。このAPIも今後どうなるかわからないということを言っていました。

じゃあ、Slicesの表示が解放されなかったらどうやってサードパーティ間連携をしていったらいいのでしょうか? 作りましょう、それっぽいのを。

というわけでSlicesが具体的にどう動いてるのかを追いかけてみたいと思います。今回はSliceViewerのソースコードをもとにSliceを要求するところからSlicesを作って返すところまで追いかけていきたいと思います。

まずSlicesを要求するところから。

SliceViewerではSliceViewのbindメソッドを呼ぶことでSlicesのリクエストが開始されます。

これは拡張関数でして、中ではSliceLiveDataというクラスを生成して値を監視しています。

SliceLiveDataではSliceManagerのインスタンスを取得して、bindSliceメソッドを呼びます。

このSliceManagerのgetInstanceメソッドはAndroid P以降かどうかで返すインスタンスが変わってきます。

Android P以降の場合SliceManagerのラッパーを返すんですが、これは新たに爆誕したSliceManagerというシステムサービスをラップしたものになります。

P未満の場合はSliceManagerCompatというクラスのインスタンスを返します。自分でシステムマネージャー作っても虚しいだけなので、今回はこっちのSliceManagerCompatを追いかけていきます。

このbindSlicesが呼ばれてたいたわけなんですが、このクラスはSliceProviderCompatのメソッドを呼ぶだけのクラスです。今、Slicesを要求している側なんですけども、実はSlicesを返す側もSliceProviderCompatを使います。

実は間にはSliceProviderCompatが入ってお互いのSliceProviderCompat同士で通信します。SliceProviderCompatではURIをチェックしたりしてbindSlicesを呼びます。

ここが終着点なんですが、ContentResolverからContentProviderClientを取得してcallメソッドを呼んで、その戻り値のバンドルからSlicesを生成して返します。

ContentProviderを使ったことがある人っていますか?

(会場挙手)

いっぱいいますよね。ContentProviderClientを使ったことがある人っていますか?

いないですね。ContentProviderClientはAPIレベル5からある古株のクラスです。ContentResolverを経由しないで直接ContentProviderとやり取りするためのものです。

ContentProviderデータを要求するときに毎回ContentResolverを使っていると、毎回URIから対応するプロバイダはどれかなって探す処理が走ります。この探す処理がなくなるのでかなり高速化します。

そのcallメソッドなんですが、リレーショナルな感じでリクエストしないときに使うようなメソッドです。引数としてメソッド名を渡します。

実際には任意のメソッドを呼ぶわけじゃなくて、対応するプロバイダーのcallメソッドが呼ばれます。呼び出されたプロバイダ側で引数として渡されるメソッド名や引数を判定して、なにかを実行して結果をBundleで返します。

Slicesを返す側の動き

ここまでがSlicesを要求するフェーズです。結局のところSliceProviderCompatのcallメソッドを呼んでいました。これに対してSlicesを返す側のアプリは何をしているのかを追いかけてみたいと思います。

callメソッドはこうなっています。handleBindSliceというメソッド内でSlicesを作って、Bundleに変換して、さらにBundleに積んで返しています。

Slicesを作っているhandleBindSliceを見てみると、こんな感じです。

なんやかんやでonBindSliceStrictを呼びます。そのonBindSliceStrictがSlicesを生成するときの終着点になります。

2秒でANRが発生するように設定して、ディスクI/Oなど重たい処理をしたらクラッシュするようにしたうえで、SliceProviderのonBindSliceを呼びます。

SliceProviderのonBindSliceは最初に説明したとおり、自分でオーバーライドして実装するものです。

なので大切なのはSlicesが要求されたときに自分で返す場合には、同期的なディスクI/Oだとかネットワークをしないで、2秒以内に返さないといけません。

すぐに返せないのであれば一旦「読み込み中」とでも返しておいて非同期的にデータを取得してnotifyChangeを呼ぶようにしましょう。

こんな感じでSlicesを返しました。そのSlicesはどのように表示されているかですが、もともとライブデータ内で要求されていたので、まずはpostValueでライブデータを更新します。

それを受け取って、SliceView内のプロパティを更新します。

プロパティアクセスっぽく見えるんですが、実際にはSliceViewのsetSliceが呼ばれていて。メソッド内のreinflateというメソッドを呼びます。

この中でSlicesの値、プロパティを読み取りながらViewを生成していくんですが、そこは単純なのでこれ以降は省略します。

Slicesのようなものを自分で作るには

長々とSlicesの内部実装を見てきたわけですが、こうしたSlicesめいたものを自分で作るんだったらどうするのか、ということでまとめます。

まずBundleと相互変換できるクラスを作ります。次にContentProviderClientを使ってBundleをやり取りします。最後にBundleから元のクラスに戻して、その値を参照しながらViewを生成するロジックを書く。ここだけ見ると簡単そうに見えまが、ひたすらめんどくさい感じになります。

全体のまとめとしては、Slicesは古典的なAPIをうまく使って作られています。まずはGoogle検索アプリで使われます。サードパーティー間の連携に使用できるかはまだ不透明なようです。

でも似たようなことは作れるようになりましたね。ということで、ご静聴ありがとうございました。

(会場拍手)