하이브리드 앱 용 인앱 이벤트

요약: 앱이 설치되어 있지만 앱이 아닌 모바일 웹 사이트에서 이벤트를 수행하는 사용자의 인앱 이벤트 기록.

소개

앱 내에서 발생하는 인앱 이벤트는 SDK를 사용하여 보고됩니다. 앱 외부에서 발생하는 이벤트에 대해서는 어떻게 됩니까?

앱 컨텍스트 외부에서 이벤트가 발생하는 시나리오는 다음의 몇 가지가 있습니다.

  • 웹 사이트에서 발생하는 이벤트: PBA(유저 중심 어트리뷰션) 를 구현하여 채널, 웹 사이트를 포함한 플랫폼 및 기기 전반에 걸친 고객 여정에 대한 통합 뷰를 얻을 수 있습니다. 이를 통해 웹투앱, 전환 경로 분석 및 원시 데이터를 제공합니다. 
  • 백엔드 서버: 이벤트는 웹 사이트 또는 앱에 대한 사용자 조치와 독립적으로 발생합니다. 예를 들어, 자동 구독 갱신
  • 하이브리드: 이벤트는 이 기사에 설명된 대로 앱이 설치된 장치의 모바일 웹 사이트에서 수행됩니다. 인앱 이벤트는 Javascript를 사용하는 앱스플라이어 SDK API를 호출하여 기록됩니다.

본 가이드에서는 하이브리드 시나리오를 다룹니다. HTML 뷰와 네이티브 뷰 간의 차이를 해소하여 HTML 뷰에서 이벤트를 기록하고 앱으로 보낼 수 있는 방법에 대해 알아봅니다. 

 

구독 서비스를 제공하는 하이브리드 앱이 있습니다. 웹사이트에서 실제로 로드되는 web view에서 구독 양식을 구현합니다.

웹 뷰에서의 해당 구독 인앱 이벤트를 기록하고, 구독 유형, 가격 등 관련 데이터를 네이티브 코드로 보낼 수 있습니다.

네이티브 코드는 데이터를 수집하고 앱스플라이어 SDK를 이용하여 해당 구독 이벤트를 전송합니다.

하이브리드 앱에서의 인앱 이벤트 기록

이 가이드에서는, 하이브리드 앱의 인앱 이벤트를 기록하는 두 가지 방법을 안내합니다.

  • [권장 사항] 자바스크립트 인터페이스: 네이티브 자바스크립트 인터페이스를 사용하여 HTML 또는 웹 뷰와 네이티브 코드 사이의 통신을 설정합니다. 이런 방식으로 웹 뷰에서 네이티브 코드로 인앱 이벤트 관련 데이터를 보낼 수 있습니다. 네이티브 코드가 데이터를 확보하면, SDK를 사용하여 앱스플라이어로 전송합니다.
  • URL 로딩: 이 메서드에서, 네이티브 코드는 url 로딩 이벤트를 수신 대기합니다. 네이티브 코드를 설정하여 특정 URL로 로딩 이벤트를 수신 대기하고 URL 파라미터에서 데이터를 추출할 수 있습니다. 그런 다음 이 데이터는 앱스플라이어 SDK로 전달됩니다.

자바스크립트 인터페이스

안드로이드와 iOS 모두 web view에서 native code를 호출할 수 있는 네이티브 자바스크립트 인터페이스를 갖고 있습니다.

URL 로딩에 비한 네이티브 자바스크립트 인터페이스 사용의 장점:

  • URL 로딩을 수신 대기하는 로직을 구현할 필요가 없습니다.
  • 다른 구현에도 네이티브 인터페이스가 선호됩니다.
  • URL 파라미터를 파싱할 필요가 없습니다.
  • 코드가 더 적기 때문에, 유지보수가 덜 필요합니다.

구현은 다음 단계들로 구성됩니다.

  1. Webview 또는 웹페이지를 위한 HTML 코드
  2. Webview를 위한 네이티브 코드 구현

Android

안드로이드 용 HTML 코드

다음 HTML 코드를 web view 또는 웹 페이지에 추가합니다.

<h1>Recording Event From Web View</h1>
<div id="main">
 <button id="recordEvent" onclick="recordEvent()"> Record Event </button>
</div>
<script type="text/javascript">
 function recordEvent(){
 var eventName = "af_purchase"
 var eventParams = "{\"af_revenue\":\"6.72\", \"af_content_type\": \"wallets\", \"af_content_id\": \"15854\"}";
 app.recordEvent(eventName, eventParams)
}
</script>

이벤트 값(event value)를 전달할 때, 다음 사항을 유의하십시오.

  • Stringified JSON로 이벤트 값을 전달합니다.
    "{\"af_revenue\":\"6.72\", \"af_content_type\": \"wallets\", \"af_content_id\": \"15854\"}"
  • 혹시 발생할 지 모르는 문제를 예방하기 위해서, 앱스플라이어에서는 인앱 이벤트 이름으로 오직 영문 소문자와 숫자(a-z 및 0-9)만 사용하도록 권장합니다.

WebActivity 클래스

다음 코드를 사용하여 웹 액티비티 클래스를 작성합니다.

public class WebActivity extends AppCompatActivity {
 WebView web;

 @Override
 public void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  setContentView(R.layout.activity_web);
  web = (WebView) findViewById(R.id.webView);
  web.getSettings().setJavaScriptEnabled(true);
  web.addJavascriptInterface(new MainJsInterface(getApplicationContext()), "app");
  web.loadUrl("https://yourwebsite.com");
 }
}

위 코드는 web view와 native code 사이의 교각 역할을 하는 app 이라는 오브젝트를 작성합니다.

Javascript 인터페이스 클래스

JavascriptInterfacerecordEvent()를 구현할 MainJsInterface class를 생성합니다.

public class MainJsInterface {
 Context mContext;

 MainJsInterface(Context c) {
  mContext = c;
 }

 @JavascriptInterface
 public void recordEvent(String name, String json){
  Map<String, Object> params = null;
  if(json!=null) {
   try {
    JSONObject jsonObject = new JSONObject(json);
    params = new HashMap<>();
    Iterator keys = jsonObject.keys();
    while (keys.hasNext()) {
     String key = keys.next();
     Object value = jsonObject.opt(key);
     params.put(key, value);
    }
   } catch (JSONException e) {
    e.printStackTrace();
   }
  }
  AppsFlyerLib.getInstance().logEvent(this.mContext, name, params);
 }
}

위의 코드는 자바스크립트를 사용하여 web view에서 호출될 수 있는 recordEvent 메서드를 선언합니다.

HTML 코드에서는 메서드가 app.recordEvent(eventName, eventParams)를 사용하여 호출되는 것을 확인할 수 있습니다. web view와 native code 사이의 교각 역할을 수행하도록 app을 설정했던 것을 기억하십시오. 이것이 app이 MainJsInterface class에 정의한 recordEvent 메서드를 호출할 수 있는 이유입니다.

iOS

iOS 용 HTML 코드

다음 HTML 코드를 web view 또는 웹 페이지에 추가합니다.

<body>
 <h1>Recroding Event From Web View</h1>
 <div id="main">
  <button id="recordEvent" onclick="recordEvent()"> Record Event </button>
 </div>
 <script type="text/javascript">
 function recordEvent(){
  var eventName = "af_purchase"
  var eventParams = "{\"af_revenue\":\"6.72\", \"af_content_type\": \"wallets\", \"af_content_id\": \"15854\"}";
  webkit.messageHandlers.event.postMessage(eventName + "+" + eventParams);
 }
 </script>
</body>

위 코드는 버튼이 클릭될 때 트리거되는 함수를 설정합니다. 이 함수는 eventNameeventParams 변수를 설정하고 webkit.messageHandlers를 사용하여 네이티브 코드에 변수를 전달합니다.

이벤트 값(event value)를 전달할 때, 다음 사항을 유의하십시오.

  • Stringified JSON로 이벤트 값을 전달합니다.
    "{\"af_revenue\":\"6.72\", \"af_content_type\": \"wallets\", \"af_content_id\": \"15854\"}"
  • 혹시 발생할 지 모르는 문제를 예방하기 위해서, 앱스플라이어에서는 인앱 이벤트 이름으로 오직 영문 소문자와 숫자(a-z 및 0-9)만 사용하도록 권장합니다.

 참고

HTML 코드는 Objective C 및 Swift 모두를 위한 것입니다.

Objective c

Web view controller

Web view controller에서 다음 코드를 view controller에 추가합니다.

-(void)loadView{
 [super loadView];

 WKWebViewConfiguration *configuration =
 [[WKWebViewConfiguration alloc] init];
 [configuration.userContentController
 addScriptMessageHandler:self name:@"event"];

 _webView = [[WKWebView alloc] initWithFrame:self.view.frame
         configuration:configuration];
 [self.view addSubview:_webView];
}

- (void)viewDidLoad {

 [super viewDidLoad];

 NSString* page = @"https://yourwebsite.com";

 NSURL *url = [NSURL URLWithString:page];
 NSURLRequest *request = [NSURLRequest requestWithURL:url];

 [_webView loadRequest:request];

 }

- (void)userContentController:(WKUserContentController *)userContentController
  didReceiveScriptMessage:(WKScriptMessage *)message {

 NSString* messageBody = message.body;
 NSString* eventName = [messageBody componentsSeparatedByString:@"+"][0];
 NSString* eventValue = [messageBody componentsSeparatedByString:@"+"][1];
 [self recordEvent:eventName eventValue:eventValue];
}

- (void)recordEvent:(NSString*)eventName eventValue:(NSString*)eventValue{

 NSData *eventValuedata = [eventValue dataUsingEncoding:(NSUTF8StringEncoding)];

 NSDictionary *eventValueDict = [NSJSONSerialization JSONObjectWithData:eventValuedata options:NSJSONReadingMutableContainers error:nil];

 [[AppsFlyerLib shared] logEvent:eventName withValues:eventValueDict];

}

위 코드는 웹 뷰의 메시지를 수신합니다. 메시지는 이벤트 이름과 이벤트 값을 포함하고 있습니다. 코드는 수신 메시지를 발견하면 그 메시지를 recordEvent 메서드로 전달합니다. 이 메서드는 그 값을 파싱하고 앱스플라이어 SDK trackEvent 메서드를 호출합니다.

Swift WebView 컨트롤러

웹 보기 컨트롤러에서 다음 코드를 추가합니다.

import WebKit
import UIKit
import AppsFlyerLib
import JavaScriptCore

class JSViewController: UIViewController, WKNavigationDelegate, WKScriptMessageHandler {
 func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
  let messageBody = message.body as? String
  let eventName = messageBody?.components(separatedBy: "+")[0]
  let eventValue = messageBody?.components(separatedBy: "+")[1]
  recordEvent(eventName: eventName!, eventValue: eventValue!)
 }

 var webView: WKWebView!
 override func loadView(){
  webView = WKWebView()
  webView.navigationDelegate = self
  view = webView
 }

 override func viewDidLoad() {
  super.viewDidLoad()
  let url = URL(string: "https://yourwebsite.com")!
  webView.load(URLRequest(url: url))
  webView.configuration.userContentController.add(self, name:"event")
 }

 func recordEvent(eventName: String, eventValue: String) {

  var eventValueDict = [String: String]()

  let eventValuesData = eventValue.data(using: String.Encoding.utf8)
  do {
   eventValueDict = try (JSONSerialization.jsonObject(with: eventValuesData!, options:[]) as? [String: String])!
  } catch let error as NSError{
   print(error)
  }

  AppsFlyerLib.shared().logEvent(eventName as String?, withValues: eventValueDict)
 }

}

위 코드는 웹 뷰의 메시지를 수신합니다. 메시지는 이벤트 이름과 이벤트 값을 포함하고 있습니다. 코드가 수신 메시지를 발견하면 그 메시지를 recordEvent 메서드로 전달하며 이 메서드는 그 값을 파싱하고 앱스플라이어 SDK logEvent 메서드를 호출합니다.

URL 로딩

안드로이드와 iOS 모두 url 로딩 이벤트를 받는(listen) 네이티브 메서드를 구현합니다. 이 메서드를 사용하면 다음을 할 수 있습니다.

  • 특정 URL의 로딩 이벤트를 받음
  • URL에 첨부된 파라미터 추출
  • 네이티브 코드에 이 파라미터 사용

아래의 구현은 두 단계로 구성됩니다.

  1. Webview 또는 웹페이지를 위한 HTML 코드
  2. URL 로딩을 위한 네이티브 코드 구현

Webview 구현

HTML 코드 (안드로이드 WebView 및 iOS WKWebView 모두에 사용)

<html>
<head>
</head>
 <body>
  <h1>Recording From Web View</h1>
  <div id="main">
   <input id="button" type="button" value="Record Event" />
  </div>
  <script type="text/javascript">
  function recordEvent(eventName,eventValue){
   var iframe = document.createElement("IFRAME");
   iframe.setAttribute("src", "af-event://inappevent?eventName="+eventName+"&eventValue="+eventValue);
   document.documentElement.appendChild(iframe);
   iframe.parentNode.removeChild(iframe);
   iframe = null;
  }
  var button = document.getElementById("button");
  button.onclick = function(event) {
   var eventName = "af_purchase";
   var eventValue = "{\"af_revenue\":\"6.72\", \"af_content_type\": \"wallets\", \"af_content_id\": \"15854\"}";
   recordEvent(eventName, eventValue);
  }
  </script>
 </body>
</html>

상기 코드는 실제 웹 페이지의 일부이거나 webview에 기본적으로 로딩될 수 있습니다.

위 코드는 버튼이 클릭될 때 트리거되는 함수를 설정합니다. 함수 자체는 사용자 정의 URL을 로딩하는 iframe element를 생성합니다. 이 URL 로딩이 앱스플라이어 SDK의 logEvent 메서드를 호출하는 네이티브 안드로이드 또는 iOS 메서드를 트리거하는 것입니다.

모든 필수 파라미터(이벤트 이름과 이벤트 값)는 URL에 추가됩니다. 아래의 안드로이드와 iOS 코드 예시는 어떻게 이런 파라미터를 추출하고 앱스플라이어 SDK logEvent 메서드에 전달하는지 보여줍니다.

이벤트 값(event value)를 전달할 때, 다음 사항을 유의하십시오.

  • Stringified JSON로 이벤트 값을 전달합니다.
    "{\"af_revenue\":\"6.72\", \"af_content_type\": \"wallets\", \"af_content_id\": \"15854\"}"
  • 혹시 발생할 지 모르는 문제를 예방하기 위해서, 앱스플라이어에서는 인앱 이벤트 이름으로 오직 영문 소문자와 숫자(a-z 및 0-9)만 사용하도록 권장합니다.

Native implementation

Android iOS

안드로이드 구현

안드로이드 WebView는 웹 뷰에서 알림을 수신하는 WebViewClient를 사용합니다. shouldOverrideUrlLoading에서는 웹 뷰에서 들어온 URL 로딩 이벤트를 처리할 수 있습니다.

@Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
	if (url.startsWith("af-event://")) {
		String[] urlParts = url.split("\\?");
		if (urlParts.length > 1) {
			String query = urlParts[1];
			String eventName = null;
			HashMap<String, Object> eventValue = new HashMap<>();

			for (String param : query.split("&")) {
				String[] pair = param.split("=");
				String key = pair[0];
				if (pair.length > 1) {
					if ("eventName".equals(key)){
						eventName = pair[1];
					} else if ("eventValue".equals(key)){
						JSONObject event;
						JSONArray keys;
						try {
							event = new JSONObject(pair[1]);
							keys = event.names();
							for (int i = 0; i < keys.length(); i++){
								eventValue.put(keys.getString(i), event.getString(keys.getString(i)));
							}
						} catch (JSONException e) {
							e.printStackTrace();
						}
					}
				}
			}
			AppsFlyerLib.getInstance().logEvent(getApplicationContext(),eventName,eventValue);
		}
		return true;
	}
	view.loadUrl(url);
	return true;

}