app无埋点SDK设计思路前瞻与源码实现初步
app无埋点SDK设计思路前瞻与源码实现初步
前言
在大数据时代,数据本身显得愈发重要,而app本身的数据统计一直存在某些方面的局限,无论是第三方友盟等统计平台,还是后台直接对数据库和接口的使用情况进行的统计,已经不能完全满足企业的需要。
- QCon已经有人发表过App无埋点SDK设计思路,但是并未奉献源码,同样第三方GrowingIO也实现了无埋点,但感觉业务关联性不是很够,当然也没有具体的源码实现。
- 从友盟的使用情况来看,目前公司对数据的统计应该是在接口及后台数据方面,偏重于后端数据,对前端用户行为并未全量收集。
目录
概述
无埋点SDK设计思路
- 任务分解
- 用户属性
- 用户model分类
- 调用时机
- 页面属性
- 页面内容
- 页面view的唯一标识
- 页面数据获取
- 页面行为
- 页面事件SDK
- Gradle插件插桩
思路实现流程总结
概述
本文主要分解下android端无埋点实现的任务,及几个主要技术的实现思路,可能面临的难点。最后分享下此思路的实现源码GitHub地址,然后一起开发
读此文章前最好通读下面两篇文章
- Android无埋点数据收集SDK关键技术
- Android AOP之字节码插桩
核心一点来讲,埋点主要是记录用户用户的一系列行为,及行为之前的页面及相应数据情况,然后行为触发时将相应的页面及核心数据提交到后台,然后后台进行数据分析及数据价值挖掘
无埋点并不是不埋点而是对传统埋点方式的简化
- 传统埋点过渡依赖开发人员 没有测试进行严格测试很容易出现埋点错误或者漏埋的情况,
- 利用第三方友盟也是有局限的(tips:许多公司对第三方的埋点也并未充分利用)
为了统计分析的需要,对用户行为的每一个事件进行埋点布置,并对这些数据结果进行分析,进一步优化产品或指导运营。
总之来讲,我们目的就是要 通过上传给后台的一条条数据,可以唯一确定用户行为,什么时间发生了什么事(这样大数据平台就可以图形化统计已有数据,并挖掘潜在数据价值)
个人思考
前端全量采集数据 后端可以进行数据分析
后端可以精确的知道前端什么时间发生了什么事
这样的分析对产品迭代 用户画像 课程设计等都是很有帮助的
任务分解
- 用户属性
- 页面属性(pagepath)
- 页面分组属性 (pagegroup)
- 页面初始化数据(pagedata)
- 页面内容
- 页面元素(viewid viewpath)
- 页面数据 (viewdata)
- 页面行为
- 触发了哪些事件 (viewevent)
- 发生了什么异常 (exception)
- 数据处理及上传
- 最低限度影响性能以及兼容性思考
一、用户属性
本部分内容相对简单,定义规范,开发实现还是相对简单的,但是一定要精确,因为后面的页面数据会直接关联此部分数据,否则会影响后台数据统计与分析。
- 本地用户model存储上传
- 自然属性,比如性别、出生年月等。
- 账户属性,比如等级、类型标签等。
- 行为特征,比如是否有过购买记录之类。
- 调用时机
- 手动登录
- 自动登录
- 注册()
- 退出
二、页面属性统计
通过activity fragment的创建方法中传入页面引用 并手动设置
GrowingIO growingIO = GrowingIO.getInstance();
growingIO.setPageGroup(this, "视频列表页");
growingIO.setPS1(this,
Preferences.getInstance(KooCet.getInstance()).getCetType()+"");
问题:友盟等埋点平台一般是传入页面的引用来统计页面的访问情况,因为前端开发人员一般会复用页面,因此这样的统计跟业务关联的强度不够,并未绑定页面内所属数据,无法对同一个页面进行业务分组,更不能对后续用户行为进行有效分析。
解决:参考GrowingIO我们目前实现思路也基本如此,毕竟这是涉及具体业务的东西,app中所有的访问页面进行分组,具体需要业务人员(产品经理,前端开发、大数据统计相关人员具体讨论): 1:根据进入来源的不同即可进行页面分组 并根据页面来源传入数据 进行属性定义,类似GrowingIO 2:没有传入数据 可以根据页面自身属性(比如设置、关于我们、个人中心等)定义
有利于数据分析
三、页面内容统计
一、页面view元素统计
viewId viewPath的统计可以查看文章。 问题:这里的难点就是viewId viewPath的唯一确定,只有唯一了,才能知道前端发生了什么行为,页面上拥有过什么内容记录,大数据后台才能进行更好的有效分析。 demo尚未调研实现:项目还未真正实战,所以目前并未去详细调研具体唯一确定页面元素的精确实现
二、页面数据统计
目前demo只是统计页面中所有的EditText Button TextView中的数据(至于ListView等控件中的数据后期继续调研) 此处可根据Android自带View树递归查找并获取元素上的数据进行提取 具体可参考robotium实现,用过robotium的应该知道,该框架可以通过一个字符串去查找页面内是否有相应的控件包含该字符串 具体看代码实现主要是这两个类 以查找activity中所有的TextView为例: ViewFetcher.java实现
// 获取所有的View
public <T extends View> ArrayList<T> getCurrentViews(Class<T> classToFilterBy, boolean includeSubclasses, View parent) {
ArrayList<T> filteredViews = new ArrayList<T>();
List<View> allViews = getViews(parent, true);
for(View view : allViews){
if (view == null) {
continue;
}
Class<? extends View> classOfView = view.getClass();
if (includeSubclasses && classToFilterBy.isAssignableFrom(classOfView) || !includeSubclasses && classToFilterBy == classOfView) {
filteredViews.add(classToFilterBy.cast(view));
}
}
allViews = null;
return filteredViews;
}
Searcher.java类中去过滤TextView
public <T extends TextView> T searchFor(final Class<T> viewClass, final String regex, int expectedMinimumNumberOfMatches, final long timeout, final boolean scroll, final boolean onlyVisible) {
if(expectedMinimumNumberOfMatches < 1) {
expectedMinimumNumberOfMatches = 1;
}
final Callable<Collection<T>> viewFetcherCallback = new Callable<Collection<T>>() {
@SuppressWarnings("unchecked")
public Collection<T> call() throws Exception {
sleeper.sleep();
ArrayList<T> viewsToReturn = viewFetcher.getCurrentViews(viewClass, true);
if(onlyVisible){
viewsToReturn = RobotiumUtils.removeInvisibleViews(viewsToReturn);
}
//判断是否属于TextView
if(viewClass.isAssignableFrom(TextView.class)) {
viewsToReturn.addAll((Collection<? extends T>) webUtils.getTextViewsFromWebView());
}
return viewsToReturn;
}
};
try {
return searchFor(viewFetcherCallback, regex, expectedMinimumNumberOfMatches, timeout, scroll);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
一般TextView需要 text,viewPath,或者认为ID ImageView需要 drawable路径,viewPath,或者Id
四、页面行为统计
一、页面触发事件
本SDK设计的主要思路:当用户在页面上产生交互行为时,记录当前页面的所有页面元素、内容及异常、崩溃等信息。并在合适的时机上传此信息。
用户一般产生的页面事件:单击、双击、长按、滑动,在android中即为 onClick onLongClick onScroll 等。当发生这些事件时,我们可以动态获取当前页面的viewdata viewpath等所有数据,并将此事件行为进行关联,然后记录。 问题:这显然不能交给前端开发人员暴力的动态去插桩这些代码。
解决:通过修改字节码的方式,可以避免直接对代码的侵入,gradle 从1.5开始,gradle插件包含了一个叫Transform的API,这个API允许第三方插件在class文件转为为dex文件前操作编译好的class文件,这个API的目标就是简化class文件的自定义的操作而不用对Task进行处理,并且可以更加灵活地进行操作。我们如何注入一个Transform呢,很简单,实现Transform抽象类中的方法,使用下面的两个方法之一进行注入即可。
android.registerTransform(theTransform)
android.registerTransform(theTransform, dependencies)
通过集成growingio 然后编译发现,growingio做了大量的插桩,及代码替换, 插桩:点击等事件 代码替换:Toast webchromclient等 之前研究热更新时使用的nuwa就实现了此机制,通过研究nuwa等其它gradle插件源码可以获取到你想要的东西,目前我们的demo这条线基本摸索成功。
二、系统及网络异常
- 系统奔溃异常捕获可以通过获取系统默认的UncaughtException处理器,并重新实现即可记录。
- 网络、数据库、下载库等自定义的异常行为需要实现统一的行为记录接口。
思路实现总结
开发阶段:(无埋点sdk集成及部分调用)
- 在有限的几个节点记录用户属性,
- 每个页面的初始阶段通过业务属性数据设置页面分组属性,
- app中的自定义异常实现统一的日志记录接口。
编译阶段:(无埋点gradle插桩无埋点sdk代码)
gradle插件通过配置文件等查找指定包内的事件、弹框等代码,并进行插桩,插入的代码可以获取页面元素及相关内容,并进行记录
后台分析阶段:
通过分析前端上传的用户属性下的所有用户行为,挖掘用户价值。
最后奉献下我们的源码实现Carry项目, 此项目调研任务进度及使用方法都有说明,欢迎一起进行技术交流
技术探讨
联系QQ: 378640336