WebViewとアプリ内/外ブラウザ起動
本記事では、以下gifの挙動の違いを解説しています。
iOS | Android |
---|---|
はじめに
Flutterアプリ(ネイテイブアプリ)でWebサイトを表示したい場合に、ご存知の通りWebViewやブラウザ起動がありますが、各OSでのそれぞれの挙動の違いや特性について改めて整理しました。Flutter公式プラグインである以下2つを本記事では取り扱っています。
※WebView表示のプラグインとして flutter_inappwebview | Flutter Package も有名で私も使っていた時期がありますが、HTTPS通信時のホスト名検証がない点や, 結構な数のイシューが放置されたままになっているなどの理由から、現在は webview_flutter
を利用しています。
Webサイトを表示する3パターンの方法
ネイテイブアプリでWebサイトを表示する方法として主に下記の3つが上げられます(呼び名はイメージしやすいように便宜上定めています)。
-
WebView埋め込み型
- FlutterのPlatformViewを使ってネイテイブのWebViewクラスを埋め込みで表示
- 必ずしもフルスクリーンである必要はなく自由な幅・高さで表示ができる
- UserAgentの指定やURLのホワイトリスと対応などカスタマイズ性が高い
-
アプリ内ブラウザ
- Safariアプリをアプリから離脱せずに表示し、Safariアプリと同等のブラウジング操作ができる
- FlutterのViewに対してモーダル遷移で表示される(見た目的には画面遷移のような形)
- 基本的には表示のみでWebView埋め込み型ほどカスタマイズ性能は無い
- Androidの場合はWebView埋込み型がフルスクリーン形式で表示されている
-
外部アプリケーション起動
- 端末のデフォルトブラウザで起動する(アプリから一旦離脱する)
- Universal Links / App Linksに対応している場合はアプリケーションが起動する
ネイテイブレベルで分解すると以下の表に分類できます。
画面 | WebView埋め込み型 | アプリ内ブラウザ | 外部アプリケーション起動 |
---|---|---|---|
iOS | WKWebView | SFSafariViewController | 端末のデフォルトブラウザ(大半はSaferiアプリ)もしくはアプリ |
Android | WebView | 同左 | 端末のデフォルトブラウザもしくはアプリ |
以降、実際にFlutterプラグインで実装する様子をサラッと見ていきます。
WebView埋め込み型
webview_flutter を使うことで「WebView埋め込み型」が実現できます。実装自体は非常に簡単です。
final controller = Completer<WebViewController>();
WebView(
initialUrl: url,
onWebViewCreated: controller.complete,
navigationDelegate: (request) {
// requestに応じてアクセスをブロックできるなどカスタマイズ性が高い
},
),
On iOS the WebView widget is backed by a WKWebView; On Android the WebView widget is backed by a WebView.
内部的には、iOSではWKWebView
, AndroidではWebView
を利用して、FlutterのPlatformView(AndroidだとAndroidView
、iOSだとUiKitView
)を使ってネイテイブビューを描画しています。
また、特にAndroidでのPlatformViewでは、Hybrid composition
orVirtual display
が利用され、これらは毎フレームごとにメモリコピーが行われることでFlutterのUI全体のパフォーマンスに影響を与える可能性があるとドキュメントに記載されておりましたが、冒頭でお見せしたgifで分かるようにWebViewで表示する程度ではその違和感は感じませんでした。
アプリ内ブラウザ、外部アプリケーション起動
url_launcher を使うことで「アプリ内ブラウザ」「外部アプリケーション起動」が実現できます。こちらも実装は非常に簡単です。
launchUrl(Uri.https('flutter.dev', ''));
// ref. https://github.com/flutter/plugins/blob/ca63d964d9fdfff9be36533ea4248d0d0ca28fd0/packages/url_launcher/url_launcher/lib/src/types.dart#L12
enum LaunchMode {
/// Leaves the decision of how to launch the URL to the platform
/// implementation.
platformDefault,
/// Loads the URL in an in-app web view (e.g., Safari View Controller).
inAppWebView,
/// Passes the URL to the OS to be handled by another application.
externalApplication,
/// Passes the URL to the OS to be handled by another non-browser application.
externalNonBrowserApplication,
}
バージョン6.1.0
以前はforceSafariVC
やforceWebView
でプラットフォームごとに個別でプロパティを設定する必要がありましたが、これらがLaunchMode
という形で統一されました。LaunchMode.platformDefault
はプラットフォームごとに下記の挙動となっています。
- [LaunchMode.platformDefault] is supported on all platforms:
/// - On iOS and Android, this treats web URLs as
/// [LaunchMode.inAppWebView], and all other URLs as
/// [LaunchMode.externalApplication].
/// - On Windows, macOS, and Linux this behaves like
/// [LaunchMode.externalApplication].
/// - On web, this useswebOnlyWindowName
for web URLs, and behaves like
/// [LaunchMode.externalApplication] for any other content.
ひとまずiOSとAndroidに注目すると、
- Web URLの場合: アプリ内ブラウザ(
inAppWebView
) - それ以外の場合(ディープリンク等): 外部ブラウザ or アプリ(
externalApplication
)
といった挙動によしなに切り替えてくれるので特別要件がなければplatformDefault
(未指定)で良さそうですね。
アプリ内ブラウザではなく問答無用で外部ブラウザ起動させたい場合は、externalApplication
を明示すると期待通りの動きになります。
(ちなみにexternalNonBrowserApplication
をした状態で通常のWeb URLを指定すると、iOSでは何も起きず、Androidでは外部ブラウザが起動する挙動を確認しましたが、普通にexternalApplication
指定で良い気がします)。
launchUrl(
uri,
mode: LaunchMode.externalApplication,
// iOS: Universal Links以外のURLを指定すると何も起こらない
// Android: App Links以外のURLを指定しても外部ブラウザ起動し表示される
// mode: LaunchMode.externalNonBrowserApplication,
);
6.1.0以前の場合
※執筆途中でタイムリーに更新が入ったために、途中まで記載していた内容です。
iOSではforceSafariVC
、AndroidではforceWebView
とそれぞれ異なるプロパティを指定することで、アプリ内で表示するか外部ブラウザ起動にするか選択できます。デフォルト値が逆転している点が注意です。
画面 | プロパティ | デフォルト値 |
---|---|---|
iOS | forceSafariVC | true(アプリ内ブラウザ起動) |
Android | forceWebView | false(外部ブラウザ起動) |
※iOS用にuniversalLinksOnly
というプロパティがありますが、url_launcuer
側で「ユニバーサルリンクの場合はアプリを起動し、それ以外はブラウザで開く」挙動を実現できているため、利用機会はほぼ無いと認識しています(ちなみに、universalLinksOnly: true
とした場合にユニバーサルリンクではないページにアクセスしようとするとPlatformExceptionとなります)。
launch(
url,
forceSafariVC: false, // default: false
forceWebView: true, // default: true
);
アプリ内ブラウザのネイテイブの挙動を見る
iOSでは内部でSafari View Controllerが使われています。iOS 9,10ではSafariアプリとCookie等のデータが共有されていたそうですが、以降はアプリごとに独立しており、特段Webサイトをハックすることもできなさそうで、セキュリティ面でのケアは不要そうです。
The view controller includes Safari features such as Reader, AutoFill, Fraudulent Website Detection, and content blocking. In iOS 9 and 10, it shares cookies and other website data with Safari. The user's activity and interaction with SFSafariViewController are not visible to your app, which cannot access AutoFill data, browsing history, or website data. You do not need to secure data between your app and Safari.
https://developer.apple.com/documentation/safariservices/sfsafariviewcontroller
Androidのアプリ内ブラウザはWebViewのフルスクリーン表示
一方で、Androidの場合はアプリ内ブラウザ(=WebViewのフルスクリーン表示)の形となっており、表示するだけであれば良いですが、何かしらのセキュリティ面でのケアが必要な場合は対応できません。WebView表示をするのであれば前述のwebview_flutter
の方がカスタマイズ性能が高く、細かいハンドリングができるので、その辺りは要件に併せて臨機応変に選定できると良いと思います。
参考
- FlutterのPlatformViewを理解する
- [Flutter] Platform Viewsの仕組みとパフォーマンス影響を理解して利用する - Qiita
- Appにおけるウェブビューを実現するには、WKWebViewとSFSafariViewControllerのどちらを使うべきですか - 見つける - Apple Developer
- SFSafariViewController | Apple Developer Documentation
Author And Source
この問題について(WebViewとアプリ内/外ブラウザ起動), 我々は、より多くの情報をここで見つけました https://zenn.dev/tsuruo/articles/56f3abbb132f90著者帰属:元の著者の情報は、元のURLに含まれています。著作権は原作者に属する。
Collection and Share based on the CC protocol