하이브리드 앱에서 Android/iOS 네이티브 영역과 웹뷰 내의 영역 모두에서 밸류포션의 기능을 사용하는 경우를 위한 가이드입니다.
하이브리드 앱을 위해 밸류포션 SDK 를 연동할 때, 이 안내 문서가 없다면 자연스레 아래의 세 가지 방법 중 하나를 택하게 될 것입니다.
- 밸류포션 대시보드에서 앱을 1개만 생성하고, 하나의 clientId 를 Android/iOS 네이티브 SDK 와 웹용 SDK 양쪽에 공통적으로 사용한다
- 밸류포션 대시보드에서 앱을 2개 생성해서, 하나의 clientId 는 Android/iOS 네이티브 SDK 를 초기화하는데 사용하고, 다른 하나의 clientId 는 웹용 SDK 를 초기화하는데 사용한다
- 밸류포션 대시보드에서 앱을 1개만 생성하고, 앱 내에서 Android/iOS 네이티브 SDK 는 사용하지 않고, 웹뷰 내에서 웹용 SDK 만 사용한다
- 1 의 방법을 사용했을 때는, Android/iOS SDK 과 웹용 SDK 가 유저를 식별하는데 사용하는 방법이 다른 관계로 네이티브 영역의 지표와 웹뷰 내의 지표가 무의미하게 섞여 버립니다.
- 2 는 1 의 문제를 해결하였지만, 결국엔 대시보드에서 두 앱의 지표를 모두 확인해야 하는 번거로움이 있습니다.
- 3 은 광고 클릭이 발생했는데 외부 브라우저를 띄워주지 못하고 웹뷰 내에서 페이지 전환이 발생하는 문제가 있습니다.
그래서 이 문서에서 안내하는 방법을 사용하면 웹뷰 내에서 발생하는 이벤트들이 브릿지를 통해 Android/iOS 네이티브 SDK 에 전달되며, 결국 Android/iOS 네이티브 SDK 를 통해서만 모든 이벤트가 밸류포션 서버에 저장되기 때문에 네이티브 영역과 웹뷰 영역의 이벤트가 의미있게 통합되어 지표를 확인하실 수 있고, 광고도 문제 없이 동작합니다.
하이브리드 앱의 경우 밸류포션에서 단 1개의 앱만 생성하신 후에, 그 Client Id, Secret Key 를 Android/iOS 네이티브 SDK 를 연동하시는데 사용하시면 됩니다.
웹뷰 쪽의 스크립트를 위해 앱(clientId)을 별도로 생성하실 필요가 없습니다.
Android/iOS 네이티브 SDK 의 기본 연동을 해 놓으신 후에 아래 추가 코드를 넣어주셔야 합니다.
- Android SDK 1.1.4 혹은 그보다 높은 버전
- iOS SDK 1.1.28 혹은 그보다 높은 버전의 framework library
- 웹 SDK v11 혹은 그보다 높은 버전 (vp-latest)
Android 네이티브
import com.valuepotion.sdk.HybridBridge;
....
public void initWebView() {
webView = (WebView) findViewById(R.id.webview);
webView.getSettings().setJavaScriptEnabled(true);
webView.setWebViewClient(new HybridBridge(activity, webView).getWebViewClient());
webView.loadUrl("...");
}
위와 같이 웹뷰 객체를 얻어온 후에 HybridBridge
객체를 생성하여 웹뷰에 세팅을 해줍니다.
이는 웹뷰에 페이지를 로드하기 전에 한번 선행되어야 합니다.
만약에 이미 웹뷰에 setWebViewClient
메소드를 통해 직접 세팅해 놓은 WebViewClient
가 있다면 아래와 같이 합니다.
public void initWebView() {
webView = (WebView) findViewById(R.id.webview);
webView.getSettings().setJavaScriptEnabled(true);
final HybridBridge hybridBridge = new HybridBridge(activity, webView);
webView.setWebViewClient(new WebViewClient() {
@Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
if (hybridBridge.shouldOverrideUrlLoading(url)) {
return true;
}
return super.shouldOverrideUrlLoading(view, url);
}
});
webView.loadUrl("...");
}
iOS 네이티브 (UIWebView 를 사용하는 경우)
// Objective-C
- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType {
// bridge method 에서 해당 request 를 처리했다면, NO 를 리턴합니다.
// 따라서 NO 를 리턴했을 때 이후 처리 또는 페이지 이동을 중지합니다.
if ([[ValuePotion bridge] webView:webView shouldStartLoadWithRequest:request] == NO) {
return NO;
}
// ...
return YES;
}
iOS 네이티브 (WKWebView 를 사용하는 경우)
// Swift
func webView(webView: WKWebView, decidePolicyForNavigationAction navigationAction: WKNavigationAction, decisionHandler: (WKNavigationActionPolicy) -> Void) {
// bridge method 에서 해당 request 를 처리했다면, false 를 리턴합니다.
// 따라서 false 를 리턴했을 때 이후 처리 또는 페이지 이동을 중지합니다.
if (!ValuePotion.bridge().webView(webView, decidePolicyForNavigationAction: navigationAction)) {
decisionHandler(.Cancel)
}
// ...
decisionHandler(.Allow)
}
VP.init({
hybrid: true,
noAd: function (iframeId) {
...
},
adDisplayed: function (iframeId) {
...
},
...
});
원래 웹용 SDK 에서 VP.init()
함수를 호출할 때 clientId
를 넣도록 안내하고 있으나,
하이브리드 앱인 경우는 clientId
를 넣지 않고, 대신 hybrid: true
를 넣어야 합니다.
위와 같이 연동이 다 되었으면, Android/iOS SDK 와 웹 SDK 둘다 기존의 방법 그대로 사용하시면 정상적으로 작동합니다.
앱에서 html 을 직접 생성해서 웹뷰에 로드 시키는 방법을 사용하고 계시다면 아래의 두 가지 경우 중 하나로 예상됩니다.
- 웹뷰를 배너 사이즈만하게 하고, 그 웹뷰 안에 다른 컨텐츠는 없이 밸류포션의 광고만 노출시키려는 경우
- 앱의 웹뷰에 원래부터 앱의 컨텐츠를 html 로 구성해서 제공해오고 있었는데, 그 웹뷰 안에 밸류포션의 광고를 노출시키려는 경우
밸류포션의 스크립트와 광고를 노출하는 코드가 담긴 웹 페이지를 만드시고,
웹서버에 올려서 http://yourservice.com/ad.html
등과 같이 실제로 존재하는 url 을 만들어주세요.
그리고 웹뷰에는 html string 을 로드하거나, local file 을 localhost:// 등으로 로드하지 말고, 저 실제 웹 URL 을 로드해주세요.
광고가 노출되는 영역이 실제로 존재하는 웹페이지가 아닌 경우에 광고를 내려주지 않는 정책을 가진 DSP 들이 있어서, 보다 좋은 광고 효율을 위해 꼭 필요한 작업입니다.
바로 위의 경우와 비슷합니다. 실제 컨텐츠는 html string 이 웹뷰에 바로 로드되었더라도,
광고 노출 만큼은 iframe 안에 실제 URL 을 로드해서 그 영역에서 작동하도록 해야 합니다.
그리고 그에 따른 부가적인 작업들이 필요합니다.
앱의 웹뷰에서 로딩되는 file://....html 파일을 (A) 라고 지칭하겠습니다.
기존엔 (A) 에서 바로 광고를 노출했지만, 광고 노출을 위한 중간 페이지인 (B) 를 새로 만들어주세요.
(B) 는 예를 들면 http://yourservice.com/ad.html 과 같은 URL 을 가질거고, 내용은 대략
<html>
<head>
<meta name="viewport" content="width=device-width, user-scalable=no">
</head>
<body style="margin:0;padding:0;">
<div id="vp_wrapper"></div>
<p>hello</p>
<script type="text/javascript" src="https://campaigns-assets-vp.daumcdn.net/websdk/vp-latest.min.js"></script>
<script type="text/javascript">
VP.init({clientId: "your_own_client_id"});
var opts = {
callback: function (filled) {
console.log("ad callback : " + filled);
}
};
VP.ad("vp_wrapper", "test_placement", 320, 100, opts);
</script>
</body>
</html>
위와 같을 겁니다. 위의 placement, width, height 는 제가 임의로 넣은 것이므로 원래 사용하시는 값으로 수정해서 사용해주세요.
이제 (A) 에서 광고를 노출하기 위해 만들어 놓았던 div 에 iframe 을 넣고, 그 iframe 이 (B) 의 url 을 가리키도록 해주세요.
그러면 (B) 에서 밸류포션의 광고 혹은 외부 광고가 정상적으로 노출되게 됩니다.
(이 때 (B) 의 url 에 사용되는 도메인을 저희에게 알려주셔야 저희가 미리 외부 광고 제공업체에 해당 도메인을 등록해서 광고가 노출되도록 설정할 수 있습니다)
원래 VP.ad() 함수에 callback 을 넣으면 filled 라는 파라메터를 통해 성공 혹은 실패 여부를 알 수 있습니다.
하지만 위의 2단계까지만 진행하면 이 callback 이 작동하지 않게 됩니다.
이유는, (B) 아래쪽에 있는 iframe 들에서 광고 노출 여부에 관한 정보를 postMessage 함수를 통해 위쪽으로 올려보낼 때
가장 top frame 으로 올려보내고 있는데,
정상적인 케이스라면 가장 top frame 이 (B) 이기 때문에 잘 받아서 처리를 하겠지만,
지금은 그보다 더 위의 단계인 (A) 가 생겨버려서 (A) 로 그 메시지가 올라가버리고 유실되는 상황입니다.
그래서 해야 할 일은, (A) 에서 받은 메시지를 다시 (B) 로 되돌려주는 작업을 해주셔야 합니다.
(A) 에 다음과 같은 스크립트를 넣어주세요.
window.addEventListener('message', function (msg) {
var iframe = document.querySelector("?????");
iframe.contentWindow.postMessage(msg.data, "*");
});
위 스크립트의 document.querySelector("?????") 에서 적절한 selector 구문을 넣어주셔서 (B) 를 로딩하는 iframe 객체를 얻을 수 있게 해주세요.
여기까지 하시면 VP.ad() 의 callback 이 정상적으로 작동합니다.
VP.ad() 의 callback 은 정상적으로 작동하도록 고쳐놨지만, 정작 광고가 없는 경우에 (A) 에서 iframe 을 붙히면서 잡아놨던 영역은 여전히 하얗게 텅비게 될텐데요.
그걸 해결하기 위해서 (B) 에서 VP.ad() 를 호출하고 그 callback 에서 no ad 일 때는 (A) 에게 'iframe 을 없애' 라는 메시지를 전달해야 합니다.
var opts = {
callback: function (filled) {
console.log("ad callback : " + filled);
if (!filled) {
window.top.postMessage({
action: "vp_ad_callback",
filled: filled
}, "*");
}
}
};
VP.ad("vp_wrapper", "test_placement", 320, 100, opts);
위와 같이 (B) 의 callback 에서 postMessage 를 통해 가장 상위 프레임에 메시지를 전달하도록 합니다.
그리고 가장 상위 프레임인 (A) 에서는
window.addEventListener('message', function (msg) {
if (typeof(msg.data) == 'object' && msg.data.action == "vp_ad_callback") {
if (!msg.data.filled) {
var iframe = document.querySelector("?????");
iframe.parentElement.removeChild(iframe);
}
} else {
var iframe = document.querySelector("?????");
iframe.contentWindow.postMessage(msg.data, "*");
}
});
위와 같이 message 핸들링 부분을 수정해서 filled 변수 값에 따라 no ad 일 때 (A) 에서 광고 영역 자체를 숨길 수 있습니다.