見出し画像

【Android Auto】画面スタックとテンプレートでハマるポイント

こんにちは、ばろっくです。ナビタイムジャパンで『auカーナビ』のアプリ開発を担当しています。

当社では、すでに『カーナビタイム』、『ドライブサポーター』、『ツーリングサポーター』とAndroid Autoに対応したアプリを次々とリリースしています。

Android Autoのライブラリは、現在バージョン1.2がリリースされています。現行のバージョンでは、タッチ操作やテンプレートに制限があります。
今後リリース予定のバージョン1.3では、「Map Template」や「タッチイベント」が追加され、さらにAutoアプリの操作性の向上が期待されます。

Android for Cars App Library design guidelines「Map Template examples」

しかし、Android Autoはスタック管理が厳格で、仕様検討の段階で適切な遷移フローの設計が必要です。スタック管理をおろそかにすると、スタック数の上限にひっかかり強制終了が発生します。1.3へのアップデート対応や新規開発前には、スタック管理に問題がないか確認してください。

初めてAuto開発を行う場合、Autoの制限(リストの表示数やプレビューの数)を回避する方法がないか、どういう動きになるか知りたくなるケースがあります。しかし、Autoアプリの開発事例が少なくWEBで検索しても、あまり情報が集めることができませんでした。
そこで、今回の記事では基本の「スタック管理」や「はまりポイント」を、エミュレータの動作結果と合わせまとめました。


テンプレートについて

公式のガイドラインでは、以下のように記載されています。

Android AutoはScreenにテンプレートを設定し、Screenインスタンスを画面スタックに追加します。テンプレートの最大スタック数は5枚で、最後のテンプレートはいずれかタイプである必要があります。

1.NavigationTemplate
2.PaneTemplate
3.MessageTemplate

Android Auto用ナビゲーションアプリを作成する「テンプレートの制限事項」

【ポイント】
スタック数でチェックするのはテンプレートの数であり、Screenではありません。Screenが1つでもテンプレートを複数切り替えることで最大スタック数に到達する可能性があります。

Android for Cars App Library design guidelines「Maximum task flow with subflow」

テンプレートの特殊ケース

更新

『ListTemplate』や『RoutePreviewNavigationTemplate』など一部のテンプレートは、ローディング機能を保有しています。読み込み状態から結果反映の更新では、テンプレートの割り当て増加はありません。しかし、リスト内容変更による更新は、テンプレートの追加と判断され、割り当てが増加します。

戻る

スタックからScreenをポップすると、ポップされたScreenのテンプレート数に基づいて割り当て数が更新されます。
戻ったScreenでは、最後に送信したテンプレートと同じタイプのテンプレートの送信が必要です。

リセット

『NavigationTemplate』が設定されると、テンプレートの割り当てがリセットされ、新しいタスクの最初のステップとして扱われます。

エミュレータでの動作確認

エミュレータでは、現在のスタック数を画面上に表示させることができます。スタック数の表示はスマホのAutoアプリのデベロッパー設定から、「デバッグ オーバーレイを有効にする」にチェックを入れてください。
エミュレータの右下に、スタック数表示の領域が現れます。

デバッグ オーバーレイ表示領域

各テンプレートでの更新とスタック数の変化を確認します。テンプレートの更新は以下のアクションで実行します。

    private val updateAction: Action = Action.Builder().setTitle("更新").setOnClickListener {
         updateCount++
         invalidate() // テンプレート更新 
    }.build()

更新するたびに更新数のupdateCountをインクリメントします。
メッセージやタイトルにupdateCountを表示することで、異なるコンテンツ表示を実現しています。

更新 - MessageTemplate

Android for Cars App Library design guidelines「Message Template」

メッセージテンプレートは、同じメッセージであれば更新してもスタックに変化はありません。メッセージ内容を変更するとスタック数が増加します。

    override fun onGetTemplate(): Template {
        val builder = MessageTemplate.Builder("メッセージ  : $updateCount").apply {
            setTitle("画面A")
            addAction(updateAction)
        }
        return builder.build()
    }
メッセージテンプレートの更新とスタック数変化

【ポイント】
同一テンプレートを用いて、「次へ」でページを送るような要件は、スタック数の上限を想定して実装する必要があります。
同じスクリーン内で文言を変更させても、スタックを節約することはできません。

更新 - ListTemplate

Android for Cars App Library design guidelines「List Template」

リストテンプレートは最大6つのアイテムをリストで表示するテンプレートです。リストテンプレートはローディング状態も表示可能です。

リストテンプレートでローディング表示を行う場合は、setLoadingを有効にします。

    private fun createLoadingListTemplate(): ListTemplate {
        val builder = ListTemplate.Builder().apply {
            setTitle("読み込み")
            setHeaderAction(Action.BACK)
            setLoading(true)
            setActionStrip(ActionStrip.Builder().addAction(updateAction).build())
        }
        return builder.build()
    }

リストアイテムの表示は、以下のコードを利用します。リストアイテムのタイトルには、更新数のupdateCountもタイトルに追加しています。

    private fun createListTemplate(): Template {
        val builder = ListTemplate.Builder().apply {
            setTitle("リスト")
            setHeaderAction(Action.BACK)
            setSingleList(createItemList())
            setActionStrip(ActionStrip.Builder().addAction(updateAction).build())
        }
        return builder.build()
    }

各テンプレート表示でのスタック数の変化を確認します。

  1. 初回ローディング表示:updateCount = 1

  2. リスト表示1回目:updateCount = 2

  3. リスト表示2回目:updateCount = 3

1.初回ローディング表示
初回表示はローディング状態のテンプレートを利用しているため、読み込み中のアニメーションが表示されます。また、スタック数は1となります。

2.リスト表示1回目
更新ボタンを実行し、読み込み状態からリストに切り替えます。updateCountは2となりますが、ローディング状態からリスト表示への切り替えではスタック数に影響を与えません。

3.リスト表示2回目
さらに更新を実行しcountの数を更新します。
今回は、リスト表示からリスト表示への更新となります。テンプレートは同一ですが、リスト項目の内容が変化したため、新しいテンプレート追加と判断されスタック数が1つ増加しています。

『GridTemplate』や『RoutePreviewNavigationTemplate』も同様の結果となります。

【ポイント】
『List Template』の最大表示件数は6件です。地点検索などで、より多くの検索結果を表示させるため「次を表示」させる機能を実装しても、新しいテンプレートと判断されるため、いずれ強制終了が発生します。厳選した6項目に絞って表示させる必要があります。

戻る

「戻る」は、Screen単位でScreenManagerスタックからポップする動作です。ポップされたScreenに紐づくTemplate数だけスタックから取り除かれます。ガイドラインには次のように記載されています。

アプリが画面 A で 2 つのテンプレートを送信してから、画面 B をプッシュし、さらに 2 つのテンプレートを送信した場合、アプリの残りの割り当ては 1 つになります。アプリが画面 A に戻った場合、ホストは割り当てを 3 にリセットします。これは、アプリが 2 つのテンプレート分前に戻ったためです。

Android Autoナビゲーションアプリを作成する「戻る操作」


また、守るべきルールが存在します

画面に戻るとき、アプリは、その画面で最後に送信したテンプレートと同じタイプのテンプレートを送信する必要があります。それ以外のテンプレート タイプを送信すると、エラーが発生します。ただし、戻る操作時にタイプが同じのままであれば、アプリは割り当てに影響を与えることなくテンプレートのコンテンツを自由に変更できます

Android Autoナビゲーションアプリを作成する「戻る操作」

更新でコンテンツ内容が変化した場合はスタック数が変化していましたが、「戻る」を実行した場合はコンテンツ表示内容に変更があっても変化しません。

【ポイント】
「戻る」動作後に、リスト内に表示している表示物が再構成(距離・時間の更新や検索結果のリロード)され、内容が前回と不一致になっても問題ありません。

リセット

ナビゲーションテンプレートが表示される場合、テンプレートの割り当てがリセットされます。

遷移前と遷移後で値が変化していることが確認できます。

この状態で、「戻る(pop)」と「ルートに戻る(popToRoot)」を実行した場合、スクリーンとスタックはどう変化するか確認します。

画面C(ナビゲーションテンプレート)から「戻る(pop)」を実行すると、直前の画面Bに戻り、スタック数も3に戻ります。
画面Cから「ルートに戻る(popToRoot)」を実行すると、Sessionに追加したScreenまで戻り、スタック数は初期の1になります。

戻ると追加

画面遷移のタイミングで、テンプレートのスタック数を節約することは可能でしょうか。
スタック数の増加を防ぐため、以下のコードを利用します。スクリーン切り替え前に、スタックされたスクリーンをpop後、新しいスクリーンをpushしテンプレート数の変化を確認します。

private val nextPageAction: Action = Action.Builder().setTitle("次").setOnClickListener {
    screen.pop()
    screen.push(SampleScreen.createScreen())
}.build()

下図の結果から、pop→pushを連続で呼び出しても、テンプレートの割り当てはリセットされていないことがわかります。最後の画面Cからpop、pushを呼び出すとスタック数オーバーで強制終了します。
しかし、最後の画面Cでpopを呼び出すと、これまでのpopでScreenManagerからScreenは取り除かれているため画面Aに戻ることができ、スタック数もリセットされ1に戻ります。

まとめ

公式ガイドラインやエミュレータの動作結果からもわかるように、かなり厳しいスタック管理がおこなわれており、複雑な表現やフローを制限することで、運転時の安全性を確保しているものと思われます。
もし複雑な操作を要求する場合は、スマホ側で事前に準備させAutoで読み込むなど、ユーザー操作を減らす工夫が必要です。

Android  Autoアプリの開発をする方は、最初にスタックの動作や仕様を把握しアプリを設計してください。