AppsFlyer 可以帮助客户直接在客户端 SDK 层级实时访问用户的归因数据。从而实现,在用户在首次打开App时,能够将用户重定向至指定页面或展示指定内容。这种功能场景通常被称为“延迟”深度链接:
该功能在 Web端很常见,但在移动应用生态系统中要实现这一点则颇为挑战。当然,AppsFlyer在不同设备平台都可以轻松为您提供相应的解决方案。
重要信息!
延迟深度链接用于新增用户。要为现有用户提供深度链接,您的应用必须支持URI方案、APP Links(Android)或Universal Links(iOS)。
请参阅深度链接设置了解更多信息。
在安装时将新用户发送到深度链接与自定义 URL Scheme(iOS、Android)深度链接已安装用户的场景非常类似。
示例
试想一个用户在搜索“纽约酒店”后点击了 HotelTonight 的 Google Adwords 广告。该用户首先被转至 Google Play/App-Store 去下载应用,然后在首次打开应用时,用户会直接看到纽约的酒店页面。通过获取 AppsFlyer 提供的归因数据,应用会收到准确的营销活动以及关键字,推动用户转化/安装。
除了增强用户体验以及提高转化率以外,此功能还可以实现基于场景的复杂营销活动,比如为安装和使用应用的用户提供优惠/奖励。例如,用安装应用即可获得50美金订购券广告系列取代点击安装的广告系列。这种营销活动不仅可以提高点击至安装的转化率,还可以提高付费用户的转化率。这些营销活动可以将投资回报提高 2-5 倍!
AppsFlyer的SDK负责启用从应用内部访问归因数据。在首次打开应用时,获取 AppsFlyer SDK 提供的归因数据可能需要几秒钟。之后每次打开应用,这个过程是即时的,因为归因数据已经存储在设备上。
SDK 集成
onConversionDataSuccess
会包含 激活的归因数据. 您可以使用该函数执行:
- 延迟深度链接: 首次进入app的新用户,跳转至指定页面
- 在用户使用app的整个周期内针对不同的事件获取激活归因数据
示例
用户在Facebook上看到一双小红鞋的广告后很喜欢,决定 下载并激活您的应用 。在这个场景下,如果想将用户在首次打开app时,直接定向到app中的小红鞋商品页面,请使用 onConversionDataSuccess
要访问 AppsFlyer 从 Android SDK 中获取的转化数据,执行 ConversionDataListener:
注意: 从SDK 5.0版本开始,获取归因转化数据的方法名称更改为 onConversionDataSuccess
。如果使用的SDK版本低于5.0.0,该方法的名称为 onInstallConversionDataLoaded
。我们建议您升级到SDK 5.0.0。要了解更多信息,请点击这里 。
public interface AppsFlyerConversionListener {
void onConversionDataSuccess(Map<String,String> conversionData);
void onConversionDataFail(String errorMessage);
}
如需从 SDK 中获取 AppsFlyer 的归因数据,请使用以下代码。
(void) onConversionDataSuccess:(NSDictionary*) installData;
onConversionDataSuccess
包含 激活的归因信息数据.该函数可以实现以下操作:
- 延迟深度链接: 首次进入app的新用户,跳转至指定页面
- 在用户使用app的整个周期内针对不同的事件获取激活归因数据
注意: 从SDK Version V5开始, onConversionDataSuccess
是获取归因转化数据方法的名称。如果使用的SDK版本低于5.0.0,则该方法的名称为 onConversionDataReceived
。我们建议您升级到SDK 5.0.0。要了解更多信息,请点击这里 。
didReceiveConversionData
里含有install的归因数据。该函数可以实现以下操作:
- 延迟深度链接: 首次进入app的新用户,跳转至指定页面
- 在用户使用app的整个周期内针对不同的事件获取激活归因数据
示例
用户在Facebook上看到一双小红鞋的广告后很喜欢。决定 下载并激活您的应用 。在这个场景下,如果想将用户在首次打开app时,直接定向到app中的小红鞋商品页面,请使用 didReceiveConversionDataLoaded
要访问 AppsFlyer 从 Android SDK 中获取的转化数据,执行 ConversionDataListener:
didReceiveConversionData(string conversionData);
代码示例
以下分别是用于 Android 和 iOS 的代码示例。
@Override
public void onCreate(){
super.onCreate();
/** Set Up Conversion Listener to get attribution data **/
AppsFlyerConversionListener conversionListener = new AppsFlyerConversionListener() {
/* Returns the attribution data. Note - the same conversion data is returned every time per install */
@Override
public void onConversionDataSuccess(Map<String, String> conversionData) {
for (String attrName : conversionData.keySet()) {
Log.d("LOG_TAG", "attribute: " + attrName + " = " + conversionData.get(attrName));
}
setInstallData(conversionData);
}
@Override
public void onConversionDataFail(String errorMessage) {
Log.d("LOG_TAG", "error getting conversion data: " + errorMessage);
}
/* Called only when a Deep Link is opened */
@Override
public void onAppOpenAttribution(Map<String, String> conversionData) {
for (String attrName : conversionData.keySet()) {
Log.d("LOG_TAG", "attribute: " + attrName + " = " + conversionData.get(attrName));
}
}
@Override
public void onAttributionFailure(String errorMessage) {
Log.d("LOG_TAG", "error onAttributionFailure : " + errorMessage);
}
};
/* This API enables AppsFlyer to detect installations, sessions, and updates. */
AppsFlyerLib.getInstance().init(AF_DEV_KEY, conversionListener, getApplicationContext());
AppsFlyerLib.getInstance().startTracking(this);
}
override fun onCreate() {
super.onCreate()
val conversionDataListener = object : AppsFlyerConversionListener{
override fun onConversionDataSuccess(data: MutableMap<String, String>?) {
data?.let { cvData ->
cvData.map {
Log.i(LOG_TAG, "conversion_attribute: ${it.key} = ${it.value}")
}
}
}
override fun onConversionDataFail(error: String?) {
Log.e(LOG_TAG, "error onAttributionFailure : $error")
}
override fun onAppOpenAttribution(data: MutableMap<String, String>?) {
data?.map {
Log.d(LOG_TAG, "onAppOpen_attribute: ${it.key} = ${it.value}")
}
}
override fun onAttributionFailure(error: String?) {
Log.e(LOG_TAG, "error onAttributionFailure : $error")
}
}
AppsFlyerLib.getInstance().init(devKey, conversionDataListener, applicationContext)
AppsFlyerLib.getInstance().startTracking(this)
}
}
提示
如果不需要转化数据,只需在 init
方法中传递 null
:
AppsFlyerLib.getInstance().init(AF_DEV_KEY, null, this);
#import "AppsFlyerTracker.h"
@interface AppDelegate : UIResponder<UIApplicationDelegate, AppsFlyerTrackerDelegate> {
...
}
- (BOOL)application:(UIApplication ?*)application didFinishLaunchingWithOptions:(NSDictionary*?)launchOptions {
[AppsFlyerTracker sharedTracker].appsFlyerDevKey = @"[MY_DEV_KEY]";
[AppsFlyerTracker sharedTracker].appleAppID = @"123456789";
// Load conversion and deep link data
[AppsFlyerTracker sharedTracker].delegate = self;
return YES;
}
-(void)applicationDidBecomeActive:(UIApplication *)application
{
[[AppsFlyerTracker sharedTracker] trackAppLaunch];
}
-(void)onConversionDataSuccess:(NSDictionary*) installData {
id status = [installData objectForKey:@"af_status"];
if([status isEqualToString:@"Non-organic"]) {
id sourceID = [installData objectForKey:@"media_source"];
id campaign = [installData objectForKey:@"campaign"];
NSLog(@"This is a none organic install. Media source: %@ Campaign: %@",sourceID,campaign);
} else if([status isEqualToString:@"Organic"]) {
NSLog(@"This is an organic install.");
}
}
-(void)onConversionDataFail:(NSError *) error {
NSLog(@"%@",error);
}
class AppDelegate: UIResponder, UIApplicationDelegate, AppsFlyerTrackerDelegate{
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
AppsFlyerTracker.shared().appsFlyerDevKey = "MY_DEV_KEY"
AppsFlyerTracker.shared().appleAppID = "123456789"
AppsFlyerTracker.shared().delegate = self
//AppsFlyerTracker.shared().isDebug = true
//AppsFlyerTracker.shared().appInviteOneLinkID = "ONELINK_ID";
return true
}
func applicationDidBecomeActive(_ application: UIApplication) {
AppsFlyerTracker.shared().trackAppLaunch()
}
func onConversionDataSuccess(_ installData: [AnyHashable: Any]) {
guard let first_launch_flag = installData["is_first_launch"] as? Int else {
return
}
guard let status = installData["af_status"] as? String else {
return
}
if(first_launch_flag == 1) {
if(status == "Non-organic") {
if let media_source = installData["media_source"] , let campaign = installData["campaign"]{
print("This is a Non-Organic install. Media source: \(media_source) Campaign: \(campaign)")
}
} else {
print("This is an organic install.")
}
} else {
print("Not First Launch")
}
}
func onConversionDataFail(_ error: Error!) {
if let err = error{
print(err)
}
}
func onAppOpenAttribution(_ attributionData: [AnyHashable : Any]!) {
if let data = attributionData{
print("\(data)")
}
}
func onAppOpenAttributionFailure(_ error: Error!) {
if let err = error{
print(err)
}
}
在 AF GameObject 的 Start
函数内:
#if UNITY_IOS
/* Mandatory - set your iOS app ID */
AppsFlyer.setAppID("123456789");
/* Call getConversionData() to get the conversion data in iOS*/
AppsFlyer.getConversionData();
AppsFlyer.trackAppLaunch();
#elif UNITY_ANDROID
/* Mandatory - set your Android package name */
AppsFlyer.setAppID ("com.company.app");
/* For getting the conversion data in Android, you need to add the "AppsFlyerTrackerCallbacks" listener.*/
AppsFlyer.init ("AF_DEY_KEY","AppsFlyerTrackerCallbacks");
#endif
在 AppsFlyerTrackerCallbacks
类内的 didReceiveConversionData
函数中执行您的逻辑 :
public class AppsFlyerTrackerCallbacks : MonoBehaviour {
public Text callbacks;
// Use this for initialization
void Start () {
print ("AppsFlyerTrackerCallbacks on Start");
}
public void didReceiveConversionData(string conversionData) {
print("AppsFlyerTrackerCallbacks:: got conversion data = " + conversionData);
}
public void didReceiveConversionDataWithError(string error) {
print ("AppsFlyerTrackerCallbacks:: got conversion data error = " + error);
}
public void didFinishValidateReceipt(string validateResult) {
print ("AppsFlyerTrackerCallbacks:: got didFinishValidateReceipt = " + validateResult);
}
public void didFinishValidateReceiptWithError (string error) {
print ("AppsFlyerTrackerCallbacks:: got idFinishValidateReceiptWithError error = " + error);
}
public void onAppOpenAttribution(string validateResult) {
print ("AppsFlyerTrackerCallbacks:: got onAppOpenAttribution = " + validateResult);
}
public void onAppOpenAttributionFailure (string error) {
print ("AppsFlyerTrackerCallbacks:: got onAppOpenAttributionFailure error = " + error);
}
public void onInAppBillingSuccess () {
print ("AppsFlyerTrackerCallbacks:: got onInAppBillingSuccess succcess");
}
public void onInAppBillingFailure (string error) {
print ("AppsFlyerTrackerCallbacks:: got onInAppBillingFailure error = " + error);
}
void printCallback(string str) {
callbacks.text += str + "\n";
}
}
{
"af_status": "Non-organic",
"media_source": "tapjoy_int",
"campaign": "July4-Campaign",
"agency": "starcomm",
"af_siteid": null,
"af_sub1": "subtext1",
"af_sub2": null,
"af_sub3": null,
"af_sub4": null,
"af_sub5": null,
"freehand-param": "somevalue",
"click_time": "2014-05-23 20:11:31",
"install_time": "2014-05-23 20:12:16.751"
}
首次启动
应用首次启动时,归因数据会从 AppsFlyer 的服务器实时发送到新激活的app。然后,SDK 会将该数据保存到应用的缓存中(Shared Preference 或 userDefault)。
针对后续的app启动,AppsFlyer 的 SDK 会直接从缓存中读取归因数据,而不会再向 AppsFlyer 服务器发起请求。因此,无论是否首次打开,归因数据始终返回相同的值。
为了帮助开发者仅在首次启动时才使用转化数据,AppsFlyer 在转化数据响应中添加了 is_first_launch
参数。 is_first_launch
在应用首次启动时为 true (Objective-C中为 YES ),之后为 false (Objective-C中为 NO )。该参数在 Android(SDK 4.8.4 及更高版本)和 iOS SDK(SDK 4.8.2 及更高版本)中均可使用。
如果您希望仅在首次启动时启用延迟深度链接,请执行判断is_first_launch
为True。
注意
由于转化数据储存在应用的共享内存中,用户清除应用数据时可能会将其删除。这会让is_first_launch
重新变成 True。因此,用户删除应用数据后启动应用被视为首次激活。
归因返回结果中的参数
返回的转化数据包含原始归因链接中的所有参数以及在点击或安装时创建的其他一些服务器参数。
由于转化数据依赖于归因链接,因而不同的来源和归因链接可能产生不同的转化数据参数。
每次app启动时,都会调用以下函数: onConversionDataSuccess
。
注意: 从SDK 5.0版本开始,获取归因转化数据的方法名称更改为 onConversionDataSuccess
。如果您使用的SDK版本低于5.0.0,则该方法的名称在Android中为 onInstallConversionDataLoaded
,在iOS中为 onConversionDataReceived
。我们建议您升级到SDK 5.0.0。要了解更多信息,请点击这里 。
视转化类型而定,可能有 3 种结果:
- 非自然激活
- 返回最初的归因数据(见以下示例)。
- 自然激活
- (或re-install)返回“organic install”
- 再归因(reattribution)
- 返回再归因的归因数据。
参数说明
参数名称 | 说明 | 示例值 | 媒体渠道 |
---|---|---|---|
af_status |
归因类型 有效值:
|
非自然 |
全部 |
af_message |
任意文本 |
自然激活/错误消息 |
全部 |
media_source |
媒体渠道名称。这是 AF 归因链接的“pid’”参数 |
注意代理商带来的激活,媒体渠道信息默认被隐藏,值为“null”。 |
全部
|
campaign |
广告系列名称(AppsFlyer 归因链接的 ‘C’ 参数或 Facebook 的广告系列名称) |
Ad1/camp123 |
全部 |
clickid |
点击 ID 或交易 ID |
123456/xsfd234 |
全部 |
af_siteid |
子渠道 ID(通常用于优化) |
子渠道1 |
全部 |
af_sub1 |
额外参数 |
一些参数 |
全部 |
af_sub2 |
额外参数 |
|
全部 |
af_sub3 |
额外参数 |
|
全部 |
af_sub4 |
额外参数 |
|
全部 |
af_sub5 |
额外参数 |
|
全部 |
af_keywords |
在搜索广告系列中用于搜索的关键字。例如:Google Search 广告系列 |
全部 |
|
click_time |
点击日期&时间(毫秒) (UTC) |
2014-01-08 00:07:53.233 UTC |
全部 |
install_time |
归因转换日期&时间(毫秒)(UTC) |
2014-01-08 00:12:51.701 UTC |
全部 |
agency |
代理或 PMD带来的激活 |
nanigans |
全部 |
is_first_launch |
首次启动时为 True,之后为 False |
true |
全部 |
is_fb
|
标记表明此安装归因于 Facebook。值:true/false |
true |
|
ad_id |
Facebook 的唯一广告标识号码 |
6012740800279 |
|
campaign_id |
广告系列活动ID |
6012700005123 |
全部 |
广告集 |
Facebook 的广告组名称 |
US - 18+ |
|
adset_id |
Facebook 广告组 ID |
6099800005123 |
|
orig_cost |
安装的成本(可以是任何货币) |
1.5 |
全部 |
cost_cents_USD |
货币换算后,以美分表示的成本值 |
150 (美分) |
全部 |
retargeting_conversion_type |
再营销转化类型 |
再归因(re-attribution) / 再互动(re-engagement) |
全部 |
注意
对于归因链接集成(非自归因平台),归因链接中出现的所有URL参数都会在GCD中返回。
广告平台示例
您可以参考归因转化数据指南,其中包含所有主要广告平台的payload示例,包含新用户获取和再营销场景。
常规 AppsFlyer 归因链接激活示例
使用以下归因链接带来的安装:
http://app.appsflyer.com/com.greatapp?pid=network_int&c=network_TH&af_ad=ad_name&af_sub1=102619
&af_sub2=network_TH_G001_Android&af_dp=app%3A%2F%2Fhome&af_prt=expertagency
&af_siteid=1777215&af_sub_siteid=1702&freehand-param=somevalue&tag={TAGID}
&clickid={CLICKID}&af_click_lookback=1d
http://app.appsflyer.com/id123456789?pid=network_int&c=network_KR&af_ad=ad_name&af_sub1=CD48704_
&af_sub2=network_KR_G001_iOS&af_dp=app%3A%2F%2Fhome&af_prt=expertagency&
af_siteid=1777236&af_sub_siteid=1702&freehand-param=somevalue&tag={TAGID}
&clickid={CLICKID}&af_click_lookback=1d
返回结果如下:
{
"media_source":"network_int",
"campaign":"network_TH",
"adset":null,
"clickid":"fb7f51d42-2621-93bd-e9a1b24f1acfab7b76e5104706104f4d6*******",
"adgroup":null,
"campaign_id":null,
"af_cost_currency":"USD",
"af_status":"Non-organic",
"af_sub_siteid":"1702",
"agency":"expertagency",
"af_sub3":null,
"af_cost_model":"CPI",
"af_siteid":"1777215",
"af_ad" = "ad_name",
"af_dp":"app://home",
"adset_id":null,
"click_time":"2017-07-19 08:30:31.890",
"cost_cents_USD":"150",
"iscache":true,
"is_first_launch":true,
"af_cpi":null,
"af_sub1":"102619",
"af_cost_value":"1.5",
"af_click_lookback":"1d",
"af_sub4":null,
"site_id":"1777215",
"adgroup_id":null,
"tag":"8d55089f-31b6-407b-9266-*********",
"orig_cost":"1.5",
"af_prt":"expertagency",
"af_sub5":null,
"install_time":"2017-07-19 08:30:35.461",
"af_sub2":"network_TH_G001_Android",
"freehand-param":"somevalue"
}
{
"media_source":"network_int",
"campaign":"network_KR",
"adset":null,
"adgroup":null,
"campaign_id":null,
"af_cost_currency":"USD",
"af_status":"Non-organic",
"agency":"expertagency",
"af_sub3":null,
"af_cost_model":"CPI",
"af_siteid":"1777236",
"af_ad" = "ad_name",
"af_dp":"app://home",
"adset_id":null,
"click_time":"2017-07-18 14:48:42.896",
"cost_cents_USD":"0",
"iscache":true,
"is_first_launch":1,
"af_cpi":null,
"af_sub1":"CD48704_",
"af_click_lookback":"1d",
"af_sub4":null,
"site_id":"1777236",
"adgroup_id":null,
"tag":"43fafd60-76ad-4a8f-9d1d-************",
"orig_cost":"0.0",
"af_prt":"expertagency",
"af_sub5":null,
"install_time":"2017-07-18 15:09:06.014",
"af_sub2":"network_KR_G001_iOS",
"clickID":"3gggBgAw2Bvxa8gR56ZA8Y3qjUy2gPkFgP6rA96s4e*******",
"freehand-param":"somevalue"
}
注意
值的顺序可能有所不同。可能添加其他值,恕不另行通知。
Facebook 激活示例
注意
Facebook广告系列没有归因链接。
在 Facebook 广告系列中定义的深度链接参数在 Facebook 以外不可用。这包括af_dp参数,该参数中含有应用的scheme路径。因此,要执行延迟深度链接,必须针对 Facebook 广告系列采用其他逻辑。将 Facebook 数据用于 getConversionData
响应,例如,广告系列、Adset、Adgroup 等,有计划地对用户进行重定向。
来自Facebook 的激活客户端返回结果如下:
{
"adset":"T:DAT-Desktop_O:All_L:AR-AE_A:All_R:1-30 Day",
"adgroup":"T:DAT-Desktop_O:All_L:AR-AE_A:All_R:1-30 Day",
"campaign_id":"6068535534218",
"af_status":"Non-organic",
"agency":null,
"af_sub3":null,
"af_siteid":null,
"adset_id":"6073532011618",
"is_fb":true,
"is_first_launch":true,
"click_time":"2017-07-18 12:55:05",
"iscache":false,
"ad_id":"6074245540018",
"af_sub1":null,
"campaign":"T:DAT_L:AR-AE",
"is_paid":true,
"af_sub4":null,
"adgroup_id":"6073532011418",
"is_mobile_data_terms_signed":true,
"af_channel":"Facebook",
"af_sub5":null,
"media_source":"Facebook Ads",
"install_time":"2017-07-19 08:06:56.189",
"af_sub2":null
}
{
"media_source":"Facebook Ads",
"campaign":"T:App Install_A:ALL",
"adset":"T:App Install_M:iOS_O:ALL_L:DE-DE_A:ALL",
"adgroup":"T:App Install_M:iOS_O:ALL_L:DE-DE_A:ALL_Banne",
"campaign_id":"6074766693717",
"af_status":"Non-organic",
"agency":null,
"af_sub3":null,
"af_siteid":null,
"adset_id":"6074767207317",
"is_fb":true,
"is_first_launch":true,
"click_time":"2017-07-17 16:23:18",
"iscache":false,
"ad_id":"6078076656717",
"af_sub1":null,
"is_paid":true,
"af_sub4":null,
"adgroup_id":"6074821181517",
"is_mobile_data_terms_signed":true,
"af_channel":"Facebook",
"af_sub5":null,
"install_time":"2017-07-18 15:10:50.190",
"af_sub2":null
}
在 SRN 中使用延迟深度链接
AppsFlyer始终会接收归因转化数据,并在首次启动时提供给app。
通过自归因广告平台(SRN) 点击深度链接/再营销广告从而安装应用的新用户,广告主也能通过归因转化数据,在app启动时,将用户重定向至app内的指定页面。
但是,针对 SRN,常规的 AppsFlyer 深度链接参数,如 af_dp,不会直接在客户端返回结果中呈现。要在app内实现同样的重定向功能,广告主的开发人员需要使用其他可用的参数(例如campaign,ad set或ad)来执行相应的逻辑。
示例
Greatapp 的市场同学吉美美 决定在 Facebook 上使用深度链接进行广告投放,受众定向为通投,可能包括新老用户。具体业务逻辑是,凡是点击该深度链接的用户,都会被重定向至app内的“领取奖金”页面。我们来看一下Greatapp的开发人员鹏哥是如何实现这个功能的。他通过AppsFlyer在客户端获取归因数据后执行了这样的逻辑:
1.首先判断激活是否来自Facebook ("is_fb=true")?
2. 如果为true, 继续获取adgroup参数值。
3.如果adgroup参数值包含单词奖金" Bonus "则将用户重定向至"领取奖金"页面。
也就是说,面对受众为新老用户的场景,客户可以用 Facebook 本身的深度链接,将老用户直接定向至活动页面,同时使用 AppsFlyer 的归因转化数据,使新用户拥有同样的跳转效果。