背景
埋点是我们大力智能技术团队的日常开发中比较重要,但又是比较繁琐的一环。每次到埋点的时候,都有一个老码农在勤勤恳恳搬砖的既视感。嗯,这个按键是ctrl,这个按键是c,这个是按键ctrl,嗯,再来一个v。欧耶,成功了,召唤神龙!!!然而神龙并没有出现。what the f**k ,都已经2021年了为啥我还要一个一个把埋点事件Event的名字从文档搬到工程里?还得取个一模一样但是全部大写的变量名。还有那个Key和Value。每次我埋点的时候,你就不能主动一下告诉我,你是属于这个Event的吗?这个年代还不主动,你们是想单身一辈子吗?为了维护一个老码农的尊严。我决定和他们死磕到底。
最终效果
先上代码:
一键导入Event,Key,Value
导入前
public interface Track {
String KEY_FROM_PAGE = "key_from_page";
String PAGE_STAY = "page_stay";
String PAGE_ENTER = "page_enter";
interface Page {
}
interface Event {
}
interface Enter {
String PROFILE_DETAIL_ENTER = "profile_detail_enter";
}
interface KEY {
}
interface Value {
}
}
复制代码
导入后
public interface Track {
String KEY_FROM_PAGE = "key_from_page";
String PAGE_STAY = "page_stay";
String PAGE_ENTER = "page_enter";
interface Page {
String POINT_READING_RECORD = "point_reading_record";
}
interface Event {
String POINT_READING_RECORD_BOOK_ENTER = "point_reading_record_book_enter";
String POINT_READING_SETTING = "point_reading_setting";
String BIND_PUSH_REQUIRE = "bind_push_require";
String HARDWARE_BIND_FINISH = "hardware_bind_finish";
String POINT_READING_CHECK_RESULT = "point_reading_check_result";
String POINT_READING_CHECK_AGAIN = "point_reading_check_again";
String POINT_READING_GUIDE_CLICK = "point_reading_guide_click";
String POINT_READING_SUPPORT_BOOK_ENTER = "point_reading_support_book_enter";
String POINT_READING_DETAIL_ENTER = "point_reading_detail_enter";
String POINT_READING_FEEDBACK = "point_reading_feedback";
String POINT_READING_RECORD_ENTER = "point_reading_record_enter";
}
}
复制代码
一键生成埋点方法
生成前
class TapReadTrackCenter(val handler: ITrackHandler) {
}
复制代码
生成后
class TapReadTrackCEnter(val handler: ITrackHandler) {
fun logPOintReadingScaleEvent() {
//Track.Event.POINT_READING_SCALE.log (handler,
//Track.Key.STATUS to Track.Value ,
//Track.Key.ACTION to Track.Value
//)
}
fun logPointReadingSettingEvent() {
//Track. Event. POINT_READING_SETTING.log (handler,
//Track.Key.PAGE_ID to Track.Value ,
//Track.Key.IMAGE_URI to Track.Value ,
//Track.Key.B00K_ID to Track.Value ,
//Track.Key.PAGE_NO to Track.Value T ,
//Track.Key.RESULT to Track.Value
//)
}
fun logPointReadingFeedbackEvent() {
//Track. Event. POINT_READING_FEEDBACK.log(handler,
//Track.Key.PAGE_ID to Track.Value ,
//Track.Key.IMAGE_URI to Track.Value ,
//Track.Key.BOOK_ID to Track.Value ,
//Track.Key.PAGE_NO to Track.Value ,
//Track.Key.CONTENT_to Track.Value
//)
}
}
复制代码
我用人格发誓:上面的代码绝对不是我手打上去的。
实现思路
Live Templates
Live Templates是Android Studio提供给我们的一个很便利的工具。但是长期以来,它的使用场景仅限于:
- 输入psfi,生成public static final int
- 输入logd,生成HLogger.tag().d({“ “})
这让我想起了健身教练曾经说过的话:为什么中下斜方肌没有自体重训练动作?因为这两块肌肉力量太强大了,你用自体重训练你就是在侮辱他们。我深刻的感觉到Live Templates长期起来也受到了严重的侮辱。em…
Groovy
其实Live Templates是支持Groovy脚本的。有了Groovy脚本的支持,理论上其实我们能做的事情是无限多的。他就藏在我们可爱的Live Templates设置里面。
因此问题就变成了如何利用Groovy脚本来生成埋点的代码。显然,我们需要有埋点的一些信息。比如有哪些Event,Page,每个Page对应哪些Event等。看着显然包含这些信息但没有任何结构化的埋点doc文档,我的脑海里已经计划着要去勾搭哪个前端的汉子帮我搞定它。然而幸运的是,其实你想要的东西早就隐藏在某个角落里,只是你还没有发现它。
埋点管理平台
各个公司都有各自的埋点管理平台,埋点平台化之后我们就可以便捷提取结构化数据,类似数据库。
最终字节的埋点平台导出来的是一个excel文件,其中包含了埋点事件相关JSON格式的信息。对,你没看错。是JSON格式的。满满的幸福感~
当然,这里不局限于什么格式,我们可以根据每个公司现有埋点平台提供的基础能力,导出各自的结构化数据。重点是要把数据源,映射成相关埋点代码。
万事俱备
OK。现在我们所需要的一切都准备好了。接下来只需要ctrl+c, ctrl+v,啊不,我呸。接下来我们只要解析这个excel文件,并创建一个Live Template就行了。
解析Excel,生成辅助文件
1. 通过解析Excel文件,我们已经知道埋点所有的Page,Key,Event以及Value。在解析时就可以直接把这些信息填充到需要的类中。这一步我是用python实现的。
2. 生成一个Event和Key的映射关系文件
3. 生成一个Page和Event的映射文件
当然你也可以把2,3放在一个文件里面。我只是为了方便解析所以分开了。
创建一个Live Template
其中info的值为
groovyScript(“/Applications/Android\ Studio.app/Contents/plugins/android/lib/templates/pageEvent.groovy”, clipboard())
传入clipboard是为了接受我们当前想要埋点的事件属于哪个Page。于是,当我们在代码里输入page关键字后,就会去执行pageEvent.groovy脚本。这个脚本是我事先放在那里的(当然不是自己copy过去的。毕竟团队其他成员也要用它)。
pageEvent.groovy脚本的内容就不贴了。主要就是根据page名和我们之前生成的辅助文件,知道有多少个Event,每个Event对应哪些Key。然后按照一定规则拼接成代码段返回。
整体效果就是文章最开头贴的效果了。看起来一切都那么的美好,王子终于不用加班了,从此王子和公主幸福快乐的生活在一起了,直到永远。The End~
等等,我还得去上手动导出Excel?
懒是无止境的。我理解你。因为我也是。所以去埋点平台上手动导出Excel这种事情我是不会做的。这辈子我也不会做的。但是埋点平台又没有提供API给我调用。怎么办呢?嗯。。。这难不倒我。我还有selenium。通过模拟浏览器操作,我们顺利的解决了这个问题。虽然没有那么的优雅,但是比起被埋点支配的恐惧,这不算什么。
小贴士
为了简化埋点的代码,我们做了一个扩展方法。最终埋点的代码比较清晰,只需要关注Key和Value本身,而不需要各种繁琐的方法调用。
Track.Event.POINT_READING_SCALE.log(handler,
Track.Key.STATUS to status,
Track.Key.ACTION to action
)
复制代码
扩展方法代码如下
fun String.log(vararg pairs: Pair<String, Any>): Event {
return log(null, *pairs)
}
fun String.log(handler: ITrackHandler?, vararg pairs: Pair<String, Any>): Event {
val event = Event.create(this).apply {
pairs.forEach {
val (key, value) = it
params.put(key, value)
}
}
event.log(handler)
return event
}
复制代码
缺陷
这套流程在我们项目中经过数个版本的实践。整体上没有太大的问题。要说唯一的缺陷。。。大概就是推动DA在你埋点之前就在埋点平台上把埋点录入完成。嗯,我的建议是,如果你们的DA是男的,请派女的上。同理可得。
后记
藉由在埋点流程中的实践,本文对Live Templates如何在项目工程中发挥更大的作用进行了一部分探索。也希望各位大佬同事能够结合自己的经验分享更多的idea,提升开发效率。早点下班,( Ĭ ^ Ĭ )