$30 off During Our Annual Pro Sale. View Details »

2020年代の WebView 実装 / saikou_no_webview_2021

2020年代の WebView 実装 / saikou_no_webview_2021

DroidKaigi 2021 で発表
https://www.youtube.com/watch?v=IOnpHyOg5sc

長期的にメンテナンス可能な Android アプリの WebView 画面実装について発表しました。

こやまカニ大好き

October 22, 2021
Tweet

More Decks by こやまカニ大好き

Other Decks in Programming

Transcript

  1. 2020年代の
    WebView 実装
    こやまカニ大好き

    View Slide

  2. 自己紹介
    こやまカニ大好き
    クックパッド モバイル基盤部 所属
    モバイルアプリ開発の生産性を向上す
    るようなタスクに従事
    最近は WebView 大好き

    View Slide

  3. Agenda
    WebView とは
    WebView の基礎知識
    クックパッドアプリとWebView
    2020年代の WebView
    最後に
    01
    02
    03
    04
    05

    View Slide

  4. Agenda
    WebView とは
    WebView の基礎知識
    クックパッドアプリと WebView
    2020年代の WebView
    最後に
    01
    02
    03
    04
    05

    View Slide

  5. WebView とは
    ウェブページをレンダリングするためのView
    通常のViewと比べて以下の利点がある(と思う)
    ● ウェブサービスの場合、低コストでコンテンツを表示可能
    ● 表示するコンテンツの変更をリリースを介さずに行うことができる
    ● 動画や音楽再生などのリッチコンテンツを扱う機能が実装済み

    View Slide

  6. WebView 以外の方法
    ウェブページの表示自体は Chrome な
    どの外部ブラウザに移譲できる
    Android では AndroidX Browser の
    CustomTabs を利用したアプリと親和
    性の高いブラウザ表示も可能

    View Slide

  7. WebView でないとダメなケース
    外部ブラウザや CustomTabs では実現できないケースが存在する
    ● 画面内のコンテンツの一部として WebView を利用している
    ● ウェブコンテンツからネイティブ画面への遷移が多い
    ● ネイティブコードの呼び出しを実行したい

    View Slide

  8. ケース #1
    コンテンツの一部として
    WebView を利用している
    ボトムタブや ViewPager の一部として
    ウェブコンテンツを表示したい場合、
    WebView を利用するしかありません。
    タブの中に表示している

    View Slide

  9. ケース #2
    ウェブコンテンツからネイ
    ティブ画面への遷移
    外部ブラウザ と 自アプリのネイティブ
    画面を行き来するのはActivity同士の
    遷移になるため制限が多く、ユーザー
    体験を損なう
    押下するとログイン画面に遷移する

    View Slide

  10. ケース #3
    ネイティブコードの呼び出し
    カスタムJavaScript や独自スキーム
    のURLフック等を通じてネイティブ実装
    された処理を呼び出したい場合がある
    ● ユーザー情報の更新
    ● GooglePlay決済処理の実行
    決済関連の処理を呼び出す

    View Slide

  11. 例外 : 認証系の処理
    アプリのセッションを引き継いでWebコンテンツを表示したい場合
    以下のような方法で実装する場合は CustomTabs でも実装可能
    ● 認証情報をURLに含める場合
    ● 認証情報をリクエストヘッダに含める場合
    ○ Chrome側の実装により制限あり
    ○ https://developer.chrome.com/docs/android/custom-tabs/headers/

    View Slide

  12. Agenda
    WebView とは
    WebView の基礎知識
    クックパッドアプリと WebView
    2020年代の WebView
    最後に
    01
    02
    03
    04
    05

    View Slide

  13. WebView の歴史(1/3)
    ● 有史以前
    ○ WebKit ベースのシステム組み込み WebView コンポーネント
    ○ OSを更新しなければアップデートされない
    ○ 同じOSバージョンでもメーカーごとに挙動が違ったりする
    ● Android 4.4
    ○ Chromium(Blink) ベースの WebView コンポーネント
    ○ 挙動はかなりまともになった
    ○ OSを更新しなければアップデートされない

    View Slide

  14. WebView の歴史(2/3)
    ● Android 5.0
    ○ WebView コンポーネントが独立してアップデートできるようになった
    ○ 外部ブラウザと分離されたため、履歴やブックマークの同期機能が無くなった
    ○ 基本的に Android 5.0 以降であれば WebView を利用する上で大きな問題はない
    ○ WebView を多用するアプリなら最低でも minSdkVersion 21 にしておいたほうが良い
    ● Android 7.0
    ○ Chrome アプリを利用してレンダリングされるようになった
    ○ Chrome Beta や Dev 版をレンダリングに利用できるようになった

    View Slide

  15. WebView の歴史(3/3)
    ● Android 10
    ○ 再び WebView コンポーネントアプリを利用するようになった
    ○ WebView コンポーネントアプリにも Beta, Dev がそれぞれ存在する

    View Slide

  16. WebView 実装を行う上での注意点
    ● minSdkVersion
    ● API レベルごとの機能差異
    ● WebView のライフサイクル
    ● WebView 起因の障害

    View Slide

  17. minSdkVersion
    ● できれば Android 6.0 (API 23) 以上
    ○ WebView というよりも RuntimePermission の都合
    ○ WebView 内で Permission を扱う処理がある場合、ある程度実装を揃えられる
    ● 最低でも Android 5.0 (API 21) 以上
    ○ すべての端末で WebView の更新が期待できる
    ○ 問題があった場合は Chromium のバグトラッカーを見れば良いので分かり易い
    ○ https://bugs.chromium.org/p/chromium/issues/list

    View Slide

  18. WebView のライフサイクル管理
    ● Fragment/Activity のライフサイクルに沿っていくつかのメソッドを呼び出す必要
    がある
    ○ onPause(), onResume() など
    ● バックグラウンドで動画再生が続いてしまうとリジェクトされる可能性がある

    View Slide

  19. Agenda
    WebView とは
    WebView の基礎知識
    クックパッドアプリと WebView
    2020年代の WebView
    最後に
    01
    02
    03
    04
    05

    View Slide

  20. WebView 誕生編
    クックパッドの Android アプリは 2012
    年リリース
    リリース当初は完全な WebView アプ
    リとして実装されていた(らしい)
    翌年ネイティブアプリとして再実装さ
    れ、当初の WebView は
    CustomWebView として残った

    View Slide

  21. WebView 成長編
    ネイティブアプリ後も引き続きコンテンツのいく
    つかは WebView 表示
    ネイティブ画面が増えたことで画面遷移やカス
    タムJavaScript実装が急増
    2018年時点で
    CustomWebView 841行
    WebViewFragment 1335 行
    WebView 関連コードはおよそ 3000行

    View Slide

  22. WebView 増殖編(1)
    2018年  GooglePlay 定期購入機能の追加
    WebViewで表示するランディングページから
    GooglePlay決済を呼び出す必要があった
    「既存のWebViewは触りたくない」
    「別Activityで表示する決済機能だけのシンプル
    なWebViewを作ろう」
    GooglePlaySubscriptionWebViewA
    ctivity 爆誕

    View Slide

  23. 2019 年  ViewPager 内に表示するWebViewに
    GooglePlay 決済機能を追加
    「既存のWebViewは触りたくない」
    「決済機能だけのシンプルなWebView を作ろう」
    「決済ページ以外は通常WebViewに遷移させよう」
    GooglePlaySubscriptionWebVie
    wContainerFragment 爆誕
    WebView 増殖編(2)

    View Slide

  24. その後も成長と増殖を繰り返した
    WebView
    2021年8月時点で Activity 2つ、
    Fragment が 4つ存在
    WebView 関連コードの総量はおよそ
    5000行(ほぼ全てJava)
    WebView 2021・夏

    View Slide

  25. 見えてきた課題
    ● カオスなカスタム JavaScript
    ● 各 WebView 画面がサポートする機能がわからない
    ● 全部 Activity/Fragment にベタ書きされていて辛い

    View Slide

  26. カオスなカスタム JavaScript
    ● 正しい実装なのかわからない 45 個のメソッド
    ● それぞれの利用頻度や利用ページも一切分からない
    ● 認証情報を一時的に WebViewFragment に持たせる危険な仕組み
    ● JavaScript からほぼ直接 SharedPreference に読み書きさせる処理
    ○ getStorage(key), setStorage(key, value), getStorageKeys() などなど
    ● アプリを終了させる finishApplication() メソッド
    ○ 実際には Activity.finish() を呼ぶ
    ○ 別Activityでの WebView 表示では正しく動いていない

    View Slide

  27. 各WebView画面がサポートする機能がわからない
    インターフェイスの整備が中途半端なため、実装の有無を返せるメソッドと返せないメ
    ソッドがある
    boolean で実装の有無を返せる
    boolean で実装の有無を返せない

    View Slide

  28. 全部 Activity/Fragment にベタ書きされていて辛い
    ● 各インターフェイスが Activity/Fragment に直接実装されている
    ○ 最初の WebViewFragment がその状態で、そこから全部コピペされている
    ○ WebViewFragment の更新にコピペ先が追従しないので差分が生まれていく
    ● ライフサイクル管理、ダイアログなどの共通処理が全てのWebView画面にほぼそ
    のままコピペされている
    ○ 300行くらいは全ての WebView で必要としている共通処理
    ○ 一部は Kotlin 化されていたりして微妙〜に違う

    View Slide

  29. 2010 年代 WebView の限界
    最初の実装から8年維持できたのは本当にすごい
    でもさすがにもうメンテナンスできない
    WebView の機能を整理したい

    View Slide

  30. Agenda
    WebView とは
    WebView の基礎知識
    クックパッドアプリと WebView
    2020年代の WebView
    最後に
    01
    02
    03
    04
    05

    View Slide

  31. 2020年代の WebView
    10年戦える最高の WebView を作りたい
    ● カスタムJavaScriptが理解可能
    ● 他の画面と同じアーキテクチャで実装されている
    ● 共通実装は一カ所にまとまっていてコピペしなくても利用できる
    ● 利用箇所ごとにカスタマイズ可能

    View Slide

  32. カオスなカスタム JavaScript の改善
    ● JavaScriptメソッドにステージの概念を追加
    ○ 利用可能 : 名前の通り利用可能
    ○ 非推奨 : ネイティブ側の実装に何らかの問題があり廃止したい
    ○ 廃止 : JSメソッドは存在するがネイティブ側の実装が削除済み
    ○ ネイティブ処理呼び出しのための callback interface も分割
    ● すべてのメソッドの呼び出しにログ追加
    ● 一定期間呼び出しがないメソッドは降格
    ● 未整理45の状態から利用可能 10、非推奨 2 まで削減できた

    View Slide

  33. クックパッドアプリはVIPERアーキテクチャ
    ● View : 画面処理
    ○ ダイアログ表示、ライフサイクルの管理
    ● Interactor : ビジネスロジック
    ○ セッション引き継ぎ用トークンの取得処理など
    ● Presenter : View とのやりとりや他のコンポーネントの処理呼び出し
    ○ URLチェックやカスタムJavaScriptは Presenter だけ知っていれば良い状態が理想
    ● Routing : 画面遷移
    ○ ネイティブ画面への遷移はここに集約
    他の画面と同じアーキテクチャで実装されている

    View Slide

  34. WebView の VIPER アーキテクチャ
    Fragment
    View
    Presenter
    Interactor
    Routing
    WebView

    View Slide

  35. WebView の VIPER アーキテクチャ
    Fragment
    View
    Presenter
    Interactor
    Routing
    WebView
    WebView は 内部にシステムから用意さ
    れているコールバックを実装する
    それぞれのイベントと対応した
    Presenter のメソッドを呼び出す

    View Slide

  36. WebView の VIPER アーキテクチャ
    Fragment
    View
    Presenter
    Interactor
    Routing
    WebView
    Presenter は呼び出されたメソッドに応じ
    て他のコンポーネントの実装を呼び出す

    View Slide

  37. WebView の VIPER アーキテクチャ
    Fragment
    View
    Presenter
    Interactor
    Routing
    WebView
    Presenter は呼び出されたメソッドに応じ
    て Routing や Interactor の実装を呼び
    出す

    View Slide

  38. WebView の VIPER アーキテクチャ
    Fragment
    View
    Presenter
    Interactor
    Routing
    WebView
    View はダイアログやページ読み込み中
    の Progress の管理を行っている
    WebView の操作が必要な場合は必ず
    View を経由する

    View Slide

  39. WebView の VIPER アーキテクチャ
    Fragment
    View
    Presenter
    Interactor
    Routing
    WebView
    View はダイアログやページ読み込み中
    の Progress の管理を行っている
    WebView の操作が必要な場合は必ず
    View を経由する

    View Slide

  40. WebView の VIPER アーキテクチャ
    Fragment
    View
    Presenter
    Interactor
    Routing
    WebView
    Interactor はセッション引き継ぎが
    必要なURLにアクセスした際の認
    証処理などを実装している

    View Slide

  41. WebView の VIPER アーキテクチャ
    Fragment
    View
    Presenter
    Interactor
    Routing
    WebView
    Routing は主にネイティブ画面への遷移
    を定義している
    「このWebViewではこのネイティブ画面
    への遷移はサポートしない」という定義
    ができるインターフェイスになっている

    View Slide

  42. WebView の VIPER アーキテクチャ
    Fragment
    View
    Presenter
    Interactor
    Routing
    WebView
    Routing は主にネイティブ画面への遷移
    を定義している
    「このWebViewではこのネイティブ画面
    への遷移はサポートしない」という定義
    ができるインターフェイスになっている

    View Slide

  43. WebView の VIPER アーキテクチャ
    Fragment
    View
    Presenter
    Interactor
    Routing
    WebView
    個人的に VIPER が他のモバイル
    アプリアーキテクチャと比べて優れ
    ているところは Routing interface
    に画面遷移をすべて定義している
    ところだと思います。
    これは WebView のような画面遷
    移が複雑化しやすい画面では特に
    有効で、パラメータが異なる画面遷
    移や今回のような実装クラスによっ
    て挙動が大きく変わるような仕様も
    きちんと定義できるのが便利だと
    思っています。
    クックパッドアプリでは Routing ク
    ラスで画面遷移を処理するための
    便利な仕組みをいくつか用意して
    いますが、時間が無いのでまた別
    の機会に話します。

    View Slide

  44. 共通実装がまとまっている
    共通実装が多いのでまとめたい
    ● WebViewのライフサイクル管理
    ● ファイル選択、ダイアログ表示などの基本UI
    ● 特定ドメインのURLにアクセスした際のセッション引き継ぎ処理
    ● どのWebViewでも利用する基本的な画面遷移
    ○ 外部ブラウザや外部メーラーアプリへの遷移など

    View Slide

  45. 利用箇所ごとにカスタマイズ可能
    WebView 画面の増殖は避けられない
    Interface でカスタマイズ可能な箇所を定義して
    クックパッドアプリで画面によってカスタマイズしたい箇所
    ● ログイン、ログアウトなど認証系の処理
    ○ 認証用途で実装している WebView 以外では不要
    ● カスタムJavaScript
    ○ 一部の画面でのみ追加したい JavaScript メソッド
    ● ネイティブ画面への遷移
    ○ 別Activityで WebView を表示する場合はサポートしない、等

    View Slide

  46. ExternalWebView Contract
    カスタマイズ可能な Contract の分離
    Routing
    Interactor
    Presenter
    View
    BaseWebView Contract
    Routing
    Interactor
    Presenter
    View
    継承

    View Slide

  47. ExternalWebView Contract
    カスタマイズ可能な Contract の分離
    Routing
    Interactor
    Presenter
    View
    BaseWebView Contract
    Routing
    Interactor
    Presenter
    View
    継承
    WebView の表示箇所によるカスタマイズを
    許す処理のみ External Contract に定義す

    View Slide

  48. ExternalWebView Contract
    カスタマイズ可能な Contract の分離
    Routing
    Interactor
    Presenter
    View
    BaseWebView Contract
    Routing
    Interactor
    Presenter
    View
    継承
    すべての WebView で共通する処理は
    Base Contract に定義する

    View Slide

  49. ExternalWebView Contract
    カスタマイズ可能な Contract の分離
    Routing
    Interactor
    Presenter
    View
    BaseWebView Contract
    Routing
    Interactor
    Presenter
    View
    継承
    BaseContract のそれぞれのコンポーネント
    が 対応する ExternalContract を継承する
    ことで、 WebView や他のコンポーネントか
    らは BaseContract だけ参照すれば良い状
    態になる

    View Slide

  50. カスタマイズ可能な Contract の分離

    View Slide

  51. カスタマイズ可能な Contract の分離
    ここで ExternalContract.View を継承し
    て View を定義している

    View Slide

  52. GooglePlaySubscription
    WebView VIPER Scene
    カスタマイズ可能な WebView の実装
    BaseWebView VIPER Scene
    CustomWebView
    BaseRouting
    BaseInteractor
    BasePresenter
    BaseView
    WebView VIPER Scene
    Routing
    Interactor
    Presenter
    View
    継承

    View Slide

  53. GooglePlaySubscription
    WebView VIPER Scene
    カスタマイズ可能な WebView の実装
    BaseWebView VIPER Scene
    CustomWebView
    BaseRouting
    BaseInteractor
    BasePresenter
    BaseView
    WebView VIPER Scene
    Routing
    Interactor
    Presenter
    View
    継承
    共通実装
    BaseWebView VIPER シーンのク
    ラスは全て abstract class
    ExternalContract 以外 のメソッドを
    すべて final で実装している

    View Slide

  54. GooglePlaySubscription
    WebView VIPER Scene
    カスタマイズ可能な WebView の実装
    BaseWebView VIPER Scene
    CustomWebView
    BaseRouting
    BaseInteractor
    BasePresenter
    BaseView
    WebView VIPER Scene
    Routing
    Interactor
    Presenter
    View
    継承
    各画面固有の WebView 実装(External
    Contract)
    各画面固有の WebView 以外の実装
    各 WebView 実装ではBaseWebView のコン
    ポーネントをそれぞれ継承し、足りないメソッド
    を実装する

    View Slide

  55. 実際のカスタマイズ例

    View Slide

  56. 実際のカスタマイズ例
    ここで BaseWebViewRouting を継承している
    PsLandingPageWebViewContract.Routing は ActionBar など WebView 以外の
    部分からの画面遷移をサポートするための定義が書かれた interface

    View Slide

  57. 実際のカスタマイズ例
    このWebViewではこの画面遷移は未実装なので、 Unimplemented を返している
    各画面遷移は BaseWebViewPresenter 内のメソッドで UnImplemented かどうか
    判定され、ネイティブ画面に遷移するかウェブページとして読み込むかが変わる

    View Slide

  58. GooglePlaySubscription
    WebView VIPER Scene
    カスタマイズ可能な WebView の実装
    BaseWebView VIPER Scene
    CustomWebView
    BaseRouting
    BaseInteractor
    BasePresenter
    BaseView
    WebView VIPER Scene
    Routing
    Interactor
    Presenter
    View
    継承
    最高の WebView できた

    View Slide

  59. Agenda
    WebView とは
    WebView の基礎知識
    クックパッドアプリと WebView
    2020年代の WebView
    最後に
    01
    02
    03
    04
    05

    View Slide

  60. GooglePlaySubscription
    WebView VIPER Scene
    カスタマイズ可能な WebView の実装
    BaseWebView VIPER Scene
    CustomWebView
    BaseRouting
    BaseInteractor
    BasePresenter
    BaseView
    WebView VIPER Scene
    Routing
    Interactor
    Presenter
    View
    継承
    最高の WebView できた

    View Slide

  61. GooglePlaySubscription
    WebView VIPER Scene
    クックパッドアプリの現状
    BaseWebView VIPER Scene
    CustomWebView
    BaseRouting
    BaseInteractor
    BasePresenter
    BaseView
    WebView VIPER Scene
    Routing
    Interactor
    Presenter
    View
    継承

    View Slide

  62. GooglePlaySubscription
    WebView VIPER Scene
    クックパッドアプリの現状
    BaseWebView VIPER Scene
    CustomWebView
    BaseRouting
    BaseInteractor
    BasePresenter
    BaseView
    WebView VIPER Scene
    Routing
    Interactor
    Presenter
    View
    継承
    WebViewがでかい。でかすぎる
    URL判定ロジックなど Presenter が持つべき実装
    がまだ WebView に残っているせい
    その分 Presenter と View が薄くなっていて明確
    に分離できていない

    View Slide

  63. GooglePlaySubscription
    WebView VIPER Scene
    クックパッドアプリの現状
    BaseWebView VIPER Scene
    CustomWebView
    BaseRouting
    BaseInteractor
    BasePresenter
    BaseView
    WebView VIPER Scene
    Routing
    Interactor
    Presenter
    View
    継承
    特定の WebView 画面の実装だけコード量が異 常
    に多い
    新しいWebViewアーキテクチャでは VIPER シー
    ンを分割すべきところを無理矢理ひとつの
    WebViewFragment に押し込めていた名残

    View Slide

  64. GooglePlaySubscription
    WebView VIPER Scene
    クックパッドアプリの現状
    BaseWebView VIPER Scene
    CustomWebView
    BaseRouting
    BaseInteractor
    BasePresenter
    BaseView
    WebView VIPER Scene
    Routing
    Interactor
    Presenter
    View
    継承
    ExternalWebView Contract を透過的に扱うため
    に BaseWebView Contract が ExternalWebView
    Contract を継承し、さらに abstract class として実
    装する仕組みなっているが、 この構造だと
    ExternalWebView Contract の分離が十分ではな

    理想としては WebView 実装者が
    ExternalWebView Contract だけ意識して実装で
    きるようにしたいが、うまくアーキテクチャに落とし
    込めていない

    View Slide

  65. GooglePlaySubscription
    WebView VIPER Scene
    クックパッドアプリの現状
    BaseWebView VIPER Scene
    CustomWebView
    BaseRouting
    BaseInteractor
    BasePresenter
    BaseView
    WebView VIPER Scene
    Routing
    Interactor
    Presenter
    View
    継承
    最高の WebView
    まだできてない!!!

    View Slide

  66. まとめ
    引き続き 10年戦える最高の WebView を目指して改善していきます
    クックパッドでは WebView 大好きな仲間を募集中
    WebView が好きじゃない仲間でも扱える WebView を用意しているので
    WebView が好きじゃない仲間も募集中です!!!

    View Slide

  67. WebView これだけはやっておけリスト
    ● WebViewでサポートする挙動を決めておく
    ○ ダイアログ、ファイル操作、音声入力などなど
    ● とにかくネイティブ画面への遷移を可視化できるようにする
    ● カスタムJavaScriptはできるだけ使わない方針にする
    ● カスタムJavaScriptを使う場合はメンテナンスできる仕組みを入れる
    ○ 全メソッドの発火ログ記録
    ○ 非推奨メソッド可視化の仕組み
    おまけ

    View Slide

  68. 2020年代 の WebView
    ご静聴ありがとうございました。

    View Slide