混合模式下的应用内事件

概要:如果用户安装了您的应用,但在该应用之外的移动端网页中完成了事件,AppsFlyer仍然可以帮助您记录这些事件。

简介

应用内事件(即在相关应用内发生的事件)通过SDK上报,那么应用之外发生的事件要如何上报呢?

在下列场景中,用户会在相关应用之外的环境中完成事件:

  • 用户在您的网站中完成事件:使用基于用户的归因(PBA)方案,对于跨流量入口和操作平台(包括网站和设备)的用户链路实现全面覆盖,获取经过标准化的综合数据。该方案提供网页到应用归因、转化路径分析以及相关的原始数据。
  • 后端服务器:事件的发生与网页或应用中的用户行为无关,比如自动续订事件。
  • 混合模式: 事件发生在移动端网页,但用户设备中已安装相关应用(具体示例请见下文说明)。 通过Javascript调用AppsFlyer SDK API,实现应用内事件的记录。

本文说明适用于混合模式下的使用场景,解释了如何记录HTML视图中的事件,并将其发送到相关应用,从而打通HTML和原生视图之间的数据传输。 

 示例

贵司开发了一个混合模式的应用,并向用户提供订阅套餐。您在网页视图中添加了订阅表格,该视图通过您的网站加载。

您可以在网页视图中记录订阅事件,并向原生代码发送套餐类型或价格等相关数据。

原生代码收集这些数据,并使用AppsFlyer SDK发送这些订阅事件。

在混合模式下记录应用内事件

本文针对混合模式下的应用内事件,介绍了以下两种事件记录方式:

  • 【推荐】JavaScript接口:使用原生JavaScript接口,打通HTML/网页视图(Web View)和原生代码之间的数据传输, 以便将应用内事件的相关数据从网页视图发送到原生代码。原生代码收到这些数据后,即通过SDK将其发送到AppsFlyer。
  • URL加载:您可以让原生代码监听特定URL的加载事件, 并从URL参数重提取数据,然后将这些数据传递给SDK。

Javascript接口

安卓和iOS都有原生的Javascript接口,网页视图可以通过这些接口调用原生代码。

与URL加载相比,JavaScript接口具有以下优势:

  • 不需要添加用于监听URL加载的逻辑。
  • 原生接口优于额外配置。
  • 不需要解析URL中的参数。
  • 代码更少,减轻维护负担。

部署流程包含以下两个步骤:

  1. 将HTML代码添加到网页视图或网页
  2. 配置用于网页视图的原生代码

安卓

适用于安卓的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">
 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\"}"
  • 建议仅使用小写字母和数字(a-z 和0-9)来命名您的应用内事件,以避免在传递数据时出现任何问题。

WebActivity类

使用以下代码创建一个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");
 }
}

上述代码可创建一个名为app的对象,用于连接网页视图与原生代码。

JavaScript接口类

创建一个MainJsInterface类,将recordEvent()添加为JavascriptInterface

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);
 }
}

上述代码声明了一个可在网页视图中通过Javascript调用的recordEvent方法。

请注意,在上文的HTML代码中,该方法通过app.recordEvent(eventName, eventParams)调用。这时我们已经设置了连接网页视图与原生代码app,因此app可以调用我们在MainJsInterface类中定义的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">
 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\"}"
  • 建议仅使用小写字母和数字(a-z 和0-9)来命名您的应用内事件,以避免在传递数据时出现任何问题。

 注意

上述HTML代码适用于Objective C和Swift。

Objective c

网页视图控制器

在您的网页视图控制器(web 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的logEvent方法。

适用于Swift的WebView控制器

在您的网页视图控制器(web view controller)中添加以下代码:

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方法,然后该方法会解析这些事件值,并调用AppsFlyer SDK的logEvent方法。

URL加载

安卓和iOS都部署了可监听URL加载事件的原生方法。您可以使用该方法完成以下操作:

  • 监听特定URL的加载事件
  • 提取附加在URL末尾的参数
  • 在原生代码中使用这些参数

部署流程包含以下两个步骤:

  1. 将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>

您可以将上述代码作为实际网页的一部分,或者以原生方式将其载入网页视图。

该代码可设置一个函数,用户点击按钮时即会触发该函数。该函数本身可创建一个用于加载自定义URL的iframe元素。此处的URL加载会触发安卓或iOS的原生方法,继而调用AppsFlyer SDK的logEvent方法。

所有必要参数(即事件名称和事件值)都会被添加到该URL的末尾。在下文的安卓和iOS代码示例中,我们会提取这些参数,并将其传递到AppsFlyer SDK的logEvent方法。

传递事件值时,须注意以下几点:

  • 确保事件值为JSON格式的字符串。
    "{\"af_revenue\":\"6.72\", \"af_content_type\": \"wallets\", \"af_content_id\": \"15854\"}"
  • 建议仅使用小写字母和数字(a-z 和0-9)来命名您的应用内事件,以避免在传递数据时出现任何问题。

原生部署

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;

}