인앱 이벤트는 사용자 활동에 대한 포괄적인 데이터를 제공합니다. Native view와 HTML 컨텐츠를 결합한 하이브리드 앱은 자바스크립트를 사용하여 앱스플라이어 SDK API를 호출함으로써 인앱 이벤트를 기록할 수 있습니다.
이 안내서는 HTML view와 native view 사이의 갭을 연결하는 방법을 설명하여, HTML 또는 web view에서 인앱 이벤트를 효과적으로 기록하게 합니다.
예
구독 서비스를 제공하는 하이브리드 앱이 있습니다. 웹사이트에서 실제로 로드되는 web view에서 구독 양식을 구현합니다.
Web view에서의 해당 구독 인앱 이벤트를 기록하고, 관련 데이터 (구독 유형, 가격 등)를 native code로 보낼 수 있습니다.
Native code는 데이터를 수집하고 앱스플라이어 SDK를 이용하여 해당 구독 이벤트를 전송합니다.
하이브리드 앱에서의 인앱 이벤트 기록
이 가이드에서는, 하이브리드 앱의 인앱 이벤트를 기록하는 두 가지 방법을 안내합니다.
네이티브 자바스크립트 인터페이스 (native javascript interface)
네이티브 자바스크립트 메소드가 권장되는 방법 입니다. 이는 네이티브 자바스크립트 인터페이스를 사용하여 HTML 또는 web view와 native code 사이에서 통신을 설정합니다.
이렇게 하면 web view에서 native code로 인앱 이벤트 관련 데이터를 보낼 수 있습니다. Native code가 데이터를 확보하면, 앱스플라이어 SDK를 사용하여 앱스플라이어로 전송합니다.
URL 로딩 메소드 (URL loading method)
URL 로딩 메소드에서, native code는 url 로딩 이벤트를 받습니다(listen).
Native code를 설정하여, 특정 url 로딩 이벤트를 리슨하고 url 파라미터에서 데이터를 추출할 수 있습니다. 그리고 이 데이터는 앱스플라이어 SDK로 전달됩니다.
안드로이드와 iOS 모두 web view에서 native code를 호출할 수 있는 네이티브 자바스크립트 인터페이스를 갖고 있습니다.
- 안드로이드 - JavascriptInterface
- iOS - JavaScriptCore
네이티브 자바스크립트 인터페이스를 사용하는 경우에는 URL 로딩에 비하여 다음과 같은 몇 가지 장점이 있습니다.
- URL 로딩을 받는(listen) 로직을 구현할 필요가 없습니다.
- 다른 구현에도 네이티브 인터페이스가 선호됩니다.
- URL 파라미터를 파싱할 필요가 없습니다.
- 코드가 더 적기 때문에, 유지보수가 덜 필요합니다.
아래의 구현은 두 단계로 구성됩니다.
- Webview 또는 웹페이지를 위한 HTML 코드
- 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)만 사용하도록 권장합니다.
Web activity class
다음 코드를 사용하여 웹 액티비티 클래스를 작성합니다.
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 interface class
JavascriptInterface
로 recordEvent()
를 구현할 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().trackEvent(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>
위 코드는 버튼이 클릭될 때 트리거되는 함수를 설정합니다. 이 함수는 eventName
및 eventParams
변수를 설정하고 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];
[[AppsFlyerTracker sharedTracker] trackEvent:eventName withValues:eventValueDict];
}
위 코드는 web viewe에서 메시지를 수신합니다. 메시지는 이벤트 이름(event name)과 이벤트 값(event value)을 포함하고 있습니다. 코드가 수신 메시지를 발견하면, 코드는 메시지를 recordEvent
메소드로 전달합니다. 이 메소드는 그 값(value)을 파싱하고 앱스플라이어 SDK trackEvent
메소드를 호출합니다.
Swift
Web view controller
Web view controller에서 다음 코드를 viewDidLoad
메소드에 추가합니다.
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)
}
AppsFlyerTracker.shared().trackEvent(eventName as String?, withValues: eventValueDict)
}
}
위 코드는 web viewe에서 메시지를 수신합니다. 메시지는 이벤트 이름(event name)과 이벤트 값(event value)을 포함하고 있습니다. 코드가 수신 메시지를 발견하면, 코드는 메시지를 recordEvent
메소드로 전달합니다. 이 메소드는 그 값(value)을 파싱하고 앱스플라이어 SDK trackEvent
메소드를 호출합니다.
안드로이드와 iOS 모두 url 로딩 이벤트를 받는(listen) 네이티브 메소드를 구현합니다. 이 메소드를 사용하면 다음을 할 수 있습니다.
- 특정 URL의 로딩 이벤트를 받음(listen)
- URL에 첨부된 파라미터 추출
- 네이티브 코드에 이 파라미터 사용
아래의 구현은 두 단계로 구성됩니다.
- Webview 또는 웹페이지를 위한 HTML 코드
- URL 로딩을 위한 네이티브 코드 구현
Webview 구현
HTML 코드 (안드로이드 webview 및 iOS uiwebview 모두를 위한)
<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의 trackEvent
메소드를 호출하는 네이티브 안드로이드 또는 iOS 메소드를 트리거 하는 것입니다.
모든 필수 파라미터 (이벤트 이름과 이벤트 값)은 URL에 첨부됩니다. 아래의 안드로이드와 iOS 코드 예시는 어떻게 이런 파라미터를 추출하고 앱스플라이어 SDK trackEvent
메소드에 전달하는지 보여줍니다.
이벤트 값(event value)를 전달할 때, 다음 사항을 유의하십시오.
- Stringified JSON로 이벤트 값을 전달합니다.
"{\"af_revenue\":\"6.72\", \"af_content_type\": \"wallets\", \"af_content_id\": \"15854\"}"
- 혹시 발생할 지 모르는 문제를 예방하기 위해서, 앱스플라이어에서는 인앱 이벤트 이름으로 오직 영문 소문자와 숫자(a-z 및 0-9)만 사용하도록 권장합니다.
Native implementation
안드로이드 구현
안드로이드 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().trackEvent(getApplicationContext(),eventName,eventValue);
}
return true;
}
view.loadUrl(url);
return true;
}
iOS 구현
Web view controller에서 다음 코드를 shouldStartLoadWithRequest
메소드에 추가합니다.
- (void)viewDidLoad {
[super viewDidLoad];
NSString *page = @"https://yourwebsite.com";
NSURL *url = [NSURL URLWithString:page];
NSURLRequest *request = [NSURLRequest requestWithURL:url];
_webView = [[WKWebView alloc] initWithFrame:self.view.frame];
_webView.navigationDelegate = self;
[_webView loadRequest:request];
_webView.frame = CGRectMake(self.view.frame.origin.x,self.view.frame.origin.y, self.view.frame.size.width, self.view.frame.size.height);
[self.view addSubview:_webView];
}
-(void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction* )navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler {
decisionHandler(WKNavigationActionPolicyAllow);
NSString *url = navigationAction.request.URL.absoluteString;
if([url containsString:(@"af-event")]){
[self recordEvent:(url)];
}
}
- (void)recordEvent:(NSString*)url{
NSString *eventNameAndEventValueString = [url componentsSeparatedByString: @"?"][1];
NSArray *eventNameAndEventValueArray = [eventNameAndEventValueString componentsSeparatedByString: @"&"];
NSString *eventName = [eventNameAndEventValueArray[0] componentsSeparatedByString: @"="][1];
NSString *eventValueParams = [eventNameAndEventValueArray[1] componentsSeparatedByString: @"="][1];
NSString *decodedValueParams = [eventValueParams stringByRemovingPercentEncoding];
NSData *eventValuedata = [decodedValueParams dataUsingEncoding:(NSUTF8StringEncoding)];
NSDictionary *eventValue = [NSJSONSerialization JSONObjectWithData:eventValuedata options:NSJSONReadingMutableContainers error:nil];
if (eventName != nil){
[[AppsFlyerTracker sharedTracker] trackEvent:eventName withValues:eventValue];
}
}
위의 코드는 모든 URL 로딩을 리슨합니다. "af-event"를 포함하는 URL이 로딩되면, 이것은 네이티브 recordEvent
메소드를 트리거합니다. 해당 네이티브 recordEvent
메소드는 필요한 파라미터(eventName와 eventValue)를 추출하여, 이 파라미터들을 앱스플라이어 SDK trackEvent
메소드로 전달합니다.
Web view controller에서 다음 코드를 webView
메소드에 추가합니다.
import WebKit
import UIKit
import AppsFlyerLib
class WebViewController: UIViewController, WKNavigationDelegate {
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))
}
func webView(_ view: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: ((WKNavigationActionPolicy) -> Void)) {
decisionHandler(.allow)
let pageUrl = navigationAction.request.url?.absoluteString
if(pageUrl?.hasPrefix("af-event://") ?? false){
let scanner = Scanner(string: pageUrl!)
scanner.charactersToBeSkipped = CharacterSet(charactersIn: "&?")
scanner.scanUpTo("?", into: nil)
var tempString, eventName : NSString?
var eventValue: [String: String] = Dictionary()
while(scanner.scanUpTo("&", into: &tempString)){
if let tempString = tempString {
if(tempString.hasPrefix("eventName=") && tempString.length > 10){
eventName = tempString.substring(from: 10) as NSString
}
if(tempString.hasPrefix("eventValue=") && tempString.length > 11){
let eventValues = tempString.components(separatedBy: "=")[1].removingPercentEncoding
let eventValuesData = eventValues?.data(using: String.Encoding.utf8)
do {
eventValue = try (JSONSerialization.jsonObject(with: eventValuesData!, options:[]) as? [String: String])!
} catch let error as NSError{
print(error)
}
}
}
}
if(eventName != nil){
if let eventName = eventName {
// record event
AppsFlyerTracker.shared().trackEvent(eventName as String?, withValues: eventValue)
}
}
}
}
}
위의 코드는 모든 URL 로딩을 리슨합니다. "af-event"를 포함하는 URL이 로딩되면, 이것은 필요한 파라미터(eventName와 eventValue)를 추출합니다. 그리고 이 파라미터들을 앱스플라이어 SDK trackEvent
메소드로 전달합니다.