概要:モバイルアプリに埋め込まれたウェブビューからアプリ内イベントを記録し、インタラクションがアプリ内のウェブベースのコンテンツで発生している場合でも、完全なイベント測定を可能にします。
はじめに
アプリ内で発生するアプリ内ベントは、SDKからレポートされます。アプリの外で発生するイベントはどうなるでしょうか?
アプリの外で発生するイベントにはいくつかのシナリオが考えられます:
- ウェブサイトで発生するイベント:People-Based Attribution (PBA) を実装して、ウェブサイトを含むチャネル、プラットフォーム、デバイス全体でのユーザージャーニーの統一ビューを取得します。これにより、Web-to-App、コンバージョン経路分析、及びローデータが提供されます。
- バックエンドサーバー:ウェブサイトまたはアプリでのユーザーアクションに関係なく発生するイベントです。例:自動更新のサブスクリプション、店舗来店、店舗購入など
- ハイブリッド: 本記事で説明するように、アプリを搭載したデバイス上のモバイルウェブサイト上で発生します。 アプリ内イベントは、Javascriptを使用してAppsFlyer SDK APIを呼び出すことで記録されます。
このガイドでは、ハイブリッドのシナリオについて説明します。アプリのHTMLモードとネイティブモードのギャップを埋め、HTMLモードでイベントを記録してアプリに送信できるようにする方法について説明します。
例
サブスクリプションを含むハイブリッドアプリを提供しています。ウェブサイトから読み込まれるサブスクリプションフォームをウェブビューに実装します。
ウェブビュー上のサブスクリプションというアプリ内イベントを記録し、サブスクリプションの種類や価格など、それに関連するデータをネイティブコードに送信できます。
ネイティブコードは、このデータを収集し、AppsFlyer SDKを使用してサブスクリプションのアプリ内イベントを送信します。
ハイブリッドアプリでアプリ内イベントを記録する
このガイドでは、ハイブリッドアプリでアプリ内イベントを記録するための2つの方法について説明しています。
- [推奨] JavaScriptインターフェース:HTMLまたはウェブビューとネイティブコード間の通信を確立するために、ネイティブJavaScriptインターフェースを使用します。 この方法で、ウェブビューからのアプリ内イベントに関するデータをネイティブコードに送信できます。ネイティブコードがデータを受信すると、SDKを使用してそれをAppsFlyerに送信します。
- URLの読み込み:この方法では、ネイティブコードがURLがイベントを読み込むのを処理します。 ネイティブコードを設定して、特定のURLの読み込みイベントをリスニングし、URL パラメーターからデータを抽出できます。データはその後SDKに渡されます。
Javascriptインターフェイス
AndroidとiOSの両方にJavascriptインターフェイスがあり、これによりウェブビューがネイティブコードを呼び出すことができます。
- Android:JavaScriptInterface
- iOS:JavaScriptCore
URLの読み込みよりもこの「ネイティブのJavaScriptインターフェイス」を使用するメリット:
- URLの読み込みをリスニングするロジックを実装する必要はありません。
- ネイティブインターフェイスは、他の実装よりも優先されます。
- URL からパラメーターを解析する必要はありません。
- コードが少なくて済むため、必要なメンテナンスが少なくて済みます。
実装には、次の手順が含まれています:
- ウェブビューまたはウェブページのHTMLコード
- ウェブビューのネイティブコード実装
Android
Android向けのHTMLコード
次のHTMLコードをウェブビューまたはウェブページに追加してください:
<h1>Recording Event From Web View</h1> <div id="main"> <button id="recordEvent" onclick="recordEvent()"> Record Event </button> </div> <script type="text/JavaScript"> <!-- Set Customer User ID --> function setCustomerUserId(){ app.setCustomerUserId(customerUserId) } 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>
イベント値を渡す際は、以下の点にご注意ください:
-
イベント値は、文字列化されたJSON形式で渡すようにしてください。
"{\"af_revenue\":\"6.72\", \"af_content_type\": \"wallets\", \"af_content_id\": \"15854\"}"
- この問題を回避するため、AppsFlyerではアプリ内イベント名に小文字の英数字(a~zおよび0~9)のみを使用することを推奨します。
WebActivity class
以下のコードを含む「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"); } }
上記のコードは、ウェブビューとネイティブコード間のブリッジとして機能する app と呼ばれるオブジェクトを作成します。
JavaScript インターフェイス class
MainJsInterfaceクラスを作成してrecordEvent()
をJavaScriptInterface
として実装します。
public class MainJsInterface { Context mContext; MainJsInterface(Context c) { mContext = c; } //Set Customer User ID @JavaScriptInterface public void setCustomerUserId(String customerUserId){ AppsFlyerLib.getInstance().setCustomerUserId(customerUserId); } @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); } }
上記のコードは、Javascriptを使用してウェブビューで呼び出すことができる recordEvent
を宣言しています。
HTMLコードでは、メソッドが
app.recordEvent(eventName, eventParams)
を使用して呼び出されているのがわかります。ウェブビューとネイティブコードの間のブリッジとして機能するように app.recordEvent(eventName, eventParams)
appapp.recordEvent(eventName,> を設定していることに留意してください。
このため、app が MainJsInterface class で定義した
recordEvent
メソッドを呼び出すことができます。
iOS
iOS向けのHTMLコード
次のHTMLコードをウェブビューまたはウェブページに追加してください:
<body> <h1>Recroding Event From Web View</h1> <div id="main"> <button id="recordEvent" onclick="recordEvent()"> Record Event </button> </div> <script type="text/JavaScript"> <!-- Set Customer User ID --> function setCustomerUserId(){ customerUserIdwebkit.messageHandlers.cuid.postMessage(customerUserId); } 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
を使用してネイティブコードに渡します。
イベント値を渡す際は、以下の点にご注意ください:
-
イベント値は、文字列化されたJSON形式で渡すようにしてください。
"{\"af_revenue\":\"6.72\", \"af_content_type\": \"wallets\", \"af_content_id\": \"15854\"}"
- この問題を回避するため、AppsFlyerではアプリ内イベント名に小文字の英数字(a~zおよび0~9)のみを使用することを推奨します。
注
HTMLコードは、Objective-CとSwiftの両方を対象としています。
Objective c
ウェブビューコントローラー
ビューコントローラーに次のコードを追加します。
-(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の
logEvent
メソッドを呼び出します。
Swiftウェブビューコントローラー
ウェブビューコントローラーに、次のコードを追加します:
import WebKit import UIKit import AppsFlyerLib import JavaScriptCore class JSViewController: UIViewController, WKNavigationDelegate, WKScriptMessageHandler { func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) { if message.name == "event" { let messageBody = message.body as? String let eventName = messageBody?.components(separatedBy: "+")[0] let eventValue = messageBody?.components(separatedBy: "+")[1] recordEvent(eventName: eventName!, eventValue: eventValue!) } else if message.name == "cuid" { AppsFlyerLib.shared().customerUserID = message.body as? String } } 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の読み込み
AndroidとiOSはどちらも、URL読み込みイベントをリスニングするネイティブメソッドを実装しています。このメソッドを使用すると、以下のことが実行できます:
- 特定のURLの読み込みイベントをリスニングする
- URLに追加されるパラメーターを抽出する
- これらのパラメーターをネイティブコードで使用する
以下の実装は、次の2つの手順で構成されています:
- ウェブビューまたはウェブページのHTMLコード
- ウェブビューのネイティブコード実装
ウェブビュー実装
HTMLコード(Android 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>
上記のコードは、実際のWebページの一部にすることも、ウェブビューにネイティブでロードすることもできます。
このコードは、ボタンがクリックされたときにトリガーされる関数を定義します。この関数はカスタムURLを読み込む iframe 要素を作成します。このURLの読み込みによって、ネイティブの Android メソッドまたは iOS メソッドがトリガーされ、AppsFlyer SDK logEvent
メソッドが呼び出されます。
すべての必須パラメーター(イベント名とイベント値)が URLに追加されます。以下のAndroidおよびiOSのコード例は、これらのパラメーターを抽出してAppsFlyer SDKの logEvent
メソッドに渡す方法を示しています。
イベント値を渡す際は、以下の点にご注意ください:
- イベント値は、文字列化されたJSON形式で渡すようにしてください。
"{\"af_revenue\":\"6.72\", \"af_content_type\": \"wallets\", \"af_content_id\": \"15854\"}"
- この問題を回避するため、AppsFlyerではアプリ内イベント名に小文字の英数字(a~zおよび0~9)のみを使用することを推奨します。
ネイティブ実装
Android実装
Android 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; }
iOS実装
ウェブビューコントローラで、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){ [[AppsFlyerLib shared] logEvent:eventName withValues:eventValue]; } }
上記のコードは、URLの読み込みを処理します。"af-event" を含む URL がロードされると、ネイティブ recordEvent
メソッドがトリガーされます。ネイティブ recordEvent
メソッドは必要なパラメータ(eventName と eventValue)を抽出し、ネイティブ SDK logEvent
メソッドに渡します。
ウェブビューコントローラで、webView(_:decidePolicyFor:decisionHandler:)
メソッドに次のコードを追加してください。
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 AppsFlyerLib.shared().logEvent(eventName as String?, withValues: eventValue) } } } } }
上記のコードは、URLの読み込みを処理します。「af-event」を含む URL が読み込まれると、必要なパラメーター (eventName と eventValue) が抽出され、ネイティブSDKの logEvent
メソッドに渡されます。