客户端埋点的一些分析和体会

最近了解很多的关于埋点的相关文章,感受颇多,在此给大家分享一下。有不正确的地方还望各位指正。这篇文章主要分为3部分:

一、埋点的重要性

追踪用户在平台每个界面上的系列行为,事件之间相互独立(如打开商品详情页——选择商品型号——加入购物车——下订单——购买完成);
联合公司工程、ETL采集分析用户全量行为,建立用户画像,还原用户行为模型,作为产品分析、优化的基础。
无疑,数据埋点是一种良好的私有化部署数据采集方式。数据采集准确,满足了企业去粗取精,实现产品、服务快速优化迭代的需求。
但是,因手动埋点工程量极大,且一不小心容易出错,成为很多工程师的痛。且其开发周期长,耗时费力,很多规模较小的公司并不具备自己埋点的能力。无埋点成为市场新宠。最后埋点、无埋点两种技术谁能成为最后赢家,我们拭目以待。

二、埋点的终极方案

埋点的最终理想状态是:不用修改客户端,通过网络进行配置相关数据,就可以达到修改客户端的埋点数据。
原理如下图:111
具体原理:
服务端通过网络配置json数据表(服务端进行数据表的版本控制)
客户端每次开启数据的时候,都会发请求BIVersion,将获取到的版本号和本地存储的版本号进行比较。如果版本号大于本地保存的版本号,根据网络数据,更新本地数据。
客户端每次发送请求的时候,发送的相关数据,和数据结构配置,都通过读取配置表里面的数据(服务端保存的JSon数据)。这样就可以做到只通过网络端进行配置,就可以实现客户端的埋点上传数据更改了。之后维护全部都是维护网络上的数据表就可以了。不用再修改客户端

三、目前项目现状

目前版本的教师端埋点实现方案:

Android客户端,已经实现抽出一个工具类。埋点的时候只需要调用一下工具类方法,并且将需要上传的参数传过去就可以实现埋点。
优点:抽出一个公共方法,可以避免代码的冗余,如果需要修改某一个公共字段,实现了一定的数据解耦和逻辑解耦。
缺点:手动埋点工程量极大,且一不小心容易出错,成为很多工程师的痛。且其开发周期长,耗时费力。

重构版本的教师端埋点实现方案:

在抽出工具类的基础上,对页面进行封装,抽出一个基类,在基类的生命周期中,调用工具类方法,同时最大的改进是所有的埋点需要上传数据和页面埋点发送数据结构,全部都通过本地保存的数据配置表来动态获取,做到了只需要修改数据表,就可以实现BI埋点的更改。
优点:避免代码荣誉,进一步对代码进行了优化。如果需要修改某一个字段,只需要修改配置表就可以,简化了数据操作,对数据和逻辑进行了进一步的解耦,实现了无埋点的理论
缺点:由于目前服务端不支持网络配置表,所以如果配置表修改了,就需要跟新客户端才能获取到最新的数据据,不能做到动态的数据变更。

理想版本的教师端埋点实现方案:

详情请见第二条
优点:所有埋点数据全部从网络获取,可以远程的进行动态配置,远程进行埋点数据更新,无论客户端是否进行更新,都可以做到全部的客户端同时更新。
缺点:再设计数据结构之前,需要做较多的调研,从而设计出一套比较完善合理的网络配置表。如果数据结构变动比较大的话,可能会出现不兼容问题。

GreenDao教程2

总述:

所有的增删改查都需要通过greendao通过实体对象类生成的Dao来实现,

具体实现如下图

1、初始化数据库操作对象(GreenDao自动生成的操作对象)

2、通过数据库操作对象,进行增删改查操作

Tips

添加的记录需要初始化数据对象里面的数据

可以多次使用where(),进行多次筛选,也可以使用whereOr()语句,进行或语句查找

删除语句一般都是需要先进行一次查询,然后根据查询结果的list进行遍历,进行删除

修改语句一般都是需要先进行一次查询,然后根据查询结果的list进行遍历,对每一个对象进行相应的数据修改,之后再进行修改操作

 

安卓编码规范

1. 源文件基础

1.1 文件编码:UTF-8

源文件编码格式为 UTF-8。

2. 源文件结构

2.1 类声明

2.1.1 区块划分

建议使用注释将源文件分为明显的区块,区块划分如下

  1. 常量声明区
  2. UI控件成员变量声明区
  3. 普通成员变量声明区
  4. 内部接口声明区
  5. 初始化相关方法区
  6. 事件响应方法区
  7. 普通逻辑方法区
  8. 重载的逻辑方法区
  9. 发起异步任务方法区
  10. 异步任务回调方法区
  11. 生命周期回调方法区(出去onCreate()方法)
  12. 内部类声明区

2.1.2 类成员排列通用规则

  1. 按照发生的先后顺序排列
  2. 常量按照使用先后排列
  3. UI控件成员变量按照layout文件中的先后顺序排列
  4. 普通成员变量按照使用的先后顺序排列
  5. 方法基本上都按照调用的先后顺序在各自区块中排列
  6. 相关功能作为小区块放在一起(或者封装掉)

3. 格式术语

3.1 大括号

3.1.1 使用大括号(即使是可选的)

大括号与if, else, for, do, while语句一起使用,即使只有一条语句(或是空),也应该把大括号写上。

3.1.2 非空块:K & R 风格

对于非空块和块状结构,大括号遵循 Kernighan 和 Ritchie 风格 (Egyptian brackets):·

  • 左大括号前不换行
  • 左大括号后换行
  • 右大括号前换行
  • 如果右大括号是一个语句、函数体或类的终止,则右大括号后换行; 否则不换行。

例如,如果右大括号后面是else或逗号,则不换行。

示例:

return new MyClass() {

@Override public void method() {

if (condition()) {

try {

something();

} catch (ProblemException e) {

recover();

}

}

}

};

3.2 一行一个语句

每个语句后要换行。

3.2.1 变量声明

3.2.1.1 每次只声明一个变量

不要使用组合声明,比如int a, b;。

3.2.1.2 需要时才声明,并尽快进行初始化

不要在一个代码块的开头把局部变量一次性都声明了(这是c语言的做法),而是在第一次需要使用它时才声明。 局部变量在声明时最好就进行初始化,或者声明后尽快进行初始化。

3.2.2 switch语句

术语说明:switch块的大括号内是一个或多个语句组。
每个语句组包含一个或多个switch标签(case FOO:或default:),后面跟着一条或多条语句。

3.2.2.1 Fall-through:注释

在一个switch块内,每个语句组要么通过break, continue, return或抛出异常来终止,要么通过一条注释来说明程序将继续执行到下一个语句组, 任何能表达这个意思的注释都是OK的(典型的是用// fall through)。这个特殊的注释并不需要在最后一个语句组(一般是default)中出现。

示例:

switch (input) {

case 1:

case 2:

prepareOneOrTwo();        // fall through

case 3:

handleOneTwoOrThree();

break;

default:

handleLargeNumber(input);

}

3.2.2.2 default的情况要写出来

每个switch语句都包含一个default语句组,即使它什么代码也不包含。

3.2.3 注释

3.2.3.1 块注释风格

块注释与其周围的代码在同一缩进级别。它们可以是/ … /风格,也可以是// …风格。对于多行的/ … /注释,后续行必须从开始, 并且与前一行的对齐。

以下示例注释都是OK的。

/** This is // And so /* Or you can

*  okay. // is this. * even do this. */

*/

4. 命名约定

4.1 对所有标识符都通用的规则

标识符只能使用ASCII字母和数字,因此每个有效的标识符名称都能匹配正则表达式\w+。

4.2 标识符类型的规则

4.2.1 包名

包名全部小写,连续的单词只是简单地连接起来,不使用下划线。

采用反域名命名规则,全部使用小写字母。一级包名为com,二级包名为xx(可以是公司或则个人的随便),三级包名根据应用进行命名,四级包名为模块名或层级名。

例如:com.jiashuangkuaizi.kitchen

包名 此包中包含
com.xx.应用名称缩写.activity 页面用到的Activity类 (activitie层级名用户界面层)
com.xx.应用名称缩写.base 基础共享的类
com.xx.应用名称缩写.adapter 页面用到的Adapter类 (适配器的类)
com.xx.应用名称缩写.util 此包中包含:公共工具方法类(util模块名)
com.xx.应用名称缩写.bean 下面可分:vo、po、dto 此包中包含:JavaBean类
com.xx.应用名称缩写.model 此包中包含:模型类
com.xx.应用名称缩写.db 数据库操作类
com.xx.应用名称缩写.view (或者 com.xx.应用名称缩写.widget ) 自定义的View类等
com.xx.应用名称缩写.service Service服务
com.xx.应用名称缩写.receiver BroadcastReceiver服务

4.2.2 类名

类名都以UpperCamelCase风格编写。

类名通常是名词或名词短语,接口名称有时可能是形容词或形容词短语。现在还没有特定的规则或行之有效的约定来命名注解类型。

名词,采用大驼峰命名法,尽量避免缩写,除非该缩写是众所周知的, 比如HTML,URL,如果类名称中包含单词缩写,则单词缩写的每个字母均应大写。

描述 例如
Activity 类 Activity为后缀标识 欢迎页面类WelcomeActivity
Adapter类 Adapter 为后缀标识 新闻详情适配器 NewDetailAdapter
解析类 Parser为后缀标识 首页解析类HomePosterParser
工具方法类 Util或Manager为后缀标识(与系统或第三方的Utils区分)或功能+Util 线程池管理类:ThreadPoolManager日志工具类:LogUtil(Logger也可)打印工具类:PrinterUtil
数据库类 以DBHelper后缀标识 新闻数据库:NewDBHelper
Service类 以Service为后缀标识 时间服务TimeServiceBroadcast
Receiver类 以Receiver为后缀标识 推送接收JPushReceiver
ContentProvider 以Provider为后缀标识  
自定义的共享基础类 以Base开头 BaseActivity,BaseFragment

测试类的命名以它要测试的类的名称开始,以Test结束。

例如:HashTest 或 HashIntegrationTest。

接口(interface):命名规则与类一样采用大驼峰命名法,多以able或ible结尾,如

interface Runnable ;
interface Accessible。

注意:
如果项目采用MVP,所有Model、View、Presenter的接口都以I为前缀,不加后缀,其他的接口采用上述命名规则。

4.2.3 方法名

方法名都以 LowerCamelCase 风格编写。

方法名通常是动词或动词短语。

方法 说明
initXX() 初始化相关方法,使用init为前缀标识,如初始化布局initView()
isXX() checkXX() 方法返回值为boolean型的请使用is或check为前缀标识
getXX() 返回某个值的方法,使用get为前缀标识
handleXX() 对数据进行处理的方法,尽量使用handle为前缀标识
displayXX()/showXX() 弹出提示框和提示信息,使用display/show为前缀标识
saveXX() 与保存数据相关的,使用save为前缀标识
resetXX() 对数据重组的,使用reset前缀标识
clearXX() 清除数据相关的
removeXXX() 清除数据相关的
drawXXX() 绘制数据或效果相关的,使用draw前缀标识

下划线可能出现在JUnit测试方法名称中用以分隔名称的逻辑组件。一个典型的模式是:test_,例如testPop_emptyStack。

并不存在唯一正确的方式来命名测试方法。

4.2.4 常量名

常量名命名模式为CONSTANT_CASE,全部字母大写,用下划线分隔单词。那,到底什么算是一个常量?

每个常量都是一个静态final字段,但不是所有静态final字段都是常量。在决定一个字段是否是一个常量时,考虑它是否真的感觉像是一个常量。

例如,如果任何一个该实例的观测状态是可变的,则它几乎肯定不会是一个常量。只是永远不打算改变对象一般是不够的,它要真的一直不变才能将它示为常量。

// Constantsstatic final int NUMBER = 5;static final ImmutableListNAMES = ImmutableList.of(“Ed”, “Ann”);static final Joiner COMMA_JOINER = Joiner.on(‘,’); // because Joiner is immutablestatic final SomeMutableType[] EMPTY_ARRAY = {};enum SomeEnum { ENUM_CONSTANT }// Not constantsstatic String nonFinal = “non-final”;final String nonStatic = “non-static”;static final SetmutableCollection = new HashSet();static final ImmutableSetmutableElements = ImmutableSet.of(mutable);static final Logger logger = Logger.getLogger(MyClass.getName());static final String[] nonEmptyArray = {“these”, “can”, “change”};

这些名字通常是名词或名词短语。

4.2.5 非常量字段名

非常量字段名以LowerCamelCase风格的基础上改造为如下风格:

基本结构为scopeVariableNameType,

scope:范围

非公有,非静态字段命名以m开头。

静态字段命名以s开头。

公有非静态字段命名以p开头。

公有静态字段(全局变量)命名以g开头。

public static final 字段(常量) 全部大写,并用下划线连起来。

例子:

public class MyClass {

public static final int SOME_CONSTANT = 42;

public int pField;

private static MyClass sSingleton;

int mPackagePrivate;

private int mPrivate;

protected int mProtected;

public static int gField;

}

使用1字符前缀来表示作用范围,1个字符的前缀必须小写,前缀后面是由表意性强的一个单词或多个单词组成的名字,而且每个单词的首写字母大写,其它字母小写,这样保证了对变量名能够进行正确的断句。

4.2.6 参数名

参数名以LowerCamelCase风格编写

4.2.7 局部变量名

局部变量名以LowerCamelCase风格编写,比起其它类型的名称,局部变量名可以有更为宽松的缩写。

虽然缩写更宽松,但还是要避免用单字符进行命名,除了临时变量和循环变量。

即使局部变量是final和不可改变的,也不应该把它示为常量,自然也不能用常量的规则去命名它。

临时变量

临时变量通常被取名为i,j,k,m和n,它们一般用于整型;c,d,e,它们一般用于字符型。 如: for (int i = 0; i < len ; i++),并且它和第一个单词间没有空格。

4.2.8 类型变量名

类型变量可用以下两种风格之一进行命名:

  • 单个的大写字母,后面可以跟一个数字(如:E, T, X, T2)。
  • 以类命名方式(5.2.2节),后面加个大写的T(如:RequestT, FooBarT)。

4.2.9 资源文件命名规范

  1. 资源布局文件(XML文件(layout布局文件)):

全部小写,采用下划线命名法
1) contentview 命名
必须以全部单词小写,单词间以下划线分割,使用名词或名词词组。
所有Activity或Fragment的contentView必须与其类名对应,对应规则为:
将所有字母都转为小写,将类型和功能调换(也就是后缀变前缀)。
例如:activity_main.xml

2) Dialog命名:dialog_描述.xml
例如:dialog_hint.xml

3) PopupWindow命名:ppw_描述.xml
例如:ppw_info.xml

4) 列表项命名:item_描述.xml
例如:item_city.xml

5) 包含项命名:模块_(位置)描述.xml
例如:activity_main_head.xml、activity_main_bottom.xml
注意:通用的包含项命名采用:项目名称缩写_描述.xml
例如:xxxx_title.xml
2. 资源文件(图片drawable文件夹下):
全部小写,采用下划线命名法,加前缀区分
命名模式:可加后缀 _small 表示小图, _big 表示大图,逻辑名称可由多个单词加下划线组成,采用以下规则:
用途_模块名_逻辑名称
用途_模块名_颜色
用途_逻辑名称
用途_颜色
说明:用途也指控件类型(具体见UI控件缩写表)
例如:
btn_main_home.png 按键
divider_maket_white.png 分割线
ic_edit.png 图标
bg_main.png 背景
btn_red.png 红色按键
btn_red_big.png 红色大按键
ic_head_small.png 小头像
bg_input.png 输入框背景
divider_white.png 白色分割线
如果有多种形态如按钮等除外如 btn_xx.xml(selector)

名称 功能
btn_xx 按钮图片使用btn_整体效果(selector)
btn_xx_normal 按钮图片使用btn_正常情况效果
btn_xx_pressed 按钮图片使用btn_点击时候效果
btn_xx_focused state_focused聚焦效果
btn_xx_disabled state_enabled (false)不可用效果
btn_xx_checked state_checked选中效果
btn_xx_selected state_selected选中效果
btn_xx_hovered state_hovered悬停效果
btn_xx_checkable state_checkable可选效果
btn_xx_activated state_activated激活的
btn_xx_windowfocused state_window_focused
bg_head 背景图片使用bg_功能_说明
def_search_cell 默认图片使用def_功能_说明
ic_more_help 图标图片使用ic_功能_说明
seg_list_line 具有分隔特征的图片使用seg_功能_说明
sel_ok 选择图标使用sel_功能_说明

注意:

使用AndroidStudio的插件SelectorChapek可以快速生成selector,前提是命名要规范。

  1. 动画文件(anim文件夹下):

全部小写,采用下划线命名法,加前缀区分。
具体动画采用以下规则:
模块名_逻辑名称
逻辑名称
refresh_progress.xml
market_cart_add.xml
market_cart_remove.xml
普通的tween动画采用如下表格中的命名方式
// 前面为动画的类型,后面为方向

动画命名例子 规范写法
fade_in 淡入
fade_out 淡出
push_down_in 从下方推入
push_down_out 从下方推出
push_left 推向左方
slide_in_from_top 从头部滑动进入
zoom_enter 变形进入
slide_in 滑动进入
shrink_to_middle 中间缩小
  1. values中name命名
类别 命名 示例
strings strings的name命名使用下划线命名法,采用以下规则:模块名+逻辑名称 main_menu_about 主菜单按键文字friend_title 好友模块标题栏friend_dialog_del 好友删除提示login_check_email 登录验证

dialog_title 弹出框标题

button_ok 确认键 loading 加载文字

 

colors colors的name命名使用下划线命名法,采用以下规则:模块名+逻辑名称 颜色 friend_info_bg friend_bg transparent gray
styles styles的name命名使用 Camel命名法,采用以下规则:模块名+逻辑名称 main_tabBottom
  1. layout中的id命名

命名模式为:view缩写_view的逻辑名称
使用 AndroidStudio 的插件 ButterKnife Zelezny,生成注解非常方便。
如果不使用 ButterKnife Zelezny,则建议使用 view 缩写做后缀,如:username_tv(展示用户名的TextView)

5. 编程实践

5.1 @Override:能用则用

只要是合法的,就把@Override注解给用上。

5.2 捕获的异常:不能忽视

除了下面的例子,对捕获的异常不做响应是极少正确的。(典型的响应方式是打印日志,或者如果它被认为是不可能的,则把它当作一个 AssertionError 重新抛出。)

如果它确实是不需要在catch块中做任何响应,需要做注释加以说明(如下面的例子)。

try {

int i = Integer.parseInt(response);

return handleNumericResponse();

} catch (NumberFormatException ok) {

// it’s not numeric; that’s fine, just continue

}return handleTextResponse(response);

例外:在测试中,如果一个捕获的异常被命名为expected,则它可以被不加注释地忽略。下面是一种非常常见的情形,用以确保所测试的方法会抛出一个期望中的异常,因此在这里就没有必要加注释。

try {

emptyStack.pop();

fail();

} catch (NoSuchElementException expected) {

}

6. Javadoc

6.1 格式

6.1.1 一般形式

Javadoc块的基本格式如下所示:

/**

* Multiple lines of Javadoc text are written here,

* wrapped normally…

*/

public int method(String p1) { … }

或者是以下单行形式:

/** An especially short bit of Javadoc. */

基本格式总是OK的。当整个Javadoc块能容纳于一行时(且没有Javadoc标记@XXX),可以使用单行形式。

6.1.2 段落

空行(即,只包含最左侧星号的行)会出现在段落之间和Javadoc标记(@XXX)之前(如果有的话)。

除了第一个段落,每个段落第一个单词前都有标签<p>,并且它和第一个单词间没有空格。

6.1.3 Javadoc标记

标准的Javadoc标记按以下顺序出现:@param, @return, @throws, @deprecated,

前面这4种标记如果出现,描述都不能为空。 当描述无法在一行中容纳,连续行需要至少再缩进4个空格。

6.2 摘要片段

每个类或成员的Javadoc以一个简短的摘要片段开始。这个片段是非常重要的,在某些情况下,它是唯一出现的文本,比如在类和方法索引中。

这只是一个小片段,可以是一个名词短语或动词短语,但不是一个完整的句子。它不会以A {@code Foo} is a…或This method returns…开头,它也不会是一个完整的祈使句,如Save the record…。然而,由于开头大写及被加了标点,它看起来就像是个完整的句子。

Tip:
一个常见的错误是把简单的Javadoc写成 /** @return the customer ID */,这是不正确的。它应该写成/** Returns the customer ID. */。

6.3 哪里需要使用Javadoc

至少在每个public类及它的每个public和protected成员处使用Javadoc,以下是一些例外:

6.3.1 例外:不言自明的方法

对于简单明显的方法如getFoo,Javadoc是可选的(即,是可以不写的)。这种情况下除了写”Returns the foo”,确实也没有什么值得写了。

单元测试类中的测试方法可能是不言自明的最常见例子了,我们通常可以从这些方法的描述性命名中知道它是干什么的,因此不需要额外的文档说明。

Tip:
如果有一些相关信息是需要读者了解的,那么以上的例外不应作为忽视这些信息的理由。例如,对于方法名getCanonicalName,

就不应该忽视文档说明,因为读者很可能不知道词语canonical name指的是什么。

6.3.2 例外:重载

如果一个方法重载了超类中的方法,那么Javadoc并非必需的。

6.3.3 可选的Javadoc

对于包外不可见的类和方法,如有需要,也是要使用Javadoc的。如果一个注释是用来定义一个类,方法,字段的整体目的或行为,那么这个注释应该写成Javadoc,这样更统一更友好。

附录:

表1 UI控件缩写表

控件 缩写 例子
LinearLayout ll llFriend或者mFriendLL
RelativeLayout rl rlMessage或mMessageRL
FrameLayout fl flCart或mCartFL
TableLayout tl tlTab或mTabTL
Button btn btnHome或mHomeBtn
ImageButton ibtn btnPlay或mPlayIBtn
TextView tv tvName或mNameTV
EditText et etName或mNameET
ListView lv lvCart或mCartLV
ImageView iv ivHead或mHeadIV
GridView gv gvPhoto或mPhotoGV

表2 常见的英文单词缩写:

名称 缩写
icon ic (主要用在app的图标)
color cl(主要用于颜色值)
divider di(主要用于分隔线,不仅包括Listview中的divider,还包括普通布局中的线)
selector sl(主要用于某一view多种状态,不仅包括Listview中的selector,还包括按钮的selector)
average avg
background bg(主要用于布局和子布局的背景)
buffer buf
control ctrl
delete del
document doc
error err
escape esc
increment inc
infomation info
initial init
image img
Internationalization I18N
length len
library lib
message msg
password pwd
position pos
server srv
string str
temp tmp
window wnd(win)

程序中使用单词缩写原则:不要用缩写,除非该缩写是约定俗成的。