Fragment を使った画面遷移メモ

最近 Fragment 周りの実装を追う機会があり、 Android Developers の FragmentTransaction ページは各メソッドの説明が若干分かりづらいと思ったので自分用のメモとして主要なメソッドをまとめてみることにしました。

add

名前の通り Fragment を追加するときに利用します。 特定の container(ViewGroup) に Fragment を追加する場合の他、UIを持たない Fragment を追加する際にも利用します。 DialogFragment#show()内部的には FragmentTransaction#add() を呼び出しています

注意点として、画面遷移の際に add() を利用した場合、遷移前の Fragment に重ねて表示されるため、別途remove()する必要があります。 通常は画面遷移の用途としては add() と remove() を同時に行う replace() を利用するのが良いでしょう。

remove

指定したFragmentを削除するときに使います。 削除されたFragmentはActivityとの関連付けが解除され、 onDestroyView() -> onDestroy() -> onDetach() というライフサイクルを辿ります。 対象のFragmentに対する参照を自前で持っていない場合、Fragmentインスタンスは自動的に破棄され、再表示することはできません。

ただし、 remove() 操作の大きな特徴として、同時に addToBackStack() されている場合には Activity との関連付け解除が行われず、 onDestroyView() までしか実行されません。 これにより、遷移前のFragmentのインスタンスを維持したまま画面の完全な切り替えを行うことが可能です。

replace

replace()remove()add() を同時に行うメソッドで、主に画面遷移のために利用します。 同時に addToBackStack() を呼んでいない場合、遷移元の Fragment は Activity との関連付けが解除されますが、呼んでいる場合は View だけが破棄されている状態になり、同一Fragmentを再利用可能です。

ちなみに、対象の container に Fragment が何も表示されていない状態でも replace() によって画面遷移する事は可能なので、初期表示画面への遷移も replace() で実装することは可能です。 稀に初期表示画面を add() で実装して Activity 再生成のたびに初期表示画面が追加されていくアプリがあるので、画面遷移では replace() に統一してしまっても良いかもしれません。

addToBackStack

指定したトランザクションをコミットした後で元に戻したい場合に利用します。 バックスタックに積まれたトランザクションは back ボタンを押下することで自動的に pop され、トランザクションで指定されていた動作は元に戻ります。 (ただし、自動的に pop されるのは Activity が持つ FragmentManager のバックスタックに積まれた場合のみです。 getChildFragmentManager() を利用した場合は後述します) 基本的には replace() あわせて利用することで画面遷移を戻れるようにするために利用します。

detach(), attach()

detach() を利用すると Activity に追加済みの Fragment をUIから取り外すことができます。 具体的には、 remove()addToBackStack() 付きで呼び出した場合と同じく、 onDestroyView() までライフサイクルが進んだ状態になり View が破棄されつつFragment自体は残る状態になります。 ややこしいですが、 Fragment のライフサイクルである Fragment#onAttach()Fragment#onDetach() とは関係ありません。

attach() は一度 detach() されたFragmentのUIを再度生成して表示する場合に利用します。 なかなか使いどころがないと思うかもしれませんが、 Fragment が持つ OptionsMenu やネストされた Fragmentなども View と同時に破棄/再生成されるため、タブの切替処理などで有用です。 FragmentTabHostFragmentPagerAdapterではこの仕組を利用して表示Fragmentの切り替えを行っています。

show(), hide()

Fragmentの表示状態を切り替えます。 detach(), attach() と違い、Viewの破棄や再生成が行われず、ライフサイクルも変化しません。 Fragment側のフラグで管理しているため、hide()したFragmentをremove() -> add()し直した場合でもView要素は表示されません。 OptionsMenu も同時に制御されて便利ですが、View自体は生成されたままなので画面遷移の多くをこの仕組で行う場合はメモリ消費に注意が必要です。

setPrimaryNavigationFragment()

setPrimaryNavigationFragment は v26.1.0 で追加された比較的新しいメソッドです。 このメソッドを呼び出すと、これ以降のbackボタン操作において指定したFragmentが childFragmentManager の popBackStack() を処理するようになります。

  1. fragmentA に遷移(replace)
  2. fragmentA の childFragmentManager で childFragmentB に遷移(replace & addToBackStack)
  3. fragmentA の childFragmentManager で childFragmentC に遷移(replace & addToBackStack)

という遷移を行う時、 1.replace() と同時に setPrimaryNavigationFragment(fragmentA) を呼び出しておくことで childFragmentC -> childFragmentB -> fragmentA というバックスタックの解決を backボタンだけで行うことができて非常に便利です。 特にタブ実装などで複雑なFragmentの入れ子を管理する場合に detach(), attach() と組み合わせて利用することになりそうです。

commit(), commitNow()

どちらのメソッドも設定中のトランザクションを実行します。 commit()commitNow() の違いは即時実行されるかどうかですが、 どちらの場合でも onSaveInstanceState() 以降に呼ばれた場合は IllegalStateException が発生するので非同期処理から呼び出す場合には注意が必要です。

commitAllowingStateLoss(), commitNowAllowingStateLoss()

どちらのメソッドも設定中のトランザクションを実行します。 commit(), commitNow() と違い、 onSaveInstanceState() 以降に呼ばれた場合でも IllegalStateException を発生させません。 state は Fragment のsavedInstanceState ではなく、Activity側のstateとして保存されるこのコミット自体を指していることに注意してください。 このメソッドを使って onSaveInstanceState() 以降に行った操作は Activity に保存されません。 何らかの理由でActivity自体が再生成された場合、このメソッドによって行われた操作は復元されなくなります。

Android開発をする上で知っておいてほしいなと思うこと 2

nein37.hatenablog.com

上記の記事の続き。 開発時に引っかかりがちないくつかの注意点と、リリース時に知っておいたほうが良いと思うことについてまとめてみる。

開発時の知見

主にチーム開発時の注意点と標準ツールの活用、よく嵌りがちな落とし穴について。 前回記事の アプリの実装について でも少し触れていたが、もう少し広い範囲について必要だと思うことを書いてみた。

チーム開発の注意点

他の開発者と開発環境を揃えることで開発を効率化できる他、他のメンバーに自分の変更による差分をチェックして貰う必要がある。 また、Android開発者ではない人に対する技術的な説明もできたほうが良い。

標準ツールの活用

Android Sdk(とそこからダウンロードできる各種ツール)とAndroid Studioに付属しているデバッグ機能について。

標準ツールではないがstetho などのデバッグ用ツールについても知っておくとより良い。

よく嵌まる落とし穴

  • Auto Backup
    • Auto Backup が有効な場合、 SharedPreferencesやSQLiteデータベースなどが自動的にバックアップされる
    • 復元されると困る情報は除外設定を必ず行う
    • Auto Backup の設定はデフォルトで有効
  • uses-feature の暗黙的設定
    • 一部の uses-permission 設定は自動的に対応したデバイス機能の uses-feature 設定を追記する
    • 通常あまり問題にならないが、AndroidTV向けの実装を同一apkに同梱する場合などに問題が起きる

リリース時に知っておいたほうが良いこと

リリース時にチェックしておくべき項目やアプリの公開に関する設定項目のうち知っておいたほうが良い項目についてまとめる。 基本的にほとんどの内容は Android Developers に書いてあるが、Playコンソールの新機能についてはPlay Console ヘルプ(英語版)のほうが詳しい時もある。 Playコンソールに関して迷ったり疑問に思ったらまずは公式のヘルプをあたってみると良い。

  • Distribute on Google Play
    • リリースに関する基本的な項目は Distribute 以下にある
    • アプリのリリース前に一度は見ておいた方が良い
  • Core app quality
    • アプリが満たしておくべき品質のリスト
    • 特に戻るボタンの動作の一貫性などは実装時から気をつけておいたほうが良い
  • Launch checklist
    • アプリ公開時に必要な項目のチェックリスト
    • 必須項目ばかりではないが、知識として知っておいたほうが良いこともある
  • Release updates progressively
    • 段階的リリースでは公開率を段階的に上げるだけでなく問題のあるリリースを途中で中止することができる
      • 問題なければ再開することもできる
    • モバイルアプリは簡単にロールバックできないので、公開時のリスクを抑える方法として有用
  • Run alpha and beta tests | Android Developers
    • アルファ/ベータリリースについての説明
    • インストールまでにユーザー操作が必要なため若干利用し辛い印象がある
    • 後述のリリース前クラッシュレポートとあわせて一般向けではなく社内向けの開発版配信として使うのも良いかもしれない
  • Use pre-launch and crash reports
    • アルファ/ベータリリースとしてapkをアップロードすることでリリース前クラッシュレポートを利用できる
    • Firebase Test Lab と同様のものだが、リリースフローの一貫に組み込むことができるのは便利
  • Engage
    • より利用されるアプリにする方法のまとめ
    • どれでも利用できるというわけではないが、リンク先も含めて知識として有用
  • Grow
    • ユーザー数を増やす方法のまとめ
  • Developer stories
    • 成功者のストーリー一覧 -InstantApps でドーンみたいな話だけでなくPlayコンソールの細かいA/Bテストを繰り返したという話も乗っていてわりと実用的

まとめ

これで当初書きたかった内容は大体書いた。 ここまで書いてきた内容を振り返っても Android Developers は開発〜リリースまでを支えてくれる優秀な公式ドキュメントだと思えるので、安心して頼っていけそう。

Android開発をする上で知っておいてほしいなと思うこと

現在の Android Developers の情報は非常に充実していて、Developer Guides を順に読み進んでいくだけで開発に必要な知識とGoogleが想定している(であろう)最も基本的な実装を学ぶことができる。 特にこの「基本的な実装」というものが重要で、これを知っておかないと開発者間の意思疎通がスムーズに行えなかったり、そもそも気をつけておくべき注意点を見落としがちになってしまう。

とはいえ、今の膨大な公式ドキュメントをただ読めというのは厳しいので、Android開発をする上で最低限理解しておいてほしい(と僕が思っている)事柄と、それについて知ることができるドキュメント類についてまとめてみることにする。

2018/03/25 : リリース周りについて別記事に追記した。

nein37.hatenablog.com

公式ドキュメントの重要ページ

公式ドキュメントと言った場合、 Android Developers を指す。 最近は日本語化されている記事も増えてきているが、日本語ページは稀に内容が古かったりするので人からリンクを渡された場合は念のため英語ページも見ておいたほうが良い。

Android SDK Search という Chrome拡張を入れておくと Android Developers の検索をショートカットできたりクラスリファレンスからソースコードに飛べたりして非常に便利でオススメ。

最新の情報や問題を追いたいのであれば公式サイトだけでなく以下のサイトも見たほうが良い。

OSのバージョンについて

(5.x 以降では) Android のメジャーバージョンアップは大体一年に一度行われる。 過去のOSアップデートの差分については公式ドキュメントに詳しく書いてある他、最新のOSは公式リリースの前に Developers Preview が公開され、エミュレータや一部のデバイスで動作を確認することができる。

OSアップデート差分は大まかに「APIの変更点」「挙動の変更点」「サンプルコード」にわけられている他、特に重要な変更点については別ページで説明している場合もある。

以下では Oreo のアップデート差分を例に説明する

  • Android 8.0 Features and APIs | Android Developers
    • 変更(主に追加)になるAPIの概要
    • いわゆる新機能についてはここで説明される
    • ここで説明される機能のほとんどはそのバージョン以降でしか利用できないが、 Support Library 側であとからバックポートされるものもある。
  • Android 8.0 Behavior Changes | Android Developers
    • OS側の挙動変更の概要
    • 挙動変更には2種類あり、「すべてのアプリに適用されるもの」と「targetSdkVersionがある値以上の時に適用されるもの」がある
    • targetSdkVersion を変更する際はこの差分をただしく把握していないと不具合の元になるので、ここに説明があることを覚えておく
  • Code Samples | Android Developers
    • 新機能などの実装サンプル
    • 必要最低限の実装だったり過去のOSでの挙動がフォローされていなかったりするので注意
    • 新機能を利用する際はサンプルコードだけでなくAPIリファレンスを必ず参考にすること

毎月更新されるDashboardsにOSバージョンごとのシェアなどがまとまっているが、これは全世界での集計のようなので国内シェアとはずれている可能性があるので注意。

Android端末の特性について

Androidではスマートフォンタブレットの確実な判定方法が存在しない。Android Developers ではsw600dpが閾値っぽい表現がされていたとしても、メーカーがそこから逸脱した端末を出す ことがあるので確実ではない。

もし何らかの理由でタブレットを区別したい場合、「このアプリにおけるタブレットの条件」を決めておく必要がある。以下にその条件になりそうなものをいくつか挙げる。

  • sw600dp 以上の端末をタブレットとみなす
    • sw(smallest width) とは端末の向きに関わらず算出される画面の狭い方のdp値
    • Android Developers でも一応の閾値として扱われていて、システムUIとの親和性が高い
    • ただし端末そのものではなく表示領域を計算するものなので、端末側のdpi設定を大きく変更したり Nougat から導入された画面分割機能を利用した場合は一致しなくなる
    • Play ストア上のフィルタでは利用できないため、APK分割やインストール端末からの除外に利用することはできない
  • スクリーンサイズが large, xlarge 以上の端末をタブレットとみなす
    • おすすめしない
    • xlarge はほぼ確実にタブレットなのだが、 large はタブレットとは断言されず 640dp x 480dp and bigger となっていて非常に歯切れが悪い
    • sw480dp とほぼ等しい設定になるが、411dp(最近増えているスマホ幅)と480dp では近すぎ、480dp と 600dp では遠すぎてレイアウトの境目にし辛い
    • <compatible-screens> に記載することでインストール端末から除外することができるが、ファブレットのような例外を設定できない
  • Playストアの端末カタログから除外
    • もし区別の目的が「インストールさせないこと」である場合、配信設定で除外することができる
    • <compatible-screens> に記載するよりは細かいコントロールができるが、アプリだけでなくPlayコンソール側の設定も把握しておく必要があり運用が面倒になりがち

個人的な気持ちとしては、「タブレットをサポートしない」という判断は思ったほどコストが下がらない上に考えることが複雑なのであまりおすすめしない。 特に「画面回転を考慮したくない」という理由でタブレットを除外したいという意見が出ることがあるが、画面回転でクラッシュするアプリは何かしらライフサイクル周りの処理をミスっているのでどのみちクラッシュする可能性が高い。 タブレットレイアウトに対応するのが大変という意見もあるが、320-480dp のスマートフォンサイズまで全幅表示に対応し、それ以上の幅になった場合は中央寄せにでもしておけばそこまで手間ではない。 以前は android:maxWidth がこのような用途に使えなかったため不便だったが、現在はConstraintLayoutmaxWidth の設定ができるため、簡単に設定できる。 Android Studio の Scrolling Activity テンプレートで例を示すと、コンテンツ部分の content_scrolling.xml を以下のように変更すれば ConstraintLayout の最大幅を超えた場合はコンテンツの中央寄せ表示ができる。

content_scrolling.xml

<?xml version="1.0" encoding="utf-8"?>
<android.support.v4.widget.NestedScrollView xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    app:layout_behavior="@string/appbar_scrolling_view_behavior"
    tools:context="com.github.nein37.myapplication.ScrollingActivity"
    tools:showIn="@layout/activity_scrolling">

    <android.support.constraint.ConstraintLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_gravity="center_horizontal"
        android:maxWidth="480dp">

        <TextView
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_marginBottom="16dp"
            android:layout_marginEnd="16dp"
            android:layout_marginStart="16dp"
            android:layout_marginTop="16dp"
            android:text="@string/large_text"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent" />

    </android.support.constraint.ConstraintLayout>

</android.support.v4.widget.NestedScrollView>
Nexus5X Nexus9
f:id:nein37:20180318191803p:plain f:id:nein37:20180318191526p:plain

もちろんデザイナーの判断でこういった余白の使い方がダメな場合もあるが、この程度の実装で回避策が取れることは把握しておいたほうが良いと思う。

追記: タブレットや回転をサポートした場合にテスト工数も上がるのではという懸念に関してはその通りで、ちゃんと工数を積む必要がある。 ただしきちんとボタン押下イベントからのメソッド呼び出しなど単体テストが書かれていればスマートフォン/タブレットでの動作はそこまで違いがないし、表示差異は自分で手動テストしなくても Firebase Test Lab やPlayコンソールの リリース前テスト を利用すればいろいろなタブレットでの表示結果をスクリーンショット付きでテストすることができる。これらの仕組みはログインが必要な画面でもちゃんと設定すればログインしてくれるので、早い段階で導入できれば画面構成のチェックなどはかなり楽になるはずだ。

画面実装について

もともと用意されている画面部品に沿った実装にすることで工数を削減でき、システムUIと一体感のある画面になる。 Android開発者はデザイナーに対して「Androidの標準的な画面実装はこういうもの」と説明できなければいけないと思っているので、基本的なUI部品について知っている、あるいは説明されているページの存在を覚えておく必要がある。

具体的には以下のようなページとその内容を知っていてほしい。

  • ConstraintLayout
    • ConstraintLayout を使うと描画領域に応じた再配置・リサイズが非常にやりやすくなる
    • 反面、Viewサイズではなく他のViewとの関係性によってViewを配置するため、デザイナーに画面構成の指示方法について慣れてもらう必要がある
    • ConstraintLayout で画面実装ができ、画面デザインの指定方法についてデザイナに説明できるレベルまでは達していてほしい
  • FlexboxLayout
    • FlexboxLayout は CSS の Flexbox を Android のレイアウトで利用可能にしたもので、特に似たようなコンテンツを画面幅に応じて並べる画面構成に向いている
    • RecyclerView 用の LayoutManager も用意されておりいろんなケースで利用できる
    • 実際に利用したことがなくても存在は知っていてほしい
  • Design Support Library に入っているView全般
    • 記述されていないものでも TextInputLayout など重要なものが多い
  • Material Design
    • Material Design全体の仕様なので Android だけに限らないが、Androidの標準実装として知っておく必要はある
    • Design Support Library は Material Design の表現を目的としているので、Material Designに従うことで簡単に再現できるものもある

また、画面実装をする上で dp, dpialternative resource の仕組みについては他の人に説明できるレベルで理解していてほしい。

アプリの実装について

○○アーキテクチャや○○ライブラリを使っていればOKというものはないので自分やチームがテンション上がるものを使っていれば良いと思う。 とはいえ、Androidに関しては Espresso でのテストが増えていくと厳しいのでとにかく UnitTest 可能な部分を増やせる構造にしておいたほうが良い。既存のアーキテクチャを流用する場合、テストの書き方がわかる(または自分で理解できている)ものを採用しないと後々テスト書かなくなるので要注意。 Architecture Components は ドキュメントにテストに関する記述がありサンプルコードもじわじわ増えているようなので、段々導入しやすくなっていくのかなという気がしている。

その他のよく陥りがちなところ、把握しておいてほしい注意点については雑にまとめる

  • 非同期処理+画面遷移 の組み合わせは難しい
    • 何らかの非同期処理の結果を受けて Fragment を遷移させる実装をすると IllegalStateException で死ぬ
    • 可能なら遷移後の画面で非同期処理を行うようにするか、ライフサイクルに応じて処理を遅延させると良い
    • LiveData を利用するとコンポーネントがactiveなときだけ変更が通知されるのであまり意識しなくてよくなる
  • FragmentからActivity/Fragment へのコールバックは難しい
  • バックグラウンド処理の実装は公式ドキュメントをしっかり読んでからやる
    • バックグラウンド処理はOSの更新で挙動変更が入りやすいので、なるべく公式の方針に従うのが吉
    • minSdkVersion や 仕様面で許されるなら JobScheduler、定時に実行する必要があれば AlarmManager
    • Firebase JobDispatcher も実行条件を満たせるなら検討して良さそう
  • 一意な識別子について理解する
    • Android でインストール/アンインストールを跨いだ端末識別子は広告IDを利用するか、アプリ外の領域に保存しておくしかない
    • 生成したUUIDなどを保存しておく場合、Auto Backup によって保存されてよいかどうか検討する

アプリのセキュリティについて

埋め込み値

Android ではapkに埋め込んだ値はどのような難読化をしたとしても完全に安全ではない。ただし、文字列リソースや定数としてわかりやすい名前で平文埋め込みしてしまうと簡単に解析できてしまうので避けたほうが良い。 一番良いのはそもそもアプリ内に固定値を埋め込むような実装を避けることなので、なるべくこういった実装をしなくてすむようにしたい。

どうしてもやる場合は以下のいずれかを選択することになりそう。

  1. 何らかの複合処理が必要な形でコード内に埋め込む
  2. C/C++ のネイティブ実装から値を受け取るようにする
    • 平文で埋め込むとsoファイルから解析可能らしいので要注意

Android向けの著名なライブラリなどで何らかの値が必要な場合、ApplicationID、apkの証明書とセットでしか動作しない場合が多い。 その場合はもしその値が盗まれても拡散する恐れが少ないのであまり神経質にならなくても良いかもしれない(個人の感想)。

秘匿すべき値

認証トークンなどの秘匿値は基本的に暗号化して保存する必要がある。 SharedPreference は private mode であっても root 化された端末では(ユーザーだけでなく悪意のあるアプリからも)アクセス可能なので、あまり信用しないほうが良い。 KeyStore を利用して鍵を生成し、その鍵で暗号化/復号化するのがシステムが想定している挙動なのだが、生成した鍵が消えるタイミングがある ので完全に信頼するのは難しい。

個人的には、ユーザーID、パスワードなどは SmartLock for Passwords などアプリ外の仕組みに記憶させ、トークンなどをKeyStoreで暗号化した上で SharedPreference などに保存し、復号化に失敗するようになったら再度ログインからやり直し(SmartLock に情報があれば自動再試行)というのが良いのかなと思っている。

もっと言えば認証系の処理はシステム側のGoogle認証を利用するか Firebase Auth あたりに丸投げするのが一番楽で良いと思う。

海賊版対策など

apkの一部を差し替えて再署名した、いわゆる海賊版apk問題にアプリ側だけで対応するのは難しい。対応コード自体を削除されると何の意味もなくなるからだ。 Googleから提供されている仕組みとしてはSafetyNet Verify Apps API というものがあり、これは端末とアプリの情報を精査してGoogleのサーバにチェックしてもらい、信頼がおける環境かどうかをチェックすることができる。 ただし、この結果をアプリだけで判定すると脆弱なので、確実にチェックするなら(自サービスの)サーバに検証結果を送りそちらでもチェックさせる必要がある。 かなり複雑な仕組みなので最初から入れる必要はないかもしれないが、機能としては知っておいたほうが良いだろう。

また、これ以外にも Google Play コンソール側でSafetyNet に基づく除外設定を行うことができる。 これはGoogleの認定を受けていない変な端末やエミュレータ、root取得済みの端末にPlayストアからアプリをインストールさせないための仕組みで、こちらは特に実装が不要なので最初から利用しても良いかもしれない。

メモ

簡単なまとめにするつもりだったが思いの外長くなってきたので一旦ここで投稿する。 まだいくつか出てきそうなので思いついたら追記する予定。

これまでに発売されたAndroid端末のdp解像度まとめ

はじめに

今年のGooglePlayコンソールのアップデートにより端末カタログという機能が追加されました。 これは以前から存在していた配信対象から特定端末を除外する機能に、端末の詳しいスペックを閲覧・検索する機能が追加されたものです。

GooglePlayコンソールのアプリ管理画面からリリース管理→端末カタログで遷移してアクセスできます。

f:id:nein37:20180102191720p:plain

この端末カタログはDPI値は表示できて便利なのですがDP解像度は記載されていないため、イレギュラーswdp端末を探す目的には若干不便でした。 そこで、端末カタログによって得た大量のデータからGoogle Play コンソールが認識している全端末のdp解像度まとめを作ってみました。

Android端末カタログ - Google スプレッドシート

作り方

端末カタログはAPIなどが用意されておらず、ページ内のリクエストなどをアレして端末情報のリストを探しました。 端末カタログにアクセスした際、1.6MBくらいのjsonレスポンスを受け取っている箇所があり、それが端末カタログの中身になります。 このJSONファイルはほとんどの key が 1 とか 2 なので頑張って解析する必要があります。

以下が端末1件分のデータです。

  {
    "1": "NuAns/neoreloaded",
    "2": {
      "1": "NEO [Reloaded]"
    },
    "3": [
      {
        "1": {
          "1": "NEO2",
          "2": [
            {
              "1": 1080,
              "2": 1920,
              "3": 420,
              "5": "2991501312",
              "6": "3072",
              "7": 25,
              "8": 196610,
              "9": 2
            }
          ],
          "4": "Qualcomm",
          "5": "MSM8953",
          "6": "NuAns",
          "7": [
            "NEO [Reloaded]"
          ],
          "8": true,
          "9": "https://lh3.googleusercontent.com/HLh04wdpYNiMf3WyzBUgJzbcvDb7ZxaMYHOy7Wpp7FaXbeGLbwPakW7MgKpaTns83MmU0FuPAxn_",
          "11": 1,
          "12": {
            "1": 4,
            "2": [
              4,
              5,
              6
            ]
          }
        },
        "2": {
          "1": true
        }
      }
    ]
  },

同一端末であってもOSバージョンの更新などによって別物扱いできるようになっていて、OSアップデートによるデフォルトDPIの変更などもキャッチできそうです。 このあたりはさすがGoogleという感じですね。

解像度やDPIなどの値は特徴的なのですが、フォームファクタ情報などはいろんな端末の詳細ページを見比べながらアタリをつけました。 残念ながら端末発売日や液晶サイズがなく、発売日での絞込やDPI値の乖離チェックができないのが残念です。 液晶サイズに関しては今後のアップデートでぜひ追加してほしいですね。

調査していく途中で、Android端末が スマートフォン、タブレット、ウェアラブル、TV、PC、STB、Auto、その他 という区分にわけられていることがわかったのですが、swdpで区分されているわけでもなく、外見による分類?という感じでした。 一部のファブレットやTV/PCなど分類が難しそうなものもあり、誰がどうやってフォームファクタを判定しているのか非常に気になりました。

このJSONファイルをそれっぽく並べ替えてcsvに変換し、スプレッドシートに突っ込んだものがこちら)になります。

雑感

端末カタログのデータを集計している途中、ノイズがかなり多いのが気になりました。 製造メーカーが不明な端末(OSバージョンによる重複があるため、厳密に端末数ではない)だけで5000台以上あり、本当に世に出ている端末なのかわからないものも多いです。 また、端末の解像度が一桁だったり明らかにおかしいものもあり、開発中の端末かなにかでBuild.MANUFACTURERが正しく入力されていないものも拾われているのでは…という気持ちです。

謎の端末が多く見つかる一方で、国内で流通している端末が見つからないこともあります。 ヘンテコDP解像度で有名な MediaPad T2 7.0 ProPLE-701L という型番ですが、これは名前、型番のどちらで探しても見つかりませんでした。 代わりに Huawei/华为揽阅m2青春版7_0 という端末(型番 hwple703l) という同一画面解像度で同一DPI端末があることがわかり、国内モデルはこれのローカライズモデルかな?と思ったりしました。

もう少し情報が増えてくれれば絞込による謎端末探しが捗りそうなので、Googleさんには頑張ってほしいですね。

スマートフォンタブレットのswdp分布を見てみましたが、やはりスマートフォンでは 320dp, 360dpタブレットでは 600dp, 800dp に大きく偏っていました。 ただし、スマートフォンでは 411dp, 480dp といった情報量の多い端末もそこそこ増えてきているようで、今後はAPIレベルによるswdp分布などもみたほうが良いかな?という気がしました。 タブレットでは768dp端末が600dp,800dpに次いで多くなっていて、 アスペクト比 4:3 端末も意識する必要がありそうです。

今後GooglePlayコンソールの機能が拡張されてスペック検索やdp解像度のチェックができればスプレッドシートも不要になりそうですが、それまでは定期的に更新しようと思っています。

Android端末のGoogleアシスタントから自アプリの検索を呼び出す

本日からGoogleアシスタント対応アプリを日本語で開発できるようになったようですね。 僕も最近 Google Home Mini を購入したので、日本語で使えるアプリが充実してくれると嬉しいです。

developers-jp.googleblog.com

実はAndroid端末のGoogleアシスタントでは、上記のようなGoogleアシスタント対応アプリの他に、端末内にインストールされているアプリの機能を呼び出すコマンドが実装されています。 これはGoogleアシスタントの前身、Google Nowに実装されていた Google Voice Actions という仕組みを利用したものです。 Android Developers の Common Intents ページにはこの Google Voice Actions からのインテントを受け取るための intent-filter 実装例が載っていて、この通り実装することで自作アプリで Google Voice Actions を受け取ることができます。

例として、今回は検索機能を上げてみましょう。

さきほどの Common Intents ページを見ると、search for cat videos on myvideoapp という音声指示で myvideoapp 上で cat videos を検索することができるようです。 内部的には com.google.android.gms.actions.SEARCH_ACTION アクションのインテントが発行され、それを myvideoapp が受けられる場合はアプリがこのインテントで起動され、対応していない場合はウェブ検索にフォールバックされます。 myvideoapp というのは 僕が試した限りでは AndroidManifest<application> タグ、 android:label の文字列と一致していれば認識するようで、いわゆるアプリ名と一致している必要がありました。

<application android:label="アプリ名">
    <activity android:name=".SearchActivity">
        <intent-filter>
            <action android:name="com.google.android.gms.actions.SEARCH_ACTION"/>
            <category android:name="android.intent.category.DEFAULT"/>
        </intent-filter>
    </activity>
    ...
</application>

というような定義のアプリケーションがあるとき、 SearchActivity 内で com.google.android.gms.actions.SEARCH_ACTION アクションの Intent から getStringExtra(SearchManager.QUERY) することで Google Voice Actions からの検索アクションを処理できます。 検索結果のActivityが分離されている場合、実装はとても簡単です。

…と、ここまでは良いのですが、アプリ内検索機能はまだ英語版 Googleアシスタントでしか動作しません。 この挙動を試すためには、Googleアシスタントの言語設定(Androidでは端末の言語設定を参照しています)を英語にした上で search for カニ on アプリ名 のようなキーワードを文字入力する必要があります。 幸いAndroidGoogleアシスタントは文字入力を受け付けるので日本語アプリでも呼び出すことができますが、言語設定が英語の場合、日本語アプリ名をもつアプリを起動することはできません。 この仕様は非常に残念ですね…。

しかし、Googleアシスタントが日本語対応した今、「アプリ名 で カニ を検索」のようなキーワードで日本語の端末内アプリ検索がサポートされる日も遠くないでしょう。 きっと他の Google Voice Actions も日本語対応が進んでいくはずです。 その日に備えて、Common Intents や Google Voice Actions の一覧に目を通しておきましょう。

Androidのユーザー補助サービス(Accessibility Service)は楽しい

先日クックパッドで開催された potatotips #42Android のセキュリティよくなってきた話という発表をしました。 Androidのセキュリティに関する改善の歴史と、ユーザー補助サービスによるアプリ権限奪取のデモという内容でしたが、短い時間での発表だったため、ユーザー補助サービスについてあまり十分な説明ができませんでした。

この記事では、ユーザー補助サービスのより詳細な説明や(悪用以外の)活用方法、個人的に問題として考えている部分について説明しようと思います。 発表ではネガティブな面をアピールしてしまいましたが、ユーザー補助サービスを使った開発は非常に奥深く面白いので、その楽しさを少しでも伝えられればと思います。

ユーザー補助サービスとは

ユーザー補助サービス(Accessibility Service)とは、Androidアプリとユーザーの間のやり取りをサポートするためのAndroidの機能です。 ユーザー補助サービスを利用するとアプリの切り替えや画面の更新、通知の変化を読み取って音声などの別の手段でユーザーに通知することができます。 また、逆にユーザーの操作を検出してアプリに他の種類のタッチイベントとして通知させることもできます。 どちらかの単方向ではなく、アプリとユーザーの双方向のやりとりを補助できるのが大きな特徴です。

どのように動作するのか

AccessibilityEventを介してアプリ更新やユーザーの操作を検知することができます。 ユーザー補助サービスを利用するアプリでは取得できるイベントや取得できるパラメータに関して細かく設定できます。 開発者は良心に基づき最小権限で実装することが求められますが、AccessibilityServiceInfoの設定項目が非常に複雑な上、ユーザーのからの許可は「ユーザー補助」として一括で扱われるため正しい設定が難しくなっています。 個人的にインストール済みアプリのユーザー補助サービス権限についてはかなり注意を払っていますが、その権限が動作に必要最小限なのかどうかは正直あまり自信がありません。

権限の話はさておき、ユーザー補助サービスが有効になったアプリではAccessibilityService(というService)を通じて様々なイベントを受け取ることができます。 Viewイベントの場合は発生源のView情報も受け取ることができますが、Viewそのものにアクセスすることはできず、あくまでユーザー補助サービスを通して可能な範囲でのデータ取得となります。

アプリ、ユーザーのイベントを受け取る

僕が特に面白いと思っているのはこの部分です。 最前面のアプリが切り替わった時にそのパッケージを取得したり、表示中画面の文字列に特定の文言が含まれているかを判定したりすることができます。 「あるアプリの情報を他のアプリから知ることができる」という部分の面白さは非常に奥深く、GoogleNowなどもこのような方法で表示中アプリの情報を得ているかもしれない、何かより面白いことができるかもしれないと思うとわくわくします。 ユーザーのタップ情報を検知して対象Viewのサイズや配置などを知ったり、アプリごとの利用時間を測ったり、簡単な実装でも色々変わったことができて非常に面白いです。

ただし、Viewの情報がわかるといってもAccessibilityNodeInfoを経由して取得できる項目は限られており、画像や色、スタイルといった情報は残念ながら取得できません…。

アプリにイベントを伝える

ユーザー補助サービスでは、ユーザーの代わりにView操作を行うことができます。 Viewのクリックや画面のスクロール、文字列の入力など、ユーザーがアプリで操作可能なことは大体行うことができます。 先日のデモではアプリの権限を許可するためのSwitchコンポーネントに対してクリック操作を行うことでユーザー操作を介さずに自アプリに権限を与えていました。 こちらのほうは汎用的に使える機能を実装するのが難しく、正直あまり使ったことがありません。 開発中のアプリの入力画面に対してテストデータの入力を補助できるような、デバッグ補助アプリのようなものを作れるかもしれません。 正直ユーザー補助サービスでやらなくても…という感じですが、別のパッケージで実装できるので本体アプリを操作する必要がないのが利点でしょうか。

利用例

例えば、ユーザー補助サービスを使うと以下のようなアプリを作ることができます。

  • 表示中のアプリのパッケージ名やViewIdから効率的に入力補助を行うパスワード管理アプリ
    • 実際に1passwordなどはこのような動作になっているようです
  • 最前面のアプリを変更するイベントを監視し、アプリの利用時間を計測するアプリ
    • RescueTimeなどはおそらくこういった実装になっています
    • 有名サービスとはいえユーザー補助サービスを許可するのは怖いので自分で実装してみるのも良いかもしれません
  • 画面をタップした際、押されたViewのサイズなどを知ることができるアプリ
    • DroidKaigi 2017 で門田さんが発表していたアプリです
    • ユーザー補助サービス自体はUIを持たないため、画面を通じてユーザーにフィードバックするのが難しくなりがちですね…

このように、アイデア次第で色々なアプリを作ることができるのが魅力です。 普通のアプリよりはるかに広範囲なものを操作できるのも楽しいですね。

問題点と今後の展望

すでに書いたとおり、もっとも大きな問題点は「ユーザー補助サービスの権限があまりに強すぎる」というところだと思います。 アプリやユーザーのイベントを受け取る、アプリの操作を行うという双方向のアクションが可能にも関わらず、設定は一箇所のみで確認も非常に簡素です。 これはユーザー補助サービス本来の目的としては正しいですが、セキュリティモデルとしては自分で開発していても心配になるレベルです。 より細かく権限設定できるようにするか、一部の頻出機能はユーザー補助サービスとは別の機能として切り出したほうが良いと感じています。

実際にAndroid Oでは文字列入力補助のためにAutofill Frameworkが導入され、必要最小限のイベント通知とフィードバックのみが行えるようになります。 このような仕組みが今後も拡張されることで、今後はユーザー補助サービスだけに頼らなくても他のアプリと協調して動作するアプリが安全に作れるようになることを期待しています。

まとめ

ユーザー補助サービス非常に楽しいのでぜひ遊びましょう。 限られたイベントとAccessibilityNodeInfoを通じてアプリと会話する経験をすることで、よりAndroid(OS)の気持ちになってアプリのことを考えることができ(ると思ってい)ます。 また、ユーザー補助サービスの何が危険でどうすれば安全なのかを理解する上でも、実際に自分でイベントの取得やフィードバックを行ってみてできることを確かめてみることが大事だと思います。

ヤドカリをハッキングするゲームが面白い

ここ最近、職場で勧められたヤドカリをハッキングできるゲームをやっている。

www.jp.playstation.com

主人公の名前は変更できなかったので、カニカマではない。

ロボヤドカリをハッキングできるという理由だけでろくに下調べもなく買ってしまったのだが、ハッキングして放置を繰り返すだけでも結構面白くてぐるぐるとマップを回ってしまう。 そろそろスキルも全部取れそうだしサブクエスト的なものもあらかた終わったのでこのゲームに関する感想をまとめておきたい。 なお、僕はアクションゲーム自体が苦手難易度イージーだけでやっているのでゲームのレビューとしては多分訳にたたないです。

ハッキング(オーバーライド)に関して

そもそも敵の種類がそこまで多くないこともあって、ストーリーボス的な一部の機械と最初から混乱状態?みたいな機械以外はすべてハッキング可能。 ハッキングした敵は他の敵に攻撃したりしなかったり、なんだか気まぐれな感じだが、そのうち同士討ちがはじまってどちらかが死ぬまで戦ってくれる。 ハッキングするためには敵と密着する必要があるが、ハッキングの最中には対象の敵は動かなくなるので、1匹ずつバレないようにハッキングしていけば僕のようなアクション下手でも簡単に敵グループを片付けることができる。とってもラクだった。

難点は敵の種類ごとに騎乗できたりできなかったり、好戦的だったりそうでもなかったりしてハッキングの結果に差がある事。 ロボヤドカリはそもそも騎乗できないし、自分に着いてきてくれることもない。これは本当に悲しかった。 エリアを切り替えてもどってくるとハッキングしたヤドカリは大抵いなくなっているので、購入時に描いていたヤドカリランド構想が完全に潰えてしまった。 ただしかなりハッキングしたヤドカリはとても好戦的で、ハッキングが終わってすぐ他のヤドカリに殴りかかったり放電したりして戦闘面では心強い。 戦闘力もめちゃくちゃ高くてヤドカリを2匹程度ハッキングできたらあとは何もしなくても他の敵が皆死んでしまう。

あとは 敵をすべて撃破 系のミッションをうけているとハッキングした機械も自分で破壊する必要があって、最初に強い敵をハッキングしてラクをしようとしていたのに結局相手をしないといけなくなって辛かった。 ハッキングした敵の自爆コマンド実装してほしい。

ゲームシステムに関して

ものを持てる量が限られていて、頻繁にものを売ったり捨てたりしないといけないのはなかなか面倒だった。 特にコイル(武器や防具の改造アイテム)に関しては、特定のレアリティ未満のものは自動的に捨てるオプションが欲しくなる。 持てる量を増やすためには特定の動物の皮とか骨とかが必要なのだが、なぜか皮も骨もレアアイテムになっていて動物を狩っても狩っても出ない場合があり苦痛だった。 終盤になるとお金は余っているのだがアイテムを持てる量が少ない状態になりがちな気がする。 動物系アイテム、かなり高額でもいいから店売りしてほしい…。

あとは時間をユーザー側で操作できないのが非常に辛い。 夜になると本当に暗くなってしまって先に進むのが困難な時もあった。 セーブポイント的なところで翌朝になるまで待機させてほしかった。

あとは収集アイテムの位置などもマップに表示されていて迷うことがほとんどなくて遊びやすかった。 なんというか、このゲームは全体的にすごく遊びやすい。最近は難しいゲームが多いなーと思っていたので、ちゃんと遊べている感があってこれは嬉しかった。

アクションに関して

途中何度もさせられる壁登りアクションが面倒だった。 次の足場がある方向にスティックを倒すだけなのだが、主人公が装備しているARデバイス的なものでは次の足場がどこなのかわからないし、普段黄色く表示されている足場も夜になると全然目立たないので夜の壁登りは本当に苦痛だった。 夜の壁登りは避けようと思っても時間スキップもできないので待つしか無い。辛い。 敵や人間の足跡は夜でもハイライトできるのに黄色で目立つ足場がハイライトできないARデバイスは本当にポンコツすぎる…

壁登り以外は特にアクション面での不満はなかった。難易度もかなり下げられてとても遊びやすい。

まとめ

残念なところはいっぱいあるけれど、ヤドカリをハッキングできるゲームはとても珍しいのでそれだけでも買う価値があります。 特になついてくれないところも甲殻類っぽいと思えばそれはそれで良いかもしれない。

良いかもしれないといったけどそんなリアルさはゲームに求めていないので、メーカーさんはヤドカリに騎乗できる、または自分についてきてくれるDLCをお願いします。 自分がヤドカリになる続編でもいいです。お願いします。