ハイブリッドアプリのアプリ内イベント計測

概要:モバイルアプリに埋め込まれたウェブビューからアプリ内イベントを記録し、インタラクションがアプリ内のウェブベースのコンテンツで発生している場合でも、完全なイベント測定を可能にします。

はじめに

アプリ内で発生するアプリ内ベントは、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インターフェイスがあり、これによりウェブビューがネイティブコードを呼び出すことができます。

URLの読み込みよりもこの「ネイティブのJavaScriptインターフェイス」を使用するメリット:

  • URLの読み込みをリスニングするロジックを実装する必要はありません。
  • ネイティブインターフェイスは、他の実装よりも優先されます。
  • URL からパラメーターを解析する必要はありません。
  • コードが少なくて済むため、必要なメンテナンスが少なくて済みます。

実装には、次の手順が含まれています:

  1. ウェブビューまたはウェブページのHTMLコード
  2. ウェブビューのネイティブコード実装

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>

上記のコードは、ボタンがクリックされたときにトリガーされる関数を設定します。 関数はeventNameeventParamsの変数を設定し、それらを 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つの手順で構成されています:

  1. ウェブビューまたはウェブページのHTMLコード
  2. ウェブビューのネイティブコード実装

ウェブビュー実装

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 iOS

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;

}