灵动分析核心技术详解

TalkingData_DDD 发布于 2015/10/26 11:20
阅读 490
收藏 1

【开源中国 APP 全新上线】“动弹” 回归、集成大模型对话、畅读技术报告”

TalkingData 灵动分析是一个产品和运维使用的工具,他是用来分析App使用者是如何使用一个App的,通过对发布后的App进行动态埋点,解决了两大疑问:

第一,埋点不再需要在发布前预先编码;

第二,埋点工作不再是开发的工作,现在可以由产品独立完成;

那么灵动分析的实现原理中,最核心的技术是什么,按照我们的理解,灵动分析的功能可以分为以下几个大的方面:

1. 实现手机到Web的跨屏,即截屏后发送至Web显示当前手机内容。

2. Web上绘制View,通过分析应用中UI元素的x y,宽,高,把每一个ViewWeb上进行区分,并将可以用于事件绑定的元素展示给操作者

3. UI的查找,这里一个查找方法生成与执行的过程,查找方法由平台特性决定,iOS Android 是不同的思路。

4. UI 绑定与数据劫持,属于单点技术突破,只要API支持,我们就可以实现,这个并不是难点,是需要找到对应的代理方法就好。

5. 数据交互,可以使用轮询,长轮询,CometWebScoket实现

这些技术点,每一个展开都可以再分解出更多的详细技术内容,哪个才是核心,才是最重要的。根据我们实践来看,‘UI查找是整个解决方案的灵魂,其他的技术,要么可以找到很多实现方法,要么只要单点突破就可以搞定。接下来,我们分别解释 Android iOS 两个平台,在 ‘UI查找这个核心功能上是如何实现的。

首先,我们来分析Android平台的实现,要说AndroidView查找,我们先要介绍一些AndroidUI知识。

1.Activity:大家都知道ActivityAndroid的四大组件之一,负责和用户交互,Activity中的所有操作都和用户相关,可以通过setContentView(View)来显示指定的控件。

2.WindowManager:Activity的窗口和图形系统,用来管理window的添加,删除,移动等。

3.Window:在不同的地方有着不同的含义。在Activity里,Window 是一个抽象类,代表了一个矩形的不可见的容器,里面布局着若干个可视的区域(View). 每个Activity都会有一个Window类成员变量,WindowManagerService里管理的Window其实是AcitivityViewRoot。从用户角度来看,Android是个多窗口的操作系统,不同尺寸的窗口区域根据尺寸,位置,z-order及是否透明等参数叠加起来一起并最终呈现给用户。这些窗口既可以是来自一个应用,也可以来自与多个应用,这些窗口既可以显示在一个平面,也可以是不同的平面。

4.View:是一个矩形的可见区域。

5.ViewGroup:是一种特殊的View, 它可以包含其他View并以一定的方式进行布局。


对以上概念不是很理解,请百度相关概念,有了对以上内容的了解,接下来我们来描述一下他们的关系,一个应用可以有多个Activity,每个 Activity 有一个Window(PhoneWindow), 每个Window 有一个DecorViewActivity Window的顶级View, 所有在窗口里显示的View都是它的子View., 一个ViewRootImplAndroid 的默认实现,被添加在DecorView中),也就是说我们要找的,并不是setContentView(View)中的View,而是DecorView,只有找对了起点,接下来的工作才会有意义。

View中可以找到的具有标记性的属性:

Id: 静态整形数,应用在编译期,aapt会生成R类,其中包含所有资源ID

Resource Id:开发者操作控件的唯一标示,通过findViewById找到View

Class Name:View所属的Class

Tag:ViewTag属性

这些属性看上去如果Id能够使用,是可以直接解决的,但是,从aapt生成id的原则来看,在不同的版本相同的resource Id对应的整形数Id 是有可能不一样的,请参考:

http://stackoverflow.com/questions/6517151/how-does-the-mapping-between-android-resources-and-resources-id-work

所以没有办法使用Id来唯一标示。Resource Id,是开发者定义的View识别方式,对于有Resource Id View可以说具备了唯一标识,那么没有Resource IdView,我们考虑添加一个index属性,index属性可以有如下方式赋值:

1.每个ViewGroup下的所有View作为一个数组,从0开始。

2.每个ViewGroup下的所有View先按照Class分类,然后再把每个类型中的数据按照数组的方式,从0开始

3.每个ViewGroup下的所有View先按照Class分类,再确认是否有Resource Id,如果存在,就不需要index来做标记

综上,我们对要查找的View和他所有的父节点都做这样的描述,最后形成一个查找路径,这样就可以找到一个View了,接下来要考虑的是查找效率的疑问,这里我们建议使用树的深度优先算法来查找View,关于算法的实现,请百度。理论上,Activity中的每个View都可以被识别。以上就是Android的如何在UI中查找View的方法,那么,iOS 平台的实现我们通过以下三个部分做介绍

  • 1、控件到 window 的层级关系
  • 2、分析控件的详细路径
  • 3、动态修改控件

控件到 window 的层级关系:

每个 App , 至少有一个根 Window , 通常情况下我们只用一个 。window 有一个 rootViewController , 这就是我们所谓的根视图 , 我们所有的控制器都是放在 rootViewController 里面的。

这个是最简单的层级关系

如果在项目里有了这么一个路径 , 我们可以做什么呢?

  • 在当项目很复杂 , 可以其它地方可以直接修改这个控件的状态
  • 当某个控件命名存在却又没有显示出来 , 可以通过路径来辅助查找
  • 由服务器下发一些配置 , 使用 Runtime 去动态的修改已上线的项目

 下面将介绍如何使用代码来找出这些视图(控件)的路径

分析控件的详细路径

1、找出 Window :

每一个视图、控件 , 他们最终的根都是main函数返回的 application , 通过 [UIApplication sharedApplication]可以得到 。 application  windows 属性是一个数组 , 这里面装的是这个应用的所有 Window , 我们通常用的是第一个也就是 application.windows[0]

2、遍历视图 :

得到了  window   对象一切都好办了 。 然后拿到  window   的  rootViewController  , 在获取  rootViewController   里面所有的  childViewControllers   和  view   里的  subviews  , 一直递归下去就可以得到当前屏幕里所有视图对象了 , 同时可以通过  runtime   把它们的  property   、  delegate   都获取出来 。 
结合  Reveal   或者  Xcode   自带的  Captuer View Hiearachy  , 我们可以推测一下这两个的的实现原理了 :
1、根据应用得到根视图  
2、递归获取里面的所有控件 
3、按照他们的层级关系一层一层的画出来

动态修改控件

1、把上面获取到的所有控件的详细信息上传到服务器 。  
2 、根据业务需求由服务器给我们下发对应的配置列表 ,   button 为例 : 配置列表里必须要有 :

1) button   的全路径 : 如  UIWindow  ->  UIWindow  ->  UIView ->  UIView  ->  UILayoutContainerView  ->  UITabBar  ->  UIView  —>  UIButton   
2) button   的唯一标识 : 如  tag   值或自己实现的一套算法生成的唯一标识 , 目的是防止与  button   同一层次的视图搞混 。
3)、 根据路径及唯一标识来匹配  App   里的控件 , 匹配和上面的查找原理是相通的。 
4)、 匹配成功代表   button   确实存在 , 根据业务需求做后续操作 。 
提示: 匹配策略尽可能的多 , 防止意外情况某一两个标识生成失败或者生成相同 。

3、修改 button 的事件处理 。

1) 、 如某个按钮点了会  Crash   或暂时不需要被点击 , 但是又要展示出来 , 可以直接修改  button   的  enabled   属性 。 
2)、 如某业务暂时关闭 , 可以直接修改入口  按钮  frame 为0 , 前提是要自动布局已做好 。  
3)、 如给购买  按钮   添加监听事件  addTarget: action: forControlEvents:   
target   也可以通过上面  遍历视图   获取到 ,  action   可以由服务器下发 , 也可以一开始就写死 , 等有需求的时候直接传不同的参数就行了 。

4、 绑定查找控件时 , 这个界面必须要已经初始化完成了才行 , 假如界面还没生成肯定是查找不到这个控件的 。 这里给大家提供两种思路 :

1 、使用Runtime  Method Swizzing  , 直接把修改控件的方法与  didMoveToSuperview   和  didMoveToWindow   动态绑定 , 等这个控件加载出来之后再去修改 , 查找路径正确的话肯定就能找到了 。 
2、在具体的类里面 , 等控件的初始化方法调用完后 , 再去执行动态修改 , 如在 viewDidLoad   里面初始化控件 , 在  viewWillAppear:   里面动态修改 。
建议使用第一种适用范围更强 。

  上架后的   应用 可能会遇到的一些突发状况 , 未测出的Crash、临时改点小需求 , 等等 , 我们总不能每次因为一点小改动就重新提交一次   App Store , 先不说   App Store 的审核时间 , 频繁的让用户去更新应用 , 用户也会烦的 。使用这篇文章所讲的来实现动态更新是再合适不过了 。  
  首先上面讲的   动态更新 是完全脱离出来的一个模块 , 跟业务逻辑没有任何关系 , 只需要部署一次就行了 , 等开发下一个项目也可以直接拿过去使用 。这里的动态更新适用于局部的视图、控件的修改 , 如果你有其它需求可以考虑  JSPatch  wax , 下发脚本也是一个不错的选择 。

这样我们就给大家完整剖析了灵动分析的核心功能。

关于TalkingData

TalkingData自2011年起即始终专注在移动端数据服务上,管理团队来自于OracleIBMMicrosoft,腾讯,百度,360等企 业,即融合了互联网的开放精神又传承了传统IT严谨服务这两种文化。伴随4年移动互联网的高速发展,TalkingData打造了由开发者服务 平台,数据服务平台,数据商业化的完整数据服务链条。服务超过8万款移动应用及6万多应用开发者,覆盖超过17亿独立智能设备。

加载中
OSCHINA
登录后可查看更多优质内容
返回顶部
顶部