WebView 직접 연동
애드팝콘 SSP SDK 연동 없이 URL만을 이용하여 연동하고자 할 때 사용되는 가이드입니다.
1. Landing URL 내 파라미터 설정
애드팝콘 SSP 사업팀으로부터 전달 받은 URL에 포함된 매크로를 직접 치환한 뒤, 웹뷰로 URL을 호출해 주어야 합니다. 이벤트 페이지 주소는 팝콘텐츠에 따라 조금씩 다르지만, 파라미터 구조는 동일합니다.
https://reward-webview.adpopcornpopcontents.com/fortune/?userId={USN}&clientSessionId={CLIENT_SESSION_ID}&pageId=1
userId : 매체의 앱키와 이벤트 페이지에서 발생하는 리워드를 지급 받을 유저의 unique한 id를 조합하여 id가 생성됩니다. 앱키는 애드팝콘 SSP 사업실로부터 전달 받을 수 있습니다.
android, ios
{appKey}:{USN}
800296516:test_user_id
clientSessionId
android
adid:{adid}
adid:08283f4e-2094-4e96-94e5-5b95b536827d
ios
idfa:{idfa}
idfa:99ED0000-75E0-43BD-BB25-8AE5A61B6437
위 조건에 따라 매크로 치환을 할 경우 아래와 같은 랜딩 URL이 나오게 됩니다.
https://reward-webview.adpopcornpopcontents.com/fortune/?userId=800296516:test_userId&clientSessionId=adid:08283f4e-2094-4e96-94e5-5b95b536827d&pageId=1
2. 웹뷰 내 JavascriptInterface 설정
이벤트 페이지의 원활한 동작을 위해 아래 가이드를 참고하여 JavascriptInterface 설정을 진행해 주어야 합니다.
2.1 공통 사항
호출은 nativeBridge 객체로 호출합니다. 이에 아래와 같이 nativeBridge를 등록해 주어야 합니다.
webView = new WebView(context.getApplicationContext());
webView.addJavascriptInterface(this, "nativeBridge");
wkContentController = [[WKUserContentController alloc]init];
[wkContentController addScriptMessageHandler:self name:@"nativeBridge"];
webViewConfiguration = [[WKWebViewConfiguration alloc]init];
webViewConfiguration.userContentController = wkContentController;
webView = [[WKWebView alloc] initWithFrame:CGRectMake(0, 0, [UIScreen mainScreen].bounds.size.width, [UIScreen mainScreen].bounds.size.height) configuration:webViewConfiguration];
이벤트 페이지에서 기기별 호출 형식은 아래와 같습니다.
window.nativeBridge.fncName() // -> no params
window.nativeBridge.fncName(JSON.stringify({
params: { ,,, } // -> params
}))
window.webkit.messageHandlers.nativeBridge.postMessage(JSON.s
fncName: "fncName", // -> no params
}))
window.webkit.messageHandlers.nativeBridge.postMessage(JSON.s
fncName: "fncName",
params: { ,,, } // -> params
}))
인터페이스에 대한 콜백은 nativeCallback 객체로 호출합니다.
window.nativeCallback.fncName()
2.2 Interfaces
2.2.1 onPageLoaded()
설명 : 웹뷰가 정상적으로 로드 되었음을 확인합니다.
콜백 : onPageLoaded()가 호출된 이후에 웹뷰 로드 성공 여부를 콜백으로 전달해 주어야 합니다.
// AOS
window.nativeBridge.onPageLoaded()
// iOS
window.webkit.messageHandlers.nativeBridge.postMessage(JSON.s
fncName: "onPageLoaded"
}))
/**
* 웹뷰 로드 성공 여부를 '0', '1'로 구분하여 SDK에서 onPageLoaded 콜백
* @Param {('0' | '1')} success 성공 여부
*/
window.nativeCallback.onPageLoaded = (success: '0' | '1') =>
// '0': 로드 실패
// '1': 로드 성공
}
2.2.2 finish()
설명 : 현재 웹뷰를 종료합니다.
콜백 : 콜백 없음
// AOS
window.nativeBridge.finish()
// iOS
window.webkit.messageHandlers.nativeBridge.postMessage(JSON.s
fncName: "finish"
}))
2.2.3 openExternalBrowser()
설명 : 외부 브라우저를 엽니다.
콜백 : 콜백 없음
// AOS
window.nativeBridge.openExternalBrowser("https://,,,")
// iOS
window.webkit.messageHandlers.nativeBridge.postMessage(JSON.s
fncName: "openExternalBrowser",
params: {
url: "https://,,,"
}
}))
2.2.4 registerBackKeyListener() - AndroidOnly
설명 : 디바이스 back key 동작 여부를 설정합니다. (안드로이드 디바이스에서만 사용됨)
콜백 : nativeBridge 으로 registerBackKeyListener() 가 호출된 이후에는 디바이스에서 백키 이벤트를 보낼 수 있을 경우 registerBackKeyListener('1) 을 호출 한 뒤, 백키 발생 시 마다 onBackKeyPress() 콜백을 보내 주어야 합니다.
// AOS
window.nativeBridge.registerBackKeyListener()
/**
* 벡키 권한 전달 성공 여부를 '0', '1'로 구분하여 SDK에서 registerBack
* @param {('0' | '1')} success 성공 여부
*/
window.nativeCallback.registerBackKeyListener = (success: '0
// '0': Back Key 권한 얻지 못함
// '1': Back Key 권한 얻음
}
/**
* Back Key 이벤트 발생시 SDK에서 onBackKeyPress 콜백을 호출합니다.
*/
window.nativeCallback.onBackKeyPress()
2.3 광고 클릭에 대한 이벤트 처리 추가
팝콘텐츠 이벤트 페이지 내, 스크립트로 동작하는 광고에 대한 클릭 처리 추가가 필요합니다.
아래 설정이 누락될 경우, 광고 클릭 시, 웹뷰 내에서 광고 페이지로 랜딩됩니다.
private WebViewClient mWebViewClient = new WebViewClient(){
@Override
public boolean shouldOverrideUrlLoading(final WebView view, WebResourceRequest request) {
try {
String url;
if (Build.VERSION.SDK_INT >= 21) {
url = request.getUrl().toString();
} else {
url = view.getUrl();
}
return urlLoading(view, url);
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
@Override
public boolean shouldOverrideUrlLoading(final WebView view, final String url) {
try {
return urlLoading(view, url);
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
@Override
public void onPageFinished(final WebView view, String url) {
super.onPageFinished(view, url);
}
private boolean urlLoading(WebView view, String url) {
if (url != null) {
Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(url));
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
context.startActivity(intent);
return true;
} else {
view.loadUrl(url);
return false;
}
}
};
#pragma mark WKUIDelegate
- (nullable WKWebView *)webView:(WKWebView *)webView createWebViewWithConfiguration:(WKWebViewConfiguration *)configuration forNavigationAction:(WKNavigationAction *)navigationAction windowFeatures:(WKWindowFeatures *)windowFeatures
{
NSString *requestString = navigationAction.request.URL.absoluteString;
if(navigationAction.navigationType == WKNavigationTypeLinkActivated ||
navigationAction.navigationType == WKNavigationTypeOther)
{
NSURL *requestURL = [NSURL URLWithString:requestString];
if(@available(iOS 10, *))
{
[[UIApplication sharedApplication] openURL:requestURL options:@{} completionHandler:^(BOOL success) {
if (success) {
[_webView stopLoading];
}
}];
}
else
{
[[UIApplication sharedApplication] openURL:requestURL];
[_webView stopLoading];
}
}
return nil;
}
#pragma mark - WKNavigationDelegate
- (void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler
{
NSString *requestString = navigationAction.request.URL.absoluteString;
if(navigationAction.navigationType == WKNavigationTypeLinkActivated)
{
decisionHandler(WKNavigationActionPolicyCancel);
NSURL *requestURL = [NSURL URLWithString:requestString];
if(@available(iOS 10, *))
{
[[UIApplication sharedApplication] openURL:requestURL options:@{} completionHandler:^(BOOL success) {
if (success) {
[_webView stopLoading];
}
}];
}
else
{
[[UIApplication sharedApplication] openURL:requestURL];
[_webView stopLoading];
}
return;
}
decisionHandler(WKNavigationActionPolicyAllow);
}
2.4 Sample Code
아래 플랫폼별 샘플 코드는 2.2 Interfaces 구현과 관련된 사항만 간략히 작성해둔 코드이며, 이에 아래코드를 참고하여 개발 환경에 맞게 세팅하면 됩니다.
public class AdPopcornSSPPopContentsAdActivity extends Activity{
private boolean enableOnPageCallback = false;
private boolean enableBackKeyCallback = false;
private final String REGISTER_BACKKEY_ON_SCRIPT = "window.nativeCallback.registerBackKeyListener('1')";
private final String REGISTER_BACKKEY_OFF_SCRIPT = "window.nativeCallback.registerBackKeyListener('0')";
private final String PRESS_BACKKEY_SCRIPT = "window.nativeCallback.onBackKeyPress()";
private final String ON_PAGE_LOADED_SCRIPT = "window.nativeCallback.onPageLoaded('1')";
@Override
protected void onCreate(Bundle savedInstanceState) {
// TODO Auto-generated method stub
super.onCreate(savedInstanceState);
nonLeakingWebView = new NonLeakingWebView(context.getApplicationContext());
nonLeakingWebView.getSettings().setJavaScriptEnabled(true);
nonLeakingWebView.setWebViewClient(mWebViewClient);
nonLeakingWebView.addJavascriptInterface(this, "nativeBridge");
}
@JavascriptInterface
public void onPageLoaded() {
try {
enableOnPageCallback = true;
}catch(Exception e){}
}
@JavascriptInterface
public void finish() {
try {
super.finish();
}catch(Exception e){}
}
@JavascriptInterface
public void openExternalBrowser(String url){
try{
Intent openBrowser = new Intent(Intent.ACTION_VIEW);
Uri u = Uri.parse(url);
openBrowser.setData(u);
context.startActivity(openBrowser);
}catch(Exception e){}
}
@JavascriptInterface
public void registerBackKeyListener(){
try{
enableBackKeyCallback = true;
callJavascript(REGISTER_BACKKEY_ON_SCRIPT);
return;
}catch(Exception e){}
callJavascript(REGISTER_BACKKEY_OFF_SCRIPT);
}
@Override
public void onBackPressed() {
try {
if(enableBackKeyCallback) {
callJavascript(PRESS_BACKKEY_SCRIPT);
}
else{
super.onBackPressed();
}
}catch (Exception e){e.printStackTrace();}
}
public void callJavascript(final String script){
try {
if(webView!= null){
webView.post(new Runnable() {
@Override
public void run() {
webView.evaluateJavascript(script, null);
});
}
}catch(Exception e){}
}
private WebViewClient mWebViewClient = new WebViewClient(){
@Override
public boolean shouldOverrideUrlLoading(final WebView view, WebResourceRequest request) {
try {
String url;
if (Build.VERSION.SDK_INT >= 21) {
url = request.getUrl().toString();
} else {
url = view.getUrl();
}
return urlLoading(view, url);
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
@Override
public boolean shouldOverrideUrlLoading(final WebView view, final String url) {
try {
return urlLoading(view, url);
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
@Override
public void onPageFinished(final WebView view, String url) {
super.onPageFinished(view, url);
if(!isPageLoaded && enableOnPageCallback)
callJavascript(ON_PAGE_LOADED_SCRIPT);
isPageLoaded = true;
}
private boolean urlLoading(WebView view, String url) {
if (url != null) {
Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(url));
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
context.startActivity(intent);
return true;
} else {
view.loadUrl(url);
return false;
}
}
};
} // end of AdPopcornSSPPopContentsAdActivity
- (nullable WKWebView *)webView:(WKWebView *)webView createWebViewWithConfiguration:(WKWebViewConfiguration *)configuration forNavigationAction:(WKNavigationAction *)navigationAction windowFeatures:(WKWindowFeatures *)windowFeatures
{
NSString *requestString = navigationAction.request.URL.absoluteString;
if(navigationAction.navigationType == WKNavigationTypeLinkActivated ||
navigationAction.navigationType == WKNavigationTypeOther)
{
NSURL *requestURL = [NSURL URLWithString:requestString];
if(@available(iOS 10, *))
{
[[UIApplication sharedApplication] openURL:requestURL options:@{} completionHandler:^(BOOL success) {
if (success) {
[_webView stopLoading];
}
}];
}
else
{
[[UIApplication sharedApplication] openURL:requestURL];
[_webView stopLoading];
}
}
return nil;
}
#pragma mark - WKNavigationDelegate
- (void)webView:(WKWebView *)webView didCommitNavigation:(null_unspecified WKNavigation *)navigation
{
}
- (void)webView:(WKWebView *)webView didFinishNavigation:(null_unspecified WKNavigation *)navigation
{
if(!_isContentsLoaded && _enableOnPageCallback)
{
[self callJavascript:@"window.nativeCallback.onPageLoaded('1')"];
}
_isContentsLoaded = YES;
}
- (void)webView:(WKWebView *)webView didFailNavigation:(null_unspecified WKNavigation *)navigation withError:(NSError *)error
{
if(!_isContentsLoaded && _enableOnPageCallback)
{
[self callJavascript:@"window.nativeCallback.onPageLoaded('0')"];
}
_isContentsLoaded = YES;
}
- (void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler
{
NSString *requestString = navigationAction.request.URL.absoluteString;
if(navigationAction.navigationType == WKNavigationTypeLinkActivated)
{
decisionHandler(WKNavigationActionPolicyCancel);
NSURL *requestURL = [NSURL URLWithString:requestString];
if(@available(iOS 10, *))
{
[[UIApplication sharedApplication] openURL:requestURL options:@{} completionHandler:^(BOOL success) {
if (success) {
[_webView stopLoading];
}
}];
}
else
{
[[UIApplication sharedApplication] openURL:requestURL];
[_webView stopLoading];
}
return;
}
decisionHandler(WKNavigationActionPolicyAllow);
}
// Javascript Interface message 처리
- (void)userContentController:(nonnull WKUserContentController *)userContentController didReceiveScriptMessage:(nonnull WKScriptMessage *)message
{
if([message.name isEqualToString:@"nativeBridge"]){
id JSON;
if ([[message body] isKindOfClass:[NSString class]]) {
id strJson = [message body];
NSData *nsData = [strJson dataUsingEncoding:NSUTF8StringEncoding];
JSON = [NSJSONSerialization JSONObjectWithData:nsData options:0 error:nil];
}
else
{
JSON = [message body];
}
NSString *fncName = [self checkNilToBlankString:[JSON valueForKey:@"fncName"]];
if([fncName isEqualToString:@"onPageLoaded"])
{
_enableOnPageCallback = YES;
}
else if([fncName isEqualToString:@"finish"])
{
[_webView loadHTMLString:@"" baseURL:nil];
[_webView stopLoading];
_webView.UIDelegate = nil;
_webView.navigationDelegate = nil;
[_webView removeFromSuperview];
_webView = nil;
[[self presentingViewController] dismissViewControllerAnimated:NO completion:nil];
}
else if([fncName isEqualToString:@"openExternalBrowser"])
{
NSString *paramsJson = [self checkNilToBlankString:[JSON valueForKey:@"params"]];
NSString *url = [self checkNilToBlankString:[paramsJson valueForKey:@"url"]];
[self openWebBrowser:url];
}
}
}
#pragma mark General Method
- (NSString *)checkNilToBlankString:(id)target
{
NSString *returnString = @"";
if (!([target isEqual:[NSNull null]] || target == nil))
{
returnString = target;
}
return returnString;
}
- (void)callJavascript:(NSString *)data
{
dispatch_async(dispatch_get_main_queue(), ^(void){
[_webView evaluateJavaScript:data completionHandler:^(id result, NSError *error) {
}];
});
}
- (void)openWebBrowser:(NSString *)url
{
[[UIApplication sharedApplication] openURL:[NSURL URLWithString:url] options:@{} completionHandler:^(BOOL success) {
}];
}
Last updated
Was this helpful?