Save 37% off PRO during our Black Friday Sale! »

2020年代の WebView 実装 / saikou_no_webview_2021

2020年代の WebView 実装 / saikou_no_webview_2021

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

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

B61a8b5e5ce71ea4796afaea614bbb19?s=128

こやまカニ大好き

October 22, 2021
Tweet

Transcript

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

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

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

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

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

  6. WebView 以外の方法 ウェブページの表示自体は Chrome な どの外部ブラウザに移譲できる Android では AndroidX Browser

    の CustomTabs を利用したアプリと親和 性の高いブラウザ表示も可能
  7. WebView でないとダメなケース 外部ブラウザや CustomTabs では実現できないケースが存在する • 画面内のコンテンツの一部として WebView を利用している •

    ウェブコンテンツからネイティブ画面への遷移が多い • ネイティブコードの呼び出しを実行したい
  8. ケース #1 コンテンツの一部として WebView を利用している ボトムタブや ViewPager の一部として ウェブコンテンツを表示したい場合、 WebView

    を利用するしかありません。 タブの中に表示している
  9. ケース #2 ウェブコンテンツからネイ ティブ画面への遷移 外部ブラウザ と 自アプリのネイティブ 画面を行き来するのはActivity同士の 遷移になるため制限が多く、ユーザー 体験を損なう

    押下するとログイン画面に遷移する
  10. ケース #3 ネイティブコードの呼び出し カスタムJavaScript や独自スキーム のURLフック等を通じてネイティブ実装 された処理を呼び出したい場合がある • ユーザー情報の更新 •

    GooglePlay決済処理の実行 決済関連の処理を呼び出す
  11. 例外 : 認証系の処理 アプリのセッションを引き継いでWebコンテンツを表示したい場合 以下のような方法で実装する場合は CustomTabs でも実装可能 • 認証情報をURLに含める場合 •

    認証情報をリクエストヘッダに含める場合 ◦ Chrome側の実装により制限あり ◦ https://developer.chrome.com/docs/android/custom-tabs/headers/
  12. Agenda WebView とは WebView の基礎知識 クックパッドアプリと WebView 2020年代の WebView 最後に

    01 02 03 04 05
  13. WebView の歴史(1/3) • 有史以前 ◦ WebKit ベースのシステム組み込み WebView コンポーネント ◦

    OSを更新しなければアップデートされない ◦ 同じOSバージョンでもメーカーごとに挙動が違ったりする • Android 4.4 ◦ Chromium(Blink) ベースの WebView コンポーネント ◦ 挙動はかなりまともになった ◦ OSを更新しなければアップデートされない
  14. WebView の歴史(2/3) • Android 5.0 ◦ WebView コンポーネントが独立してアップデートできるようになった ◦ 外部ブラウザと分離されたため、履歴やブックマークの同期機能が無くなった

    ◦ 基本的に Android 5.0 以降であれば WebView を利用する上で大きな問題はない ◦ WebView を多用するアプリなら最低でも minSdkVersion 21 にしておいたほうが良い • Android 7.0 ◦ Chrome アプリを利用してレンダリングされるようになった ◦ Chrome Beta や Dev 版をレンダリングに利用できるようになった
  15. WebView の歴史(3/3) • Android 10 ◦ 再び WebView コンポーネントアプリを利用するようになった ◦

    WebView コンポーネントアプリにも Beta, Dev がそれぞれ存在する
  16. WebView 実装を行う上での注意点 • minSdkVersion • API レベルごとの機能差異 • WebView のライフサイクル

    • WebView 起因の障害
  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
  18. WebView のライフサイクル管理 • Fragment/Activity のライフサイクルに沿っていくつかのメソッドを呼び出す必要 がある ◦ onPause(), onResume() など

    • バックグラウンドで動画再生が続いてしまうとリジェクトされる可能性がある
  19. Agenda WebView とは WebView の基礎知識 クックパッドアプリと WebView 2020年代の WebView 最後に

    01 02 03 04 05
  20. WebView 誕生編 クックパッドの Android アプリは 2012 年リリース リリース当初は完全な WebView アプ

    リとして実装されていた(らしい) 翌年ネイティブアプリとして再実装さ れ、当初の WebView は CustomWebView として残った
  21. WebView 成長編 ネイティブアプリ後も引き続きコンテンツのいく つかは WebView 表示 ネイティブ画面が増えたことで画面遷移やカス タムJavaScript実装が急増 2018年時点で CustomWebView

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

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

    GooglePlaySubscriptionWebVie wContainerFragment 爆誕 WebView 増殖編(2)
  24. その後も成長と増殖を繰り返した WebView 2021年8月時点で Activity 2つ、 Fragment が 4つ存在 WebView 関連コードの総量はおよそ

    5000行(ほぼ全てJava) WebView 2021・夏
  25. 見えてきた課題 • カオスなカスタム JavaScript • 各 WebView 画面がサポートする機能がわからない • 全部

    Activity/Fragment にベタ書きされていて辛い
  26. カオスなカスタム JavaScript • 正しい実装なのかわからない 45 個のメソッド • それぞれの利用頻度や利用ページも一切分からない • 認証情報を一時的に

    WebViewFragment に持たせる危険な仕組み • JavaScript からほぼ直接 SharedPreference に読み書きさせる処理 ◦ getStorage(key), setStorage(key, value), getStorageKeys() などなど • アプリを終了させる finishApplication() メソッド ◦ 実際には Activity.finish() を呼ぶ ◦ 別Activityでの WebView 表示では正しく動いていない
  27. 各WebView画面がサポートする機能がわからない インターフェイスの整備が中途半端なため、実装の有無を返せるメソッドと返せないメ ソッドがある boolean で実装の有無を返せる boolean で実装の有無を返せない

  28. 全部 Activity/Fragment にベタ書きされていて辛い • 各インターフェイスが Activity/Fragment に直接実装されている ◦ 最初の WebViewFragment

    がその状態で、そこから全部コピペされている ◦ WebViewFragment の更新にコピペ先が追従しないので差分が生まれていく • ライフサイクル管理、ダイアログなどの共通処理が全てのWebView画面にほぼそ のままコピペされている ◦ 300行くらいは全ての WebView で必要としている共通処理 ◦ 一部は Kotlin 化されていたりして微妙〜に違う
  29. 2010 年代 WebView の限界 最初の実装から8年維持できたのは本当にすごい でもさすがにもうメンテナンスできない WebView の機能を整理したい

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

    01 02 03 04 05
  31. 2020年代の WebView 10年戦える最高の WebView を作りたい • カスタムJavaScriptが理解可能 • 他の画面と同じアーキテクチャで実装されている •

    共通実装は一カ所にまとまっていてコピペしなくても利用できる • 利用箇所ごとにカスタマイズ可能
  32. カオスなカスタム JavaScript の改善 • JavaScriptメソッドにステージの概念を追加 ◦ 利用可能 : 名前の通り利用可能 ◦

    非推奨 : ネイティブ側の実装に何らかの問題があり廃止したい ◦ 廃止 : JSメソッドは存在するがネイティブ側の実装が削除済み ◦ ネイティブ処理呼び出しのための callback interface も分割 • すべてのメソッドの呼び出しにログ追加 • 一定期間呼び出しがないメソッドは降格 • 未整理45の状態から利用可能 10、非推奨 2 まで削減できた
  33. クックパッドアプリはVIPERアーキテクチャ • View : 画面処理 ◦ ダイアログ表示、ライフサイクルの管理 • Interactor :

    ビジネスロジック ◦ セッション引き継ぎ用トークンの取得処理など • Presenter : View とのやりとりや他のコンポーネントの処理呼び出し ◦ URLチェックやカスタムJavaScriptは Presenter だけ知っていれば良い状態が理想 • Routing : 画面遷移 ◦ ネイティブ画面への遷移はここに集約 他の画面と同じアーキテクチャで実装されている
  34. WebView の VIPER アーキテクチャ Fragment View Presenter Interactor Routing WebView

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

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

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

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

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

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

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

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

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

    個人的に VIPER が他のモバイル アプリアーキテクチャと比べて優れ ているところは Routing interface に画面遷移をすべて定義している ところだと思います。 これは WebView のような画面遷 移が複雑化しやすい画面では特に 有効で、パラメータが異なる画面遷 移や今回のような実装クラスによっ て挙動が大きく変わるような仕様も きちんと定義できるのが便利だと 思っています。 クックパッドアプリでは Routing ク ラスで画面遷移を処理するための 便利な仕組みをいくつか用意して いますが、時間が無いのでまた別 の機会に話します。
  44. 共通実装がまとまっている 共通実装が多いのでまとめたい • WebViewのライフサイクル管理 • ファイル選択、ダイアログ表示などの基本UI • 特定ドメインのURLにアクセスした際のセッション引き継ぎ処理 • どのWebViewでも利用する基本的な画面遷移

    ◦ 外部ブラウザや外部メーラーアプリへの遷移など
  45. 利用箇所ごとにカスタマイズ可能 WebView 画面の増殖は避けられない Interface でカスタマイズ可能な箇所を定義して クックパッドアプリで画面によってカスタマイズしたい箇所 • ログイン、ログアウトなど認証系の処理 ◦ 認証用途で実装している

    WebView 以外では不要 • カスタムJavaScript ◦ 一部の画面でのみ追加したい JavaScript メソッド • ネイティブ画面への遷移 ◦ 別Activityで WebView を表示する場合はサポートしない、等
  46. ExternalWebView Contract カスタマイズ可能な Contract の分離 Routing Interactor Presenter View BaseWebView

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

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

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

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

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

  52. GooglePlaySubscription WebView VIPER Scene カスタマイズ可能な WebView の実装 BaseWebView VIPER Scene

    CustomWebView BaseRouting BaseInteractor BasePresenter BaseView WebView VIPER Scene Routing Interactor Presenter View 継承
  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 で実装している
  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 のコン ポーネントをそれぞれ継承し、足りないメソッド を実装する
  55. 実際のカスタマイズ例

  56. 実際のカスタマイズ例 ここで BaseWebViewRouting を継承している PsLandingPageWebViewContract.Routing は ActionBar など WebView 以外の

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

  58. GooglePlaySubscription WebView VIPER Scene カスタマイズ可能な WebView の実装 BaseWebView VIPER Scene

    CustomWebView BaseRouting BaseInteractor BasePresenter BaseView WebView VIPER Scene Routing Interactor Presenter View 継承 最高の WebView できた
  59. Agenda WebView とは WebView の基礎知識 クックパッドアプリと WebView 2020年代の WebView 最後に

    01 02 03 04 05
  60. GooglePlaySubscription WebView VIPER Scene カスタマイズ可能な WebView の実装 BaseWebView VIPER Scene

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

    BaseInteractor BasePresenter BaseView WebView VIPER Scene Routing Interactor Presenter View 継承
  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 が薄くなっていて明確 に分離できていない
  63. GooglePlaySubscription WebView VIPER Scene クックパッドアプリの現状 BaseWebView VIPER Scene CustomWebView BaseRouting

    BaseInteractor BasePresenter BaseView WebView VIPER Scene Routing Interactor Presenter View 継承 特定の WebView 画面の実装だけコード量が異 常 に多い 新しいWebViewアーキテクチャでは VIPER シー ンを分割すべきところを無理矢理ひとつの WebViewFragment に押し込めていた名残
  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 だけ意識して実装で きるようにしたいが、うまくアーキテクチャに落とし 込めていない
  65. GooglePlaySubscription WebView VIPER Scene クックパッドアプリの現状 BaseWebView VIPER Scene CustomWebView BaseRouting

    BaseInteractor BasePresenter BaseView WebView VIPER Scene Routing Interactor Presenter View 継承 最高の WebView まだできてない!!!
  66. まとめ 引き続き 10年戦える最高の WebView を目指して改善していきます クックパッドでは WebView 大好きな仲間を募集中 WebView が好きじゃない仲間でも扱える

    WebView を用意しているので WebView が好きじゃない仲間も募集中です!!!
  67. WebView これだけはやっておけリスト • WebViewでサポートする挙動を決めておく ◦ ダイアログ、ファイル操作、音声入力などなど • とにかくネイティブ画面への遷移を可視化できるようにする • カスタムJavaScriptはできるだけ使わない方針にする

    • カスタムJavaScriptを使う場合はメンテナンスできる仕組みを入れる ◦ 全メソッドの発火ログ記録 ◦ 非推奨メソッド可視化の仕組み おまけ
  68. 2020年代 の WebView ご静聴ありがとうございました。