MapReduce工作机制——Word Count实例(一)

MapReduce的考虑是分布式计算,也尽管是分而治之,并行计算提高速度。

前言

是因为日益明朗的精细化运营需要,网易乐得从去年始于构建大数目平台,<<无埋点多少搜集SDK>>因此立项,用于向那个数量平台供全量,完整,准确的客户端数据.
  <<无埋点数据搜集SDK>>Android端从着手,到经验重构,逐步全面及今天早就生抢一年的年月了.期间于开源社区以及同行被取了有些老有意义之技巧参考,因此于斯SDK趋于完美之今天,我们呢设想以立即一头在技术上的探索经验与得到分享出来.

  1. 4月16-18日,QCon京城2017天下软件开发大会齐起同事表示Android/IOS两端进行统一的技能分享,欢迎大家面前失去交流
  2. 俺们见面日渐整理一些技术文章到是简书账号“移动端数据搜集及剖析”

前关于Android端的<<无埋点数据收集SDK>>使用的技术,写了一样首文章<<Android
AOP之许节码插桩>>,这个是Android端进行整收集的起点,我们就算之所以是主意轻松将到各种"Hook"点之.
  本篇文章虽然随即说一下有关收集SDK内部募集逻辑的局部关键技术.


编程思想

先是,要将数据抽象为键值对的花样,map函数输入键值对,处理后,产生新的键值对作为中结果输出。接着,MapReduce框架自动将中等结果按键做聚合处理,发给reduce函数处理。最后,reduce函数以键和对应之价值的聚合作为输入,处理后,产生其它一样雨后春笋键值对作为最后输出。后面会构成实例介绍任何经过。

目录

一、概述
1.1 SDK数据收集能力现状
1.2 关键技术点概述
其次、View的绝无仅有标识(ID)
2.1 调研
2.2 利用ViewTree构建ViewID
2.3 ViewPath的生成
2.4 ViewPath的优化
老三、页面的细分
3.1 合理划分页面的最主要
3.2 Android中之页面
3.3 页面名组成
季、无需埋点轻松收集定制的事务数据
4.1 配置示范
4.2 无埋点采访流程
4.3 数据路径(DataPath)
五、结语


运行条件

预先不考虑用YARN的状况,那个时段MapReduce的运作环境就是YARN,此处我们讨论的凡达标时环境。

一、概述

据有首先简要介绍一下咱的集方案时得收集及什么样数据,然后对本文重点介绍的其三个技术点开展概述.

TaskTracker

slave的角色,负责申报心跳和执行命令。一个集群有差不多个TaskTracker,但一个节点才来一个,TaskTracker和DataNode运行在同一节点。

1.1 SDK数据搜集能力现状

时咱们的SDK进行数量搜集时中心发生半点独力量:

a. 通用数据全量收集
  通用数据据的是与事务无关的用户作为数据,无论是电商以还是社区下,接入SDK后通用数据的集及且是无差的,这些通用数据约有:

事件 描述
冷启动事件 App第一次启动时的,版本号、设备ID、渠道、内存使用情况,磁盘使用情况等信息
前后台事件 App进入前台或者后台
页面事件 页面(Activity或Fragment)显示(Show)/隐藏(Hide)
控件点击事件 某个控件(包括页面上控件和弹窗中控件)被用户点击
列表浏览事件[可选] 某个列表的哪些条目被用户浏览了
位置事件[可选] 上报用户地理位置信息
其它事件 省略描述

b. 业务相关数据需求通过下配置进行无埋点定制收集
  除了上述通用数据,与具体事务有关的数据搜集。拿网易贵金属的首页举个例子:

希冀1-1 无埋点采访工作数据示例

借而需要以用户点击上图吉利框区域时,把“粤贵银”这个交易品的ID(或者下方显示的指数等,只要在内存中是的数码都得)一起报上来。
  对于这个种植需求,数据搜集SDK做到了无需埋点切莫依靠开发周期,通过线达下一些配备信息,即可尽管经常展开数据搜集。具体原理第四节省讲述。

JobTracker

master的角色,负责任务调度和集群资源监察,不介入计算。根据TaskTracker周期性发来之中心跳信息,考虑TaskTracker的资源剩余量、作业优先级等等,为该分配合适的职责。

1.2关键技术点概述

a. View的唯一标识(ID),(详见本文第二节省)
  当我们搜集控件数据经常相遇的首先个问题就是是:如何把界面及之另一个View与另外View区分开来.

按照:某个Button被点击了
咱们当报告数据的早晚用拿这个Button和其余兼具控件(比如另一个Button,另一个ImageView等)区分开来,这样就条反馈的数量才能够代表"就是不行Button被点击了一晃".

即即用吗界面及之每一个控件生成一个唯一的ID.
此ID除了有着区分性,还需要用于一致性一致性大凡跟一个View无论界面布局如何动态变化,或者说反复进去同一页面,此ID需要保持无变.

b. 页面的撤并,(详见本文第三省)
  除了Activity有些Fragment也欲当作页面,这便要求:

  • 当Fragment show/hide时呈报有关页面事件.
  • 页面Fragment中来的用户交互事件为急需归于此Fragment页面,即点击某个View需要上报页面Fragment的音(从View中怎么抱Fragment信息?)

c. 无需埋点轻松收集定制的政工数据,(详见本文第四节)
  如前方所陈述,默认情况下多少搜集SDK会收集全量的用户交互数据,对于定制的政工收集需求,数据搜集SDK也成功了无需代码埋点,通过线达颁发一些布局进行立马收集


Word Count实例

亚、View的绝无仅有标识(ID)

环境

  • Java 1.7
  • Hadoop 2.7
  • Maven 3.3
  • Intellij IDEA 2016.3
  • Windows 10

题主在合龙开发条件下写了Word Count程序,配置的pom.xml如下:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>WordCount</groupId>
    <artifactId>Hadoop</artifactId>
    <version>1.0-SNAPSHOT</version>

    <dependencies>
        <dependency>
            <groupId>org.apache.hadoop</groupId>
            <artifactId>hadoop-common</artifactId>
            <version>2.7.0</version>
        </dependency>
        <dependency>
            <groupId>org.apache.hadoop</groupId>
            <artifactId>hadoop-mapreduce-client-core</artifactId>
            <version>2.7.0</version>
        </dependency>
        <dependency>
            <groupId>org.apache.hadoop</groupId>
            <artifactId>hadoop-mapreduce-client-common</artifactId>
            <version>2.7.0</version>
        </dependency>

    </dependencies>

</project>

2.1 调研

用以区分界面及每个View的ID? Android系统是否提供于了咱这ID?

委,Android系统提供了一个ID,view.getId()即可获取一个int型的id用于区分View,但是这ID因为以下简单个由也连无能够满足我们的需要.

  1. 出相当一部分view是NO_ID,比如当布局文件中无指定id,或者直接在代码里面new出来view,view.getId()返回的全部都是NO_ID
  2. 其一ID是无安宁的,由于这个ID其实就是历次编译产生的R文件中的int常量,因此与一个按钮,两独版编译出来的ID很可能时时不相同的.

因而,我们只好协调动手构建我们的ID喽,怎么构建?答案是下所属Page+ViewTree构建ViewID.

编码

  1. Mapper类

    Mapper类的4个泛型分别表示:map函数输入键值对之键的近乎,map函数输入键值对的价值的类似,map函数输有键值对的键的好像,map函数输有键值对的价值的近乎。

    map函数部分,key是LongWritable类型,表示该行;value是Text类型,表示行的始末;Context类的write(Text
    key, IntWritable value)将中等结果输出。

    package com.hellohadoop;
    
    import org.apache.hadoop.io.IntWritable;
    import org.apache.hadoop.io.LongWritable;
    import org.apache.hadoop.io.Text;
    import org.apache.hadoop.mapreduce.Mapper;
    
    import java.io.IOException;
    import java.util.StringTokenizer;
    
    /**
     * Created by duyue on 2017/7/13.
     */
    public class TokenizerMapper  extends Mapper<LongWritable, Text, Text, IntWritable> {
    
        // 直接把单词的个数设置成1, 认为出现了1次
        private final static IntWritable one = new IntWritable(1);
        private Text word = new Text();
    
        public void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException {
            // 每行文本拆分成单个单词
            StringTokenizer itr = new StringTokenizer(value.toString());
            while (itr.hasMoreTokens()) {
                word.set(itr.nextToken());
                // 每个单词(忽略重复)的个数都为1
                // 即,出现两次"good"会写入两次"good",而不会认为"good"出现了2次
                context.write(word, one);
            }
        }
    }
    
  2. Reducer类

    Reducer类的4个泛型表示:reduce函数输入键值对的键的好像,reduce函数输入键值对之值的切近(与map函数输出对应),reduce函数输出键值对的键的接近,reduce函数输出键值对之价值的类似。

    reduce函数部分:接收及之参数形若:<key, List<value>>,因为map函数把key值相同(同一单词)的有value都发送给reduce函数,统计后输出结果。

    package com.hellohadoop;
    
    import org.apache.hadoop.io.IntWritable;
    import org.apache.hadoop.io.Text;
    import org.apache.hadoop.mapreduce.Reducer;
    
    import java.io.IOException;
    import java.util.Iterator;
    
    /**
     * Created by duyue on 2017/7/13.
     */
    public class IntSumReducer extends Reducer<Text, IntWritable, Text, IntWritable> {
    
        private IntWritable result = new IntWritable();
    
        public void reduce(Text key, Iterable<IntWritable> values, Context context) throws IOException, InterruptedException {
            int sum = 0;
            for (IntWritable val : values) {
                sum += val.get();
            }
            result.set(sum);
            context.write(key, result);
        }
    }
    
  3. 编写main函数

    package com.hellohadoop;
    
    import org.apache.hadoop.conf.Configuration;
    import org.apache.hadoop.fs.Path;
    import org.apache.hadoop.io.IntWritable;
    import org.apache.hadoop.io.Text;
    import org.apache.hadoop.mapreduce.Job;
    import org.apache.hadoop.mapreduce.lib.input.FileInputFormat;
    import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat;
    
    import java.io.IOException;
    
    /**
     * Created by duyue on 2017/7/13.
     */
    public class WordCount {
        public static void main(String[] args) throws IOException, ClassNotFoundException, InterruptedException {
            Configuration conf = new Configuration();
            Job job = new Job(conf, "word count");
            job.setJarByClass(WordCount.class);
            job.setMapperClass(TokenizerMapper.class);
            job.setReducerClass(IntSumReducer.class);
            job.setOutputKeyClass(Text.class);
            job.setOutputValueClass(IntWritable.class);
            FileInputFormat.addInputPath(job, new Path(args[0]));
            FileOutputFormat.setOutputPath(job, new Path(args[1]));
            System.exit(job.waitForCompletion(true) ? 0 : 1);
        }
    }
    

2.2 利用ViewTree构建ViewID

以Android的定义里,每个Window(ActivityWindow/DialogWindow/PopupWindow等)上面还生在相同株ViewTree.而屏幕中视底各种控件(ImageView/Button等)都是这株ViewTree上之节点.
  有Android开发条件之同桌才需要打开AndroidDeviceMonitor-dump view
hierarchy 就可以看到ViewTree的形容,如下图:

图2-1 ViewTree概念图

于是,我们萌生出一个想方设法:

利用Page+ViewTree中的位置构建ViewID.

View以ViewTree中的职主要为此有限接触来规定:

  • 纵向的纵深
  • 横向的index

考虑当下片单因素后,我们定义一个ViewPath:

ViewPath:当前view到ViewTree根节点的如出一辙漫长路径,用于在ViewTree中唯一定位当前view。路径中之每个节点包含两部分信息,即节点View类型信息,以及节点View在兄弟中之index。

如下图,是一个简约的ViewTree模型(简单到深度只生半点叠,每层只有两三单控件)

图2-2 ViewTree模型图

遵照事先给的定义,上图中控件1,2,3,4之ViewPath如下

控件1ViewPath: RootView/LinearLayout[0]   index为1表示此节点是兄弟节点中第一个控件
控件4ViewPath: RootView/LinearLayout[0]/ChildView1[0]
控件2ViewPath: RootView/RelativeLayout[1]
控件3ViewPath: RootView/LinearLayout[2]

上述被有的ViewPath中,每个节点(除了首节点)有个别组成部分情节:

  • LinearLayout,RelativeLayout,ChildView1等ViewType信息(节点View的类型
  • “[]”内的index信息,此index指示此节点是手足节点的第几个

就是前期的ViewPath,用ViewPath定位view,有星星点点碰特别重大:

  • 一致性: 同一个view的ViewPath在ViewTree的动态变化中应维持无换
  • 区分度: 不同view的ViewPath应该例外

照这最初的ViewPath定义在实践中还免可知当一致性与区分度上满足我们的需,后面会指向ViewPath进行优化。

运作程序

这边主要因让事先Maven依赖的包,为了成功展示日志文件,需要在resources包着上加log4j.properties文件,位置如下图:

log4j.properties配置.PNG

文本配置:

log4j.rootLogger=debug, stdout, R

log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout

log4j.appender.stdout.layout.ConversionPattern=%5p - %m%n

log4j.appender.R=org.apache.log4j.RollingFileAppender
log4j.appender.R.File=firestorm.log

log4j.appender.R.MaxFileSize=100KB
log4j.appender.R.MaxBackupIndex=1

log4j.appender.R.layout=org.apache.log4j.PatternLayout
log4j.appender.R.layout.ConversionPattern=%p %t %c - %m%n

log4j.logger.com.codefutures=DEBUG

配置Configuration如图:

Configuration配置.png

团结若创input文件夹,并以在Project
Structure中安装为Excluded类型。在input文件夹下创造需要统计单词数的文本,位置如下图:

Input文件夹位置

题主统计的是莎士比亚红的十四执行诗的Sonnet 18,运行程序后:

运作后的状态图

其中part-r-00000饱受保留了统计结果,图太长,截了同一组成部分:

统计结果

上述就是Word Count实例在Idea下运行的情形

下期主:MapReduce编程涉及到的API

2.3 ViewPath的生成

地方我们由构建ViewID的急需引出了ViewPath的定义,那么当彼此事件(例如:按钮点击)发生时,我们怎么样颇成者控件的ViewPath?
  如达到一致首文章<<Android
AOP之许节码插桩>>所述,当用户点击某个按钮时,我们插入OnClickListener.OnClick方法吃之如下代码用会让调用:

Monitor.onViewClick(view);    

上面,入参view即为目前叫点击的view,获取之view的ViewPath伪代码如下:

  public static ViewPath getPath(View view) {
    do {
      //1. 构造ViewPath中于view对应的节点:ViewType[index]
      ViewType=view.getClass().getSimpleName();
      index=view在兄弟节点中的index;
      ViewPath节点=ViewType[index];
    }while ((view=view.getParent())instanceof View);//2. 将view指向上一级的节点
  }

结构出的ViewPath如下面例子所示:

DecorView/LinearLayout[0]/FrameLayout[0]/ActionBarOverlayLayout[0]/ContentFrameLayout[0]/FrameLayout[0]/LinearLayout[0]/ViewPager[0]/ButtonFragment[0]/AppCompatButton[0]

2.4 ViewPath的优化

a. 一致性优化1
情景:

于祈求2-2
ViewTree模型图中,如果像下图备受所显示,在控件2和3负动态插入一个FrameLayout呢?

贪图2-3 Android界面动态性变化情景1

这儿据原始ViewPath的定义,我们来看望控件3之ViewPath发生了如何变化?

ViewTree动态变化前: RootView/LinearLayout[2]
ViewTree动态变化后: RootView/LinearLayout[3]

优化:

ViewPath节点中index的含义从“兄弟节点的第几单”优化为:“相同档次兄弟节点的第几独”

优化后,发生图2-3所示界面布局动态变化时,控件3的ViewPath变化为:

ViewTree动态变化前: RootView/LinearLayout[1]   index为1表示此节点是兄弟节点中第二个LinearLayout
ViewTree动态变化后: RootView/LinearLayout[1]

好观看,此处优化使控件3的ViewPath在ViewTree动态插入除了LinearLayout之外其它任何项目时犹保持前后一致。

b. 一致性优化2
情景:

每当图2-2
ViewTree模型图中,如果如下图被所出示,在控件2和3着动态插入一个LinearLayout时,控件3的ViewPath能否继续保持前后一致?

按照上述场景,控件3ViewPath的变更如下:

ViewTree动态变化前: RootView/LinearLayout[1]   index为1表示此节点是兄弟节点中第二个LinearLayout
ViewTree动态变化后: RootView/LinearLayout[2]   前面插入一个LinearLayout导致此节点变为兄弟节点中第三个LinearLayout了

问题
上述现象指的实在是一个问题:ViewTree中以及种兄弟节点动态变化(插入/移除/移位)影响ViewPath的一致性

  • ViewPath节点中之index,在和种类(ViewType相同,例如都是LinearLayout)兄弟节点动态加入/删除时,当前节点的index无法以扭转前后保持一致。
  • “一致性优化1”中的优化得敌不同门类兄弟节点的熏陶,却对同类型兄弟节点的影响无可奈何

由ViewPath的定义及麻烦找到以同色兄弟节点动态变化前后保持一致的方式,但咱可分析产生这个种界面动态变化的状况:

  1. 使用Fragment的动态布局
      Android界面的动态布局有状况中,使用Fragment实现界面动态变化的效率与熏陶控件数量还是于好之(相对于直接addView())
  2. ListView(等只是复用View)中同类型的itemViews。
      此种植情况则尚未产生在一个itemView前动态插入一个itemView,但是由itemView的复用,导致itemView著的情节以及于父节点listView内之index的呼应关系动态变化,因此为归入此类。

2丁所说“ListView等只是复用View”造成的问题后会出优化,此处针对1遭受的景讨论。1蒙受场景发生常一旦下图:

图2-4 使用Fragment造成界面动态性的气象

上图中FragmentA,FragmentB,FragmentC的顶层视图控件全部凡LinearLayout同类型),此时立刻三单Fragment加入的依次拿导致ViewPath在此间各种非等同,从而致使ViewPath在动态变化前后未可知保持一致(如前方:ViewTree动态变化前后控件3ViewPath的变更所示)。
优化:

于ViewPath节点中,使用Fragment的讳替换ViewType

优化后,发生图2-4所示界面布局动态变化时,控件3的ViewPath变化为:

ViewTree动态变化前: RootView/FragmentB[0]   index为0表示此节点是兄弟节点中第一个FragmentB
ViewTree动态变化后: RootView/FragmentB[0]  

如若齐,此次优化使得,在顶层视图ViewType相同之Fragment动态增长/删除到ViewTree时,ViewPath在变前后保持一致。

c. 对可复用View的优化
情景
  以极端经常以的ListView为例,假设有一ListView满屏一味显示3独章,那么是ListView可能只有3单分支控件(ItemView),而这ListView上滑动之后方可显示100项内容
  这3单ItemView与100桩内容是一律对准几近之附和关系,而且映射并无可靠规律。
  这,我们希望ViewPath可以区分这100宗显示的情条目,而未单纯区分3个ItemView

点情景中之题目可用下图表达:

图2-5 可复用View的ViewPath区分性优化

设若达到图被,内容条目1及4都是为此itemView1来表现的,按照事先的ViewPath定义,图2-5丁各个内容条目的ViewPath如下:

内容条目1: ListView/ItemView[0]   index为0表示此节点是兄弟节点中第一个ItemView
内容条目4: ListView/ItemView[0]   
内容条目2: ListView/ItemView[1]  
内容条目3: ListView/ItemView[2]  

得看到内容条目1暨4之ViewPath区分无开。此种植问题可以总结也:

著内容与ViewTree中之控件不是逐一对应的景况导致基于ViewTree的ViewPath区分度不够

  • 而复用View,比如:ListView,RecyclerView,Spinner等,呈现出来子View的数目及实际子View的数码未必相同
  • ViewPager设置缓存页面数为1,第二页显示时,第二个页面顶级View其实是ViewPager的率先单ChildView。此种状态为会造成显示内容(第二页)与ViewTree中之控件(第一单ChildView)不对应的状况。

从而我们于ViewPath作如下优化:

ViewPath节点的index取内容的第几件,而非第几只ItemView。

优化:
优化后图2-5遭遇逐条内容条目的ViewPath如下:

内容条目1: ListView/ItemView[0]   index为0表示此节点是ListView显示的第一个内容条目
内容条目4: ListView/ItemView[3]   
内容条目2: ListView/ItemView[1]  
内容条目3: ListView/ItemView[2]  

足见,之前ViewPath无法区分的始末条目1以及4现在可区分别了。各种可复用View取内容之第几宗的代码方法如下:

ListView,Spinner等AdapterView------------ListView.getPositionForView(itemView)
RecyclerView------------------------------------RecyclerView.getChildAdapterPosition(itemView)
ViewPager----------------------------------------ViewPager.getCurrentItem()

d. ViewPath起点优化
  ViewPath从ContentView为起点,而非DecorView

  • DecorView : Window上的根视图,ViewTree中之清,最顶层视图
  • ContentView:
    客户端程序员定义之备视图的父节点,如Actvity中常见的setContentView(view)

一个实际上被之ViewPath如下:

DecorView/LinearLayout[0]/FrameLayout[0]/ActionBarOverlayLayout[0]/ContentFrameLayout[0]/FrameLayout[0]/LinearLayout[0]/ViewPager[0]/ButtonFragment[0]/AppCompatButton[0]

上面的“ContentFrameLayout[0]”此节点代表的即使是ContentView,程序员在xml或者代码里面构建的View都以ContentView中。

从DecorView到“ContentFrameLayout[0]”的即时同一段落Path是Android系统Framework层决定的,理论及应有是一致的,但是出于碎片化等原因或许ViewPath的当下同段子发生变化.在实践中,我们吧发现确发生局部Rom发生了此类情况,但是比率很小.
  为了挡住这种可能导致和一个View在不同装备及产生ViewPath不同的景象,ViewPath的起点定义在ContentView比较好.如上面的ViewPath可优化为:

ContentView/FrameLayout[0]/LinearLayout[0]/ViewPager[0]/ButtonFragment[0]/AppCompatButton[0]#mybutton

做法:
  构造每一个ViewPath节点时可以取view.getId(),看看id的packageId部分是匪是网的(系统资源id以16进制的0x01,0x00从头),如果是,生成ViewPath时屏蔽这段就是可.


老三、页面的撤并

3.1 合理划分页面的第一

页面在Android中针对应于Activity和一些Fragment(比如很多app首页多tab的宏图,若每个tab是采取Fragment实现之,那么这种tab一般作为一个页面).页面的划分很关键,因为个别触及:

  1. 对页面,需要得到Show/Hide两独会,在这机上报页面Show/Hide事件,非页面则未需要
  2. 页面的划分关系在用户交互事件的所属,例如,按钮点击事件上报格式如下:
事件名称 所属页面 ViewPath 其他属性
ButtonClicked MainActivity XXX 省略

表中之"所属页面"代表本次按钮点击事件时有发生在MainActivity中.将互事件归属为页面这样针对性后面无论是进行路径分析还是统计控件点击量分布且起十分特别之好处.

3.2 Android中的页面

Android中日常需要当作页面的有Activity和Fragment(对于如全屏Dialog或者全屏的View暂勿考虑).对于Activity,上省被涉及的点滴沾还不行轻办到.

a. Activity页面

  1. 从今Application.ActivityLifecycleCallbacks的onActivityResumed/onActivityPaused这点儿独回调方法就是足以独家获Activity页面Show/Hide的会,并于此时机上报相应页面事件
  2. 互动归属的Activity页面可以通过Context轻松获得,例如上篇稿子<<Android
    AOP之许节码插桩>>论及,当按钮点击时,会沾我们插桩的代码:

Monitor.onViewClick(view)

入参view即为咱点击的view,通过view.getContext()我们一般就可以拿走这View所属的Activity,伪代码如下:

//从View中利用context获取所属Activity的名字
public static String getActivityName(View view) {
    Context context = view.getContext();
    if (context instanceof Activity) {
      //context本身是Activity的实例
      return context.getClass().getSimpleName().;
    } else if (context instanceof ContextWrapper) {
      //Activity有可能被系统"装饰",看看context.base是不是Activity
      Activity activity = getActivityFromContextWrapper((ContextWrapper) context);
      if (activity != null) {
        return activity.getClass().getSimpleName();
      } else {
        //如果从view.getContext()拿不到Activity的信息(比如view的context是Application),则返回当前栈顶Activity的名字
        return currentActivityName;
      }
    }
    return "";
  }

b. fragment页面
  相对于Activity,将或多或少Fragment看作页面的逻辑就要有点复杂一些了.这其中涉及下面几乎单问题:

  • 如何Fragment可以要当作页面?
      这是内需人工决策的,机器做不了此决定.
      时我们是人工干预是交由用户研究团体,所有Fragment截图等消息均显示在凉台达成,由用研同事选择要作为页面的那些,用研选择的结果以自动化配置到SDK中
  • 哪得到Fragment页面的Show/Hide页面事件?
      由于fragment使用状况比较多样,单单靠OnResume/OnPause两个回调表示fragment
    Show/Hide是休纯粹的,比如:
    场景一
      首页一个Activity承载多独Fragment
    Tab的状态,此时tab间切换并无见面触发Fragment的OnResume/OnPause.接触的回调函数是onHiddenChanged(boolean
    hidden)

    场景二:
      一个ViewPager承载多个页面的Fragment时
        a.当第一只Fragment1显示时,虽然第二独Fragment2这从未亮,但是Fragment2的OnResume却跟履行,处于resumed的状态.
        b.ViewPager页面切换OnResume/OnPause/onHiddenChanged均无接触,触发的回调是setUserVisibleHint
      这会儿判断Fragment Show/Hide应该据此setUserVisibleHint,而非OnResume/OnPause
      如前同首文章XXX,所述,我们由此插桩的法子Hook到了fragment的如下生命周期函数用于包装成Show/Hide事件:

onResume()
onPause()
onHiddenChanged(boolean hidden)
setUserVisibleHint(boolean isVisibleToUser)

运这几个回调包装成适用于各种气象的FragmentShow/Hide事件的伪代码如下:

//此回调发生,则证明是场景一中使用情景,
  onHiddenChanged(boolean hidden) {
    hidden == true ------FragmentShow
    hidden == false------FragmentHide
  }
//场景二中ViewPager页面切换时触发Fragment的此回调,
  setUserVisibleHint(boolean isVisibleToUser) {
    if (fragment.isResumed()) {//只有resumed状态的fragment适用此情景
      isVisibleToUser == true ------FragmentShow
      isVisibleToUser == false------FragmentHide
    }
  }
//上述使用情景之外的一般场景
  OnResume/OnPause{
 //fragment没有被hide,并且UserVisibleHint为可见的情景
    if (!fragment.isHidden() && fragment.getUserVisibleHint()) {
      OnResume ------ FragmentShow
      OnPause  ------ FragmentHide
    }
  }
  • 如何将Fragment内部的互动归属到Fragment页面,也就是说如何当相互发生时从view实例拿到Fragment页面的名(像前以到Activity页面名字如出一辙)?
      view可以通过context拿到Activity的消息,但是却无路子以到fragment的援。那么,当有View交互发生,我们以需要获得Fragment页面名字的景象下,我们只能优先用Fragment页面名写副者View的属性被。
      做法大致如下:
        a.
    按照前同首文章xxx里面的法子,在Fragment.OnCreateView方法的最终插桩,拿到return的view(即为之Fragment的顶层视图)
        b. 判断是Fragment是否让指定为Fragment页面,如果是,下一样步
        c.遍历以Fragment的顶层视图为根节点的ViewTree,
    Fragment名设置及者ViewTree的各个一个view上。设置法如下所示:

view.setTag(0xff000001, fragmentName);

注意:View类有星星点点个称呼也setTag的方法

public void setTag(final Object tag)

以此措施,类中用同样Object对象存储tag,protected Object mTag =
null;。listAdapter中时时用于安装holder。我们这边用之莫是其一,勿会见为斯所以法冲突

public void setTag(int key, final Object tag)

以此办法,类中有平等稀疏数组存储tag,private SparseArray<Object>
mKeyedTags;
  tag的key官方推荐资源id,因此我们好选用类似0xff000001之类的app用非顶之资源id进行tag存储以避免冲突
    d. 当用以Fragment名时,如下调用即可取得:

view.getTag(0xff000001)

3.3 页面名组成

眼前说了将互动事件(比如点击事件)归属到某个一个页面的措施是:

当互相事件备受安一个字段,值为页面名称。

页面可以是Activity或者Activity承载的Fragment,我们的页面名称组成如下:

Activity类名[Activity别名][Fragment类名][Fragment别名]

证如下:

  1. “[]”内之部分是可选的,可能有或没。另外,各个部分之间出分隔符分割。
  2. 页面名成中,Activity的描述(类名/别名)是第一叠,Fragment的叙述(类名/别名)是第二重合
  3. 号的面世是为了解决只依靠类名无法精确区分页面的一些情况,比如:
    每当某个电商以中,“商品详情页”(同一个Activity)用于展示各种货品(iphone,电视等),如果用拿“不同商品之商品详情页“区分成不同页面来统计pv等指标的话,需要装别名,如:

商品详情页#iphone
商品详情页#电视

对于别名的安,需要程序员在业务代码里面(如Activity.OnCreate,Fragment.onCreate等)显式设置.


季、无需埋点轻松收集定制的事情数据

4.1 配置示范

事先涉嫌过,数据收集SDK可以经过部署下发即经常募集定制的多寡,那么以Android端这个是怎做到的为?
率先,看一下颁发的安排样例:

//第一部分:描述
PageName:MainActivity
ViewPath:DecorView/.../ViewPager[0]/ButtonFragment[0]/AppCompatButton[0]
EventType:ViewClick
//第二部分:数据路径(当描述符合时,按照此路径取数据)
DataPath:this.context.demoList[5]

方例子翻译成多少需求就是:

1. 当页面(MainActivity)
2. 中的控件(DecorView/.../ViewPager[0]/ButtonFragment[0]/AppCompatButton[0])
3. 发生点击事件(ViewClick)时
4. 按照路径(this.context.demoList[5])取出数据
5. 并附加到点击事件上面一起上报

以此描述,我们还可以描述如下等等各种数据需求:

当(某页面)发生事件(Show)时,按照路径(xxx)取出数据,并附加到页面Show事件上面一起上报

总下描述的组成部分,如下:

第一层 第二层 含义
描述部分 页面 限定页面
ViewPath 限定按钮
EventType 限定时机(点击/前台/PageShow)
数据路径 一种DSL,指示目标数据在内存中的位置(可理解为“引用路径”)

4.2 无埋点采访流程

上节显示了用来无埋点定制业务数据收集的配备,那么SDK收到这么的一律份配置如何最终把想要之数搜集上来吗?

  • 步骤一:生原始事件。比如点击时采访,当点击时见面硌我们插桩的代码,并转原始之点击事件

Monitor.onViewClick(view)
  • 步骤二:匹配配置
    在onViewClick方法被匹配下发的布信息,看看Page,ViewPath是否跟时view匹配,EventType是否以及目前事变类匹配,若匹配则展开下同样步
    注:ViewPath的配合可以生标准匹配与歪曲匹配,精确匹配时一个ViewPath精确匹配唯一一个控件.模糊匹配时一个ViewPath可配合多单控件,例如可以用用一个ViewPath模糊匹配一个列表中的具有久目.
  • 手续三:按照数据路径(DataPath)逐级反射拿到对象数据,并将找到的数量附在原始的点击事件及展开反映。

4.3 数据路径(DataPath)

上述手续三进展多少搜集主要是按照DataPath的讲述进行(例如示例中提到的"this.context.demoList[5]"),DataPath是一律种植我们用来采集定制数据要定义之等同栽DSL.含义如下:

a. 含义

DataPath:
指向设搜集的靶子数的一模一样修引用路径,解析这个路并逐级反射最终将到对象数据.

DataPath写法被之局部重大字(符):

关键字(符) 含义
. 表示对象所属关系,如:a.b 表示实例a中的字段b
.() 表示公有方法调用,如:a.b() 表示调用实例a中的方法b.注意:方法入参可以是DataPath指向的Object
[] 数组/线性表的index. 注意:此index可以是常量数字,也可以是一个DataPath指向的数字
this DataPath字符串的起点,表示起点为当前实例(当前View)
item DataPath字符串的起点,表示起点为当前View父节点中AdapterView adapter中当前条目. 常用于列表中的数据获取
parent DataPath节点中的关键字,用于表示当前view的parentView.效果同view.getParent(),使用此关键字可减少视图引用中的反射
childAt(x) DataPath节点中的关键字,用于表示当前view的第x个childView.效果同view.getChildAt(x),使用此关键字可减少视图引用中的反射

b. 应用示范
  下面用少单例说明如何自DataPath找到对象数据.

图4-1 DataPath示例

演示1:列表数据获得
  上图备受显得是一个列表,红框中凡是列表的率先个条目.那么,如果我们怀念如果在列表中条目点击时,将列表展示的交易品ID(或者合作方ID)等非以界面及出示如果又有让内存中的数据从点击事件反映.此处DataPath该怎么形容?

item.productId

DataPath解释:

  1. 起点定为"item",则意味之后ListView(或者RecylerView)绑定的Adapter中即数item为起点取数据.
    假设此ListView绑定的Adapter如下:

public class DemoAdapter extends BaseAdapter {
  private ArrayList<DataItem> mDataItems;
  ......
}

尽管此处”item”代表的虽是mDataItems[x] (x表示目前被点击条目的itemId)

2."productId"是model类DataItem中代表"交易品ID"的字段名称.

由此DataPath获取数据:

  1. 当第x章被点击时,如果发现有配合的配备,对于起点为”item”的DataPath,先经过view.getParent找到上层ListView实例,然后经listView.getAdapter()获得绑定的Adapter实例,最后通过Adapter.getItem(ListView.getPositionForView(itemView))得到数码遭到第x独item,即mDataItems[x]
  2. 映获取mDataItems[x]受的productId字段,即可获第x只条目的"交易品ID",将这个ID跟随第x条条框框的点击事件进行汇报即可.

实例2:界面数据获得
  同样时眼热4-1所出示,加入我们怀念在列表中条目点击时,拿条目中显得的”最新价格”跟随点击事件上报.此处DataPath该怎么形容?
  红框所示ViewTree子树如下:

贪图4-2 列表Item ViewTree子树结构

倘齐图,选中部分凡列表的ItemView(RelativeLayout),可见”最新价格”是由于index为2的TextView所展示,由这而得,列表中条目点击获取”最新价格”数据的DataPath如下:

this.childAt(2).mText

DataPath解释:

  1. 起点为"this",表示目前给点击的view实例(图4-2遭遇为选中的RelativeLayout)
  2. “childAt(2)”表示RelativeLayout.getChildAt(2),得到图4-2中index为2的TextView
  3. “mText”
    表示取出步骤2吃取得TextView实例的mText字段(TextView控件显示的亲笔内容存储在mText字段内)
  4. 用取出的界面上显示的”最新价格”数据增长到原点击事件受到,一起齐报.

c. DataPath注意事项:
1.混淆.
  由于DataPath本质上讲述的经常内存中的"引用路径",并且按照DataPath取数据常常用了反光的方式,因此DataPath应该描述的凡混淆后的"引用路径".
  虽然DataPath可能遭遇混淆的熏陶,但是

* 用于存储数据的model类通常是不被混淆的.如我们之前的item关键字直接将起点设置为列表条目的model类对象,不受混淆影响.
* 通过关键字parent/childAt(x)可以在视图的引用中不受混淆影响
* 接口的方法通常不受混淆影响.因此在DataPath中多用接口方法调用

故而开发以布DataPath时应竭尽用上述未为模糊影响的字段及智.但是,如果确用了模糊了之字段怎么办.我们的方案是:

数码报警

据版本1上安排的DataPath
“a.b”,在提升新版本2后不再适用,则新版本2按照"a.b"收集时拿采集不顶,产生报警音及后台.后高接受大量者种信息会唤起开为新本子配置适用新本子的DataPath.

2.代码变化导致引用路径变化,从而致使之前安排的DataPath失效.
  以及代码中埋点相比,线达部署进行收集数据以及代码的变迁是互相的,无关之.这便发或致原有代码修改导致DataPath失效.其实如果客户端架构设计合理,功能迭代更多是于开展代码的恢宏,而未改,这种导致DataPath失效的情事应该会大大降低的.
  但是无论如何:

配置的DataPath摆脱无了跟本的相关性

对这个种问题我们照例是经过前提到的"数据报警"进行监督和避免的.


五、结语

综上,本文介绍了数额收集逻辑中3单比根本之接触(ViewID/Page/DataPath),结合上平等首文章的(AOP原理),Android端无埋点数据搜集技术及比主要的接触全因总了毕.
  当然实现SDK过程遭到遭受过许多比有趣的技能问题,后续也会见陆续展开整理.