GA提供了多个维度的数据分析,覆盖了电商场景下所有的统计需求;
行为
屏幕
一级屏幕
二级屏幕
名词解释
- 二级屏幕:在一级屏幕基础上细分的二级屏幕;
- 屏幕名称:屏幕统计埋点,key的名字;
- 屏幕浏览量:屏幕pv,等于当前屏幕所有二级屏幕pv总和;
- 唯一屏幕浏览量:屏幕浏览会话数量(默认30分钟),等于当前屏幕所有二级屏幕会话数量的总和;
埋点位置
Android
- Fragment/Activity onStart ;
- 如果是ViewPager+Fragment结构,需要在切换Tab(点击/滑动)时添加统计;
iOS
- ViewController viewWillAppear;
- 如果是UIScrollView+ViewController结构,需要在切换Tab(点击/滑动)时添加统计;
- 从后台进入前台,需要添加统计;
埋点代码
Android
|
|
iOS
|
|
常见问题
一级屏幕浏览量 != 二级屏幕浏览量 (iOS)
埋点代码错误,tracker和builder都必须设置二级屏幕;
12tracker.set(GAIFields.customDimension(for: 1), value: dimensionVaue)//必须添加,否则会造成屏幕guard let builder = GAIDictionaryBuilder.createScreenView().set(dimensionVaue, forKey: GAIFields.customDimension(for: 1)) else {return}
屏幕浏览量大于实际浏览量 (iOS & Android)
- 产品列表展示统计造成的影响, 产品列表展示统计,会将当前一级屏幕及二级屏幕数据上传,解决方法是,强制将一级屏幕和二级屏幕参数值设置为空字符串;
Android解决方案
1234// 必须添加以下参数,否则产品列表不显示builder.set("&t", "screenview");builder.set("&cd", "");builder.set("&cd1", "");iOS解决方案
123// 特别注意:商品列表数据,必须强制设置屏幕及二级屏幕为空字符串,否则造成屏幕统计数量过多tracker.set(kGAIScreenName, value: "")tracker.set(GAIFields.customDimension(for: 1), value: "")
行为流
- 行为流,是根据屏幕统计数据分析,得出用户在App内所有操作行为的展现,可以查看每个交互行为的转化;
应用速度
使用场景
App启动时间的统计分析
- App启动时间,特别是冷启动时间对启动率和留存率的影响还是蛮大的;
- 可以将App启动时间做二级维度的拆分,冷启动时间、热启动时间、CPU数量、内存大小、系统版本等,为启动时间的优化提供数据支持;
API执行时间统计分析
- 服务端的API执行时间统计,是从接收到请求到处理完响应的过程,无法准确反应实际场景下用户的等待时间;在App端额外添加统计作为辅助;
- 可以对整个请求时间做二级维度的拆分,请求时间、响应时间、数据解析时间,做优化时有数据支持更有针对性;
其它业务场景下的统计分析
- 为版本迭代的效果提供数据依据,例如,订单确认页的改版是为了提高用户下单的效率,除了观察下单数量的变化外,在订单确认页到提交订单实际停留时间埋点更直接有效;
- 一些实际业务场景统计
- 订单确认页到提交订单实际停留时间;
- 购物流程耗时;
- 支付流程耗时;
- 注册流程耗时
- 分享流程耗时;
转化-电子商务
产品列表业绩
名词解释
- 在产品列表中获得的浏览次数:列表请求次数 x 列表中所有商品的总和;
- 在产品列表中获得的点击次数:点击进入详情页的次数;
- 产品结账次数:进入自定义的checkout流程的次数;目前是进入订单确认页的次数;
- 唯一身份购买次数:商品实际支付成功的次数;例如:一个订单包含多件单品,单品也可能来自不同的产品列表,支付成功后该数据会体现在不同的产品列表中;
- 产品收入:实际支付成功的金额;
产品列表展示
埋点位置
- 所有产品列表获取到网络数据,将新获取到的所有产品添加到统计;
- 首次进入页面获取数据;
- 请求数据失败重试;
- 下拉刷新;
- 加载更多;
埋点代码
Android
123456789101112131415161718192021222324252627282930313233/*** 跟踪产品列表* @param impressionList* @param statProducts*/protected static void trackProductList(String impressionList, List<StatProduct> statProducts){if (StringUtils.isNotEmpty(impressionList) && statProducts != null && statProducts.size() > 0){final HitBuilders.ScreenViewBuilder builder = new HitBuilders.ScreenViewBuilder();int productCount = 0;for (StatProduct statProduct : statProducts) {final Product product = toProduct(statProduct);builder.addImpression(product, impressionList);productCount ++;}if (LogUtils.DEBUG){LogUtils.d("跟踪产品列表==========>" + impressionList + ":" + productCount);}// 必须添加以下参数,否则产品列表不显示builder.set("&t", "screenview");builder.set("&cd", "");builder.set("&cd1", "");final Tracker t = getTracker();t.send(builder.build());if (BuildInfo.DEBUG){sendHitsNow();}}}iOS
12345678910111213141516171819202122232425262728293031323334353637public static func trackerItemProductList(className: String, impressionList: String, itemInfos: [ItemInfo]) {guard let builder = GAIDictionaryBuilder.createScreenView() else {return}var productCount = 0for item in itemInfos {if let title = item.title, let price = item.price, let id = item.itemId {productCount = productCount + 1addProductImpressionToBuilder(id: String(id), name: title, price: price, impressionList: impressionList, builder: builder)}}sendProductList(className, impressionList: impressionList, builder: builder, productCount: productCount)}private static func addProductImpressionToBuilder(id: String, name: String, price: Double, impressionList: String, builder: GAIDictionaryBuilder){let product = GAIEcommerceProduct()let customId = id + "-\(name)"product.setId(customId)product.setName(name)product.setBrand("xxx")product.setPrice(price as NSNumber!)builder.addProductImpression(product, impressionList: impressionList, impressionSource: nil)}private static func sendProductList(_ className: String, impressionList: String, builder: GAIDictionaryBuilder, productCount: Int){guard let tracker = getTracker() else {return}// 特别注意:商品列表数据,必须强制设置屏幕及二级屏幕为空字符串,否则造成屏幕统计数量过多tracker.set(kGAIScreenName, value: "")tracker.set(GAIFields.customDimension(for: 1), value: "")tracker.send(builder.build() as [NSObject : AnyObject])zzlog(" Google Analytics className --> \(className) 展示列表 -->|\(impressionList)| --> productCount:\(productCount)")}
产品点击
埋点位置
- 跳转到商品详情页点击事件;
埋点代码
Android
12345678910111213141516171819202122232425262728293031323334353637383940414243/*** 跟踪产品点击* @param impressionList* @param statProduct*/protected static void trackProductClick(String impressionList, StatProduct statProduct){if (StringUtils.isNotEmpty(impressionList) && statProduct != null){final Product product = toProduct(statProduct);final Tracker t = getTracker();if (LogUtils.DEBUG){LogUtils.d("跟踪产品点击==========>" + impressionList + ":" + statProduct.getName());}// detailfinal ProductAction detailAction = new ProductAction(ProductAction.ACTION_DETAIL).setProductActionList(impressionList);final HitBuilders.ScreenViewBuilder detailBuilder = new HitBuilders.ScreenViewBuilder().addProduct(product).setProductAction(detailAction);// 必须添加以下参数,否则产品列表不显示detailBuilder.set("&t", "screenview");detailBuilder.set("&cd", "");detailBuilder.set("&cd1", "");t.send(detailBuilder.build());// clickfinal ProductAction clickAction = new ProductAction(ProductAction.ACTION_CLICK).setProductActionList(impressionList);final HitBuilders.ScreenViewBuilder clickBuilder = new HitBuilders.ScreenViewBuilder().addProduct(product).setProductAction(clickAction);// 必须添加以下参数,否则产品列表不显示clickBuilder.set("&t", "screenview");clickBuilder.set("&cd", "");clickBuilder.set("&cd1", "");t.send(clickBuilder.build());if (BuildInfo.DEBUG){sendHitsNow();}}}iOS
12345678910111213141516171819202122232425262728293031323334353637383940public static func trackerProductClick(className: String, id: String, name: String, price: Double, impressionList: String){guard let tracker = getTracker() else {return}var product = GAIEcommerceProduct()let customId = id + "-\(name)"product.setId(customId)product.setName(name)product.setBrand("xxx")product.setPrice(price as NSNumber!)guard let builder = GAIDictionaryBuilder.createScreenView() else {return}builder.addProductImpression(product, impressionList: impressionList, impressionSource: nil)product = GAIEcommerceProduct()product.setId(customId)product.setName(name)product.setBrand("xxx")product.setPrice(price as NSNumber!)let action = GAIEcommerceProductAction()action.setAction(kGAIPADetail)action.setProductActionList(impressionList)builder.setProductAction(action)builder.add(product)//tracker.set(kGAIScreenName, value: className)let detailBuilderDict = builder.build() as [NSObject : AnyObject]tracker.send(detailBuilderDict)zzlog("detailBuilderDict:\(detailBuilderDict)")guard let clickBuilder = GAIDictionaryBuilder.createEvent(withCategory: "Ecommerce", action: kGAIPAClick, label: nil, value: nil) else {return}let clickAction = GAIEcommerceProductAction()clickAction.setAction(kGAIPAClick)clickBuilder.add(product)clickAction.setProductActionList(impressionList)clickBuilder.setProductAction(clickAction)let clickBuilderDict = clickBuilder.build() as [NSObject : AnyObject]tracker.send(clickBuilderDict)zzlog("clickBuilderDict:\(clickBuilderDict)")zzlog("Google Analytics \(name) 在 |\(impressionList)| 列表中被点击了")}
产品添加到购物车
埋点位置
- 添加商品到购物车成功;
埋点代码
- Android
iOS
1234567891011121314151617181920212223public static func trackerAddItemProductToCartList(className: String, quantity: Int, itemInfo: ItemInfo) {guard let tracker = getTracker() else {return}if let title = itemInfo.title, let price = itemInfo.price, let itemId = itemInfo.itemId {let product = GAIEcommerceProduct()let customId = String(itemId) + "-\(title)"product.setId(customId)product.setName(title)product.setBrand("xxx")product.setPrice(price as NSNumber!)product.setQuantity(quantity as NSNumber!)let action = GAIEcommerceProductAction()action.setAction(kGAIPAAdd)guard let builder = GAIDictionaryBuilder.createEvent(withCategory: "Ecommerce", action: kGAIPAAdd, label: nil, value: nil) else {return}builder.setProductAction(action)builder.add(product)//tracker.set(kGAIScreenName, value: className)tracker.send(builder.build() as [NSObject : AnyObject])zzlog(" Google Analytics 添加单品到购物车 --> |\(builder.build() as [NSObject : AnyObject])|")}GAIHelper.sendHitsNow()}
结账行为
可以查看结账流程每一步的转化率;
名词解释
- 带来交易的会话:实际支付成功的会话数(默认30分钟视为一个会话);
自定义Checkout流程
埋点位置
- 订单确认页页面初始化位置;
- 点击确认订单按钮后,提交订单成功;
- 支付按钮点击事件;
埋点代码
- Android
iOS
123456789101112131415161718192021222324public static func trackCheckoutStep(orderSn: String, orderPrice: Double, items: [Item]){guard let tracker = getTracker() else {return}guard let builder = GAIDictionaryBuilder.createEvent(withCategory: "Ecommerce", action: "Checkout", label: nil, value: nil) else {return}for item in items{if item.isSuite ?? false{if item.children != nil{for subItem in item.children{builder.add(createProduct(item: subItem))}}}else{builder.add(createProduct(item: item))}}// actionlet action = GAIEcommerceProductAction()action.setAction(kGAIPACheckout)// 根据checkout流程设置步骤数,目前为:1、2、3action.setCheckoutStep(1)builder.setProductAction(action)tracker.send(builder.build() as [NSObject : AnyObject])GAIHelper.sendHitsNow()}
Purchase
埋点位置
- App支付成功页面或服务端支付成功代码处;
埋点代码
- Android
iOS
1234567891011121314151617181920212223242526public static func onPurchaseCompleted(transactionId: String, revenue: Double, items: [Item]) {guard let tracker = getTracker() else {return}guard let builder = GAIDictionaryBuilder.createEvent(withCategory: "Ecommerce", action: kGAIPAPurchase, label: nil, value: nil) else {return}for item in items{if item.isSuite ?? false{if item.children != nil{for subItem in item.children{builder.add(createProduct(item: subItem))}}}else{builder.add(createProduct(item: item))}}// actionlet action = GAIEcommerceProductAction()action.setAction(kGAIPAPurchase)action.setTransactionId(transactionId)action.setRevenue(revenue as NSNumber!)builder.setProductAction(action)tracker.send(builder.build() as [NSObject : AnyObject])GAIHelper.sendHitsNow()zzlog(" Google Analytics 电子商务 支付完成:\(builder.build() as [NSObject : AnyObject])")}
购物行为
名词解释
- 已结账的会话:进入自定义checkout流程的会话数;
- 带来交易的会话:实际支付成功的会话数;
销售业绩
销售业绩,显示的是实际支付成功的订单;
订单列表
订单详情
产品业绩
产品业绩,显示的是实际支付成功的商品;
常见问题
产品列表业绩,无产品列表数据(Android)
|
|
产品列表业绩,无产品点击数据(Android)
|
|
产品列表业绩,出现大量noset
App端统计问题
- 根本原因是产品被添加到购物车或产品结账前必须执行了点击统计;
服务端统计问题
服务端在支付成功的统计中,将”t”对应的值写成了”pageview”,必须为”event”
1put("t", "event");//匹配的类型(订单支付成功就填这个) 必填
产品列表业绩,产品结账次数错误
- 产品结账次数,是进入checkout流程第一步的次数;如果有可能直接进入checkout流程第二步就会造成此数据不显示,应该考虑重构checkout流程;
结账行为,统计数据与实际数据差距较大
- GA默认120秒定时发送缓存的统计数据,如果用户走完购买流程后直接退出App可能造成数据统计不全;
解决方案
- 可将checkout流程、添加到购物车、Purchase放在服务端埋点;
App在checkout流程、添加到购物车、Purchase埋点时,主动调度GA发送数据;
12// 主动调度数据匹配GAI.sharedInstance().dispatch()App进入后台,主动调度GA发送数据;
123456789101112131415161718192021222324252627func applicationDidEnterBackground(_ application: UIApplication) {sendHitsInBackground()}func applicationWillEnterForeground(_ application: UIApplication) {GAI.sharedInstance().dispatchInterval = 120}private var dispatchHandler:((_ result: GAIDispatchResult ) -> ())?private func sendHitsInBackground() {var taskExpired = falselet taskId = UIApplication.shared.beginBackgroundTask {taskExpired = true}if (taskId == UIBackgroundTaskInvalid) {return}self.dispatchHandler = {[weak self] (result) inif (result == GAIDispatchResult.good && !taskExpired) {GAI.sharedInstance().dispatch(completionHandler: self?.dispatchHandler)}else{UIApplication.shared.endBackgroundTask(taskId)}}GAI.sharedInstance().dispatch(completionHandler: self.dispatchHandler)}
接入问题
怎么区分开发/生产环境
编译期区分生产/测试环境
- Android可以在BuildConfig中,根据编译环境动态配置GA AppKey;
- iOS可以通过DEBUG宏动态配置GA AppKey;
运行期区分生产/测试环境
- 日常测试,下载生产环境的App,通过更改DNS连接到测试服务器测试,可能会造成统计数据发送到生产环境;
- 将GA AppKey在服务端根据环境动态下发到App;
查看GA日志
Android
|
|
iOS
Android接入后Crash
java.lang.NoSuchMethodError: No static method zzb(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
in class Lcom/google/android/gms/common/internal/zzaa;
or its super classes (declaration of 'com.google.android.gms.common.internal.zzaa'
appears in /data/data/com.zaozuo.android/files/instant-run/dex/slice-com.google.android.gms-play-services-basement-9.2.0_838f224be8ca39a2f48a2f75d5178b0e700ad7c6-classes.dex)
at com.google.firebase.provider.FirebaseInitProvider.zza(Unknown Source)
at com.google.firebase.provider.FirebaseInitProvider.attachInfo(Unknown Source)
at android.app.ActivityThread.installProvider(ActivityThread.java:5595)
at android.app.ActivityThread.installContentProviders(ActivityThread.java:5168)
at android.app.ActivityThread.handleBindApplication(ActivityThread.java:5108)
at android.app.ActivityThread.-wrap2(ActivityThread.java)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1633)
at android.os.Handler.dispatchMessage(Handler.java:111)
at android.os.Looper.loop(Looper.java:207)
at android.app.ActivityThread.main(ActivityThread.java:5896)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:789)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:679)
参考资料:
- 开发文档:https://developers.google.com/analytics/?hl=en
- 调度文档:
https://developers.google.com/analytics/devguides/collection/ios/v3/dispatch#implement-sendHitsInBackground - 自定义维度和指标帮助文档:https://support.google.com/analytics/answer/2709828?hl=zh-Hans#scope
- Measurement Protocol 参数参考:
https://developers.google.com/analytics/devguides/collection/protocol/v1/parameters#events