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
Sponsored
·
SiteGround - Reliable hosting with speed, security, and support you can count on.
→
k-tomoyasu
September 25, 2025
Programming
0
74
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
220
Android Studioプラグインを作ってみよう 〜Compose for Desktopで始めるプラグイン開発〜
fusuma0325
1
750
Kotlin Multiplatform Projectで社内用APIクライアントを作る
fusuma0325
0
2.1k
Kotlin/Nativeで作ってみるCLI, iOSアプリ
fusuma0325
1
140
Redashアラートの最近 - カスタマイズ機能を作った話
fusuma0325
0
840
Other Decks in Programming
See All in Programming
CSC307 Lecture 01
javiergs
PRO
0
690
登壇資料を作る時に意識していること #登壇資料_findy
konifar
4
1k
AI & Enginnering
codelynx
0
110
それ、本当に安全? ファイルアップロードで見落としがちなセキュリティリスクと対策
penpeen
7
3.9k
Rust 製のコードエディタ “Zed” を使ってみた
nearme_tech
PRO
0
160
Grafana:建立系統全知視角的捷徑
blueswen
0
330
CSC307 Lecture 05
javiergs
PRO
0
500
プロダクトオーナーから見たSOC2 _SOC2ゆるミートアップ#2
kekekenta
0
200
AI時代のキャリアプラン「技術の引力」からの脱出と「問い」へのいざない / tech-gravity
minodriven
21
7.1k
ThorVG Viewer In VS Code
nors
0
770
Amazon Bedrockを活用したRAGの品質管理パイプライン構築
tosuri13
4
280
インターン生でもAuth0で認証基盤刷新が出来るのか
taku271
0
190
Featured
See All Featured
Marketing to machines
jonoalderson
1
4.6k
Exploring the Power of Turbo Streams & Action Cable | RailsConf2023
kevinliebholz
37
6.3k
Music & Morning Musume
bryan
47
7.1k
YesSQL, Process and Tooling at Scale
rocio
174
15k
[Rails World 2023 - Day 1 Closing Keynote] - The Magic of Rails
eileencodes
38
2.7k
Mind Mapping
helmedeiros
PRO
0
80
State of Search Keynote: SEO is Dead Long Live SEO
ryanjones
0
110
Typedesign – Prime Four
hannesfritz
42
2.9k
Dealing with People You Can't Stand - Big Design 2015
cassininazir
367
27k
Fireside Chat
paigeccino
41
3.8k
So, you think you're a good person
axbom
PRO
2
1.9k
Dominate Local Search Results - an insider guide to GBP, reviews, and Local SEO
greggifford
PRO
0
77
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/