Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Features
Speaker Deck
PRO
Sign in
Sign up for free
Search
Search
MRAID広告の実装から見るWebViewとアプリ間のインタラクション実装
Search
k-tomoyasu
September 25, 2025
Programming
0
63
MRAID広告の実装から見るWebViewとアプリ間のインタラクション実装
2025/09/24開催のAfter DroidKaigi 2025 from ピクシブでの発表資料です。
https://pixiv.connpass.com/event/363493/
k-tomoyasu
September 25, 2025
Tweet
Share
More Decks by k-tomoyasu
See All by k-tomoyasu
Coroutinesを中心としたAndroidアプリでの並行数制限・排他制御
fusuma0325
0
130
Android Studioプラグインを作ってみよう 〜Compose for Desktopで始めるプラグイン開発〜
fusuma0325
1
640
Kotlin Multiplatform Projectで社内用APIクライアントを作る
fusuma0325
0
2.1k
Kotlin/Nativeで作ってみるCLI, iOSアプリ
fusuma0325
1
140
Redashアラートの最近 - カスタマイズ機能を作った話
fusuma0325
0
830
Other Decks in Programming
See All in Programming
Le côté obscur des IA génératives
pascallemerrer
0
150
ソフトウェア設計の実践的な考え方
masuda220
PRO
4
630
Pythonに漸進的に型をつける
nealle
1
100
Foundation Modelsを実装日本語学習アプリを作ってみた!
hypebeans
0
120
AI駆動で0→1をやって見えた光と伸びしろ
passion0102
1
740
モテるデスク環境
mozumasu
3
700
Google Opalで使える37のライブラリ
mickey_kubo
3
130
The Past, Present, and Future of Enterprise Java
ivargrimstad
0
530
CSC509 Lecture 08
javiergs
PRO
0
230
エンジニアインターン「Treasure」とHonoの2年、そして未来へ / Our Journey with Hono Two Years at Treasure and Beyond
carta_engineering
0
390
なぜあの開発者はDevRelに伴走し続けるのか / Why Does That Developer Keep Running Alongside DevRel?
nrslib
3
410
AkarengaLT vol.38
hashimoto_kei
1
110
Featured
See All Featured
Practical Orchestrator
shlominoach
190
11k
Thoughts on Productivity
jonyablonski
70
4.9k
The Cost Of JavaScript in 2023
addyosmani
55
9.1k
Scaling GitHub
holman
463
140k
The Language of Interfaces
destraynor
162
25k
Reflections from 52 weeks, 52 projects
jeffersonlam
353
21k
VelocityConf: Rendering Performance Case Studies
addyosmani
332
24k
GitHub's CSS Performance
jonrohan
1032
470k
Templates, Plugins, & Blocks: Oh My! Creating the theme that thinks of everything
marktimemedia
31
2.5k
How To Stay Up To Date on Web Technology
chriscoyier
791
250k
Refactoring Trust on Your Teams (GOTO; Chicago 2020)
rmw
35
3.2k
Fireside Chat
paigeccino
40
3.7k
Transcript
After DroidKaigi 2025 MRAID広告の実装から見るWebView とアプリ間のインタラクション実装 fusuma Comic Division pixivコミックSection プロダクト開発Unit
開発Team
fusuma 2021年中途入社 pixivコミック Androidアプリ開発
What’s MRAID • ネイティブアプリの広告SDKを利用していると目にすることがある ◦ SDKのドキュメントだったり、 Logcatに出力されていたり • Mobile Rich
Media Ad Interface Difinitions (MRAID) ◦ IABが策定したモバイルアプリ内のリッチメディア広告に関する規格 • WebViewで実装された広告とネイティブアプリを連携 ◦ スクロールで広告が画面外となったら広告の音を止める ◦ 広告内の要素をタップしたら広告のサイズを変更する、カレンダーを起動する等
What’s MRAID • JavaScript側でMRAIDのAPIを呼び出すことで広告がインタラクティブな挙動 をする • WebView上のhtml/javascriptだけでは実現できない ◦ 広告が見えなくなったら広告の音声を止める →
アプリの画面上のどの位置に WebView(広告)があるか分からない ◦ WebView(広告)内の要素タップでカレンダー起動 → Intentを投げる必要がある
今日のトピック • ネイティブアプリ <-> WebViewで相互に通信する方法 ◦ ネイティブアプリ -> WebView ◦
WebView -> ネイティブアプリ ▪ addJavascriptInterface方式 ▪ カスタムURLスキーマ方式
ネイティブアプリ → WebView • WebView#evaluateJavascript ◦ webView.evaluateJavascript("{script}", null) で渡したスクリプトが WebView上で実行さ
れる ◦ 第二引数(オプショナル)で実行結果を受け取るコールバックの設定 ◦ API Level 19未満はwebView.loadUrl("javascript:{script}")で実現できる • iOSでもWKWebView#evaluateJavaScriptで同等の機能が提供される
WebView → ネイティブアプリ • addJavascriptInterface方式 • カスタムURLスキーマ方式
addJavascriptInterface方式 • JavaScriptからネイティブアプリ側の処理を実行する代表的な手段 • ネイティブアプリ側の実装をJavaScript側に公開する • WebView#addJavascriptInterface ◦ 第一引数に公開したいメソッドを実装したクラスのインスタンス ◦
第二引数にJavaScript上でのオブジェクト名
addJavascriptInterfaceの実装
None
class WebAppInterface(private val context: Context) { // 公開するメソッドにアノテーションを付与 @JavascriptInterface fun
showToast(message: String) { // ※JavaScriptからの呼び出しはUIスレッドではないことに注意 Toast.makeText(context, message, Toast.LENGTH_SHORT).show() } } val webview = WebView(context) // JavaScriptを有効にする webview.settings.javaScriptEnabled = true // クラスのインスタンスを "Android" という名前で登録 webview.addJavascriptInterface(WebAppInterface(context), "Android") Kotlin
<body> <button onclick="sendToApp('From WebView!')">Show Toast</button> <script> function sendToApp(message) { Android.showToast(message);
} </script> </body> JavaScript
カスタムURLスキーマ方式 • MRAIDの実装でみられるパターン • URL遷移をフックするWebViewClient#shouldOverrideUrlLoadingを活用 • WebViewのURL遷移をネイティブアプリ側の処理のトリガーにする ◦ 実行したい処理をURLで表現してアプリ側に渡す ◦
WebViewClient#shouldOverrideUrlLoadingでURLを検証してネイティブアプリ側の処理を実行
shouldOverrideUrlLoadingを用いた処理の流れ 1. WebView側でmraid://…というスキーマでURL遷移する 2. 遷移時にshouldOverrideUrlLoadingの引数に1.のURLが渡される ここでネイティブアプリ側の処理がトリガー 3. URL検証 ◦ `mraid`というカスタムURLスキーマでMRAIDの処理と判断できる
◦ ホストが`resize` ならresizeを実行する ◦ クエリパラメータからresize時のパラメータを決定 mraid://resize?w=...&h=...&... トリガー コマンド パラメーター
カスタムURLスキーマ方式の実装
<body> <button onclick="sendToApp('From WebView!')">Show Toast</button> <script> function sendToApp(message) { const
encodedMessage = encodeURIComponent(message); // カスタムURLへ遷移させる (遷移がトリガーとなるので<a>タグでも動作する) window.location.href = `myapp://toast?message=${encodedMessage}`; } </script> </body> JavaScript
val webview = WebView(context).also { it.settings.javaScriptEnabled = true } webview.webViewClient
= object : WebViewClient() { override fun shouldOverrideUrlLoading( view: WebView?, request: WebResourceRequest ): Boolean { val url = request.url // スキーマ(myapp)とホスト(toast)をチェック if (url.scheme == "myapp" && url.host == "toast") { // クエリパラメータ"message"を取得 val message = url.getQueryParameter("message") if (!message.isNullOrEmpty()) { Toast.makeText(context, message, Toast.LENGTH_SHORT).show() } return true // WebViewによる読み込みをキャンセル } return false } Kotlin
戻り値の有無 • addJavascriptInterface方式ではJavaScript側はアプリ側のメソッドから直 接戻り値を受け取れる • カスタムURLスキーマ方式では一手間かけて間接的に値を渡す ◦ アプリ側はshouldOverrideUrlLoadingでURLで渡された処理を行い、 evaluateJavascript でJavaScript側に値をセット
実装の容易さ • 総合的にはaddJavascriptInterface • カスタムURLスキーマはAndroid・iOS両方で利用できるのが特徴 ◦ iOSはWKNavigationDelegate#webView(_:decidePolicyFor:decisionHandler:)でURL 遷移をインターセプトする ◦ JavaScript側はどちらのプラットフォームでも同じ
URL遷移を行えばよい ▪ (JavaScript側では) プラットフォームごとの実装が減る
セキュリティ • WebViewからアプリのコードを実行する穴を開けてる点は共通 ◦ いずれも読み込むWebページの信頼性が重要 ▪ 自身で管理するWebページ以外で利用しないことが推奨される ▪ 意図しないWebページからアプリのコードを実行されるリスクを回避
MessageChannel
MessageChannel • API Level 23以降から利用できる ◦ addJavascriptInterface・shouldOverrideUrlLoadingはAPI Level 1から利用可能 •
HTML5のmessage portsのWebView実装 ◦ https://developer.android.com/reference/android/webkit/WebMessagePort • ネイティブアプリ側と JavaScript側で相互にメッセージを送り合える • 送信するoriginを制限することで意図しない Webページからアプリのコードが実行されるリスクを緩 和できる • Webの標準仕様に乗っかれる
val webView = WebView(context) // JavaScriptを有効にする webView.settings.javaScriptEnabled = true val
channel = webview.createWebMessageChannel() val nativePort = channel[0] val webViewPort = channel[1] // JavaScriptからのメッセージを受け取るコールバックを設定する nativePort.setWebMessageCallback(object : WebMessagePort.WebMessageCallback() { override fun onMessage(port: WebMessagePort, message: WebMessage?) { val messageText = message?.data ?: return Toast.makeText(context, messageText, Toast.LENGTH_SHORT).show() } }) webView.webViewClient = object : WebViewClient() { // WebViewがページを読み込み終わったら portをJavaScriptに送る override fun onPageFinished(view: WebView, url: String?) { // 第二引数のtargetOriginで送信先のoriginを制限することでセキュリティリスクを緩和 // とりあえず動かすだけなら Uri.parse(“*”)などを指定する。 view.postWebMessage( WebMessage("port_setup", arrayOf(webViewPort)), Uri.parse("https://foo.example.com") ) } Kotlin
<body> <button onclick="sendToWebView('From WebView!')">Show Toast</button> <script> let appPort; // 送られたportを受け取る
window.addEventListener('message', event => { if (event.data === 'port_setup') { appPort = event.ports[0]; appPort.onmessage = (event) => { // ネイティブから送られたメッセージを受け取って処理 }; } }); function sendToApp(message) { // アプリ側にメッセージ送信 appPort.postMessage(message); } </script> </body> JavaScript
まとめ • WebViewにはネイティブアプリと相互に通信する方法がある • WebView -> ネイティブアプリ方向の通信は複数の手法がある ◦ 代表的なaddJavaScriptInterfaece、MRAID実装でみられるカスタム URLスキーマ、API
Level23以降で使えるMessageChannel ◦ 多くの場合は自身の Webページしか読み込まないようにして addJavaScriptInterfaceを使う のが簡単 • WebViewの可能性が広がる。が、採用は慎重に
参考 • Android Developers ◦ WebViewでwebアプリを開発する ▪ https://developer.android.com/develop/ui/views/layout/webapps/webview ◦ ネイティブブリッジのリスク
▪ https://developer.android.com/privacy-and-security/risks/insecure-webview-native-brid ges • Appnexus ◦ MRAID実装 ▪ https://github.com/appnexus/mobile-sdk-android/