某些情况下我们需要对小程序某些用户的行为进行数据进行统计,比如统计某个页面的UV, PV等,统计某个功能的使用情况等。好让产品对于产品的整个功能有所了解。 在网页里,我们很多人都用过谷歌统计,小程序里也有一些第三方数据统计的库, 比如腾讯的MTA等等。 但是,第三方的数据统计库要么功能太简单,满足不了需求,要么就是要收费。(留下了贫穷的泪水。) 等等,又不是你出钱,怕啥? 贵一点就贵一点呀。
嗯,说的没错。但是,公司团队内部想实现一套完整的自己的数据统计系统以满足自己的需求。所以,还是没有用第三方的。
所以,具体要统计些啥?
产品经理
想知道用户都是怎么进入我们的小程序的?
用户在我们小程序里那个页面停留的时间最长?平均用户停留时间是多少?
想知道我们最近开发的那个功能用的人多不多?
想统计小程序里的一些按钮有多少用户点击了
开发自己
总是很难复现用户端出现的bug,
要是可以知道用户端发生错误时,知道用户当时的用的手机型号,微信版本,网络环境,页面参数,和错误信息就好了
想知道我们小程序启动时间是多少?
接口在用户端的平均响应时间是多少ms? 哪些接口报错了
针对产品经理的需求,我们可以知道,Ta想要的是就是数据统计要实现的功能。对于开发来说,我们关注的更多就是错误统小程序性能这块的东西。
好,到这里,我们需求是明白了。就是要实现一套既能统计普通的埋点数据,也要能统计到小程序里一些特殊触发的事件,比如appLaunch, appHide 等,还要可以统计错误。
好,那先来看看如何实现产品的需求吧
用户进入小程序可以在 小程序 onLaunch 回调里拿到参数 的scene 值,这样就可以知道用户是怎么进入小程序的了。小case, 难不到我。
嗯,第一个需求实现了,那如何统计第二个呢?如何统计某个页面的停留时间呢?
这也难不倒我,用户在进入页面时会触发onShow 事件, 同样,在离开页面(或者切后台时)会触发onHide事件,我只需要在onShow里记录一下时间,同时在onHide 里也记录一下时间,把两个时间一减就可以了。
Page({ data: { beginTime: 0, endTime: 0 }, onShow: function() { // Do something when page show. this.setData({ beginTime: new Date().getTime() }) }, onHide: function() { // Do something when page hide. let stayTime = new Date().getTime() - this.beginTime; // 这个就是用户在这个页面的停留时间了 }, })
等等,这样确实实现了需求,万一产品要统计所有也面的停留时长? 那我们岂不要在每一个页面都这样写一遍?有没有更好的方法呢?
好,接下来就是数据统计实现的要点了,即拦截微信原生事件,这样可以在某个特殊事件触发时,做一些我们统计的事情。同时,还要拦截微信发生网络请求的方法,这样可以拿到网络请求相关的数据,最后,为了能统计到错误,还需要拦截微信发生错误的方法。
1.特殊事件的监听
App(Object object)
注册小程序。接受一个 Object 参数,其指定小程序的生命周期回调等。
App() 必须在 app.js 中调用,必须调用且只能调用一次。不然会出现无法预期的后果。
拦截全局的事件:
下面是小程序官方文档对于App 注册方法的文档:
App({ onLaunch (options) { // Do something initial when launch. }, onShow (options) { // Do something when show. }, onHide () { // Do something when hide. }, onError (msg) { console.log(msg) }, globalData: 'I am global data' })
假如我们要在小程序onLaunch 时打印一句hello Word,我们有哪些方法实现?
方法1:
直接写在onLaunch方法里
onLaunch (options) { console.log('hello World') }
方法2:
使用 monkey patch方法猴子补丁(monkey patch)
猴子补丁主要有以下几个用处:
在运行时替换方法、属性等
在不修改第三方代码的情况下增加原来不支持的功能
在运行时为内存中的对象增加patch而不是在磁盘的源代码中增加
举个栗子,假如我们在console.log 方法里都先打印出当前的时间戳,我们可以这样:
var oldLog = console.log console.log = function() { oldLog.call(this, new Date().getTime()) oldLog.apply(this, arguments) }
同理,我们针对onLaunch 进行猴子补丁
var oldAp = App App = function(options) { var oldOnLaunch = options.onLaunch options['onLaunch'] = function(t) { // 做一些我们自己想做的事情 console.log('hello word....') // 调用原来的onLaunch 方法 oldOnLaunch.call(this, t) } // 调用原来的App 方法 oldApp(options) // 想像一下,小程序内部调用onLaunch 方法应该是这样子的: options.onLaunch(params) } // 问题,有的时候,我们可能没有注册某一个事件,比如页面的onShow, 所有,我们在替换的时候还需要判断一下参数是否传了对应的方法 Page({ onLoad (options) {}, onHide (options) {} }) // 针对这种情况,我们需要这样写 var oldPage = Page Page = function(options) { if (options['onShow']) { // 如过有注册onShow 这个回调 var oldOnShow = options.onShow // onShow 方法调用时都是 传了一个对象 options['onShow'] = function(t) { // doSomething() oldOnShow.call(this, t) } } // 调用原来的Page 方法。 oldPage.apply(null, [].slice.call(arguments)) // 注意: 下面这两种写都会报错: VM23356:1 Options is not object: {"0":{}} in pages/Badge.js 问题具体原因暂时未找到。 // oldPage.call(null, arguments) // oldPage(arguments) }