最后说一下我的学习路线
其实很简单就下面这张图,含概了Android所有需要学的知识点,一共8大板块:
- 架构师筑基必备技能
- Android框架体系架构(高级UI+FrameWork源码)
- 360°Androidapp全方位性能调优
- 设计思想解读开源框架
- NDK模块开发
- 移动架构师专题项目实战环节
- 移动架构师不可不学习微信小程序
- 混合开发的flutter
Android学习的资料
我呢,把上面八大板块的分支都系统的做了一份学习系统的资料和视频,大概就下面这些,我就不全部写出来了,不然太长了影响大家的阅读。
330页PDF Android学习核心笔记(内含上面8大板块)
Android学习的系统对应视频
总结
我希望通过我自己的学习方法来帮助大家去提升技术:
-
1、多看书、看源码和做项目,平时多种总结
-
2、不能停留在一些基本api的使用上,应该往更深层次的方向去研究,比如activity、view的内部运行机制,比如Android内存优化,比如aidl,比如JNI等,并不仅仅停留在会用,而要通过阅读源码,理解其实现原理
-
3、同时对架构是有一定要求的,架构是抽象的,但是设计模式是具体的,所以一定要加强下设计模式的学习
-
4、android的方向也很多,高级UI,移动架构师,数据结构与算法和音视频FFMpeg解码,如果你对其中一项比较感兴趣,就大胆的进阶吧!
希望大家多多点赞,转发,评论加关注,你们的支持就是我继续下去的动力!加油!
网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
基于我们上述的控件 ID 定义,在页面元素不发生变动的情况下,基本能够保证『稳定性』和『唯一性』,但是页面元素发送动态变化,或者不同版本之间 UI 进行改版的情况下,我们的控件 ID 就会变得不够稳定,比如以下情况:
在插入一个 FrameLayout 之后,我们 Button 的控件路径就变成了 FrameLayout[0]/LinearLayout[2]/Button[0]
,与之前的 ID 相比,已经发生了改变,变得不那么『稳定』了,于是我们做了以下的优化:
-
优化1:将兄弟节点中的位置,变成相同类型控件的位置。优化后的控件路径为:
FrameLayout[0]/LinearLayout[1]/Button[0]
,即使在插入 FrameLayout 后,其路径仍旧不变,相较之前会更加稳定一些。但如果插入的是 LinearLayout,或者整个页面的 UI 进行了重构,控件路径依旧会发生改变。 -
优化2:因为不同的系统版本或手机厂商,会对页面的根 View 做一定的处理,所以我们需要屏蔽掉这种情况,对于我们而言,我们只关心我们自定义的那部分布局,即通过 setContentView 传入的布局。我们可以通过判断控件 ID 是否等于
android.R.id.content
来获取我们自定义的布局的根 View,并将其作为我们控件路径的起点。 -
优化3:在 Android 中,除了
R.id
和控件路径之外,还有一个比较常用的可以作为控件 ID 的特征信息,那就是开发者写在布局文件中,关联控件的 Resource ID。Resource ID 是开发者自己定义的关联 View 的标识,在一个页面当中,理论上是唯一的(为什么说是理论上,因为还是存在有多个相同 Resource ID 的情况,比如动态的 add 多个 layout,且包含了相同的 Resource ID,但这种情况非常少),并且在页面的重构过程中,Resource ID 也一般不会修改,因此用 Resource ID 来作为控件 ID 是非常合适的。但并不是所有的控件都有 Resource ID,我们可以先尝试去获取这个 ID,假如 Resource ID 存在,则使用 Resource ID 来作为控件 ID,假如 Resource ID 不存在,则降级使用控件路径作为控件 ID。核心代码如下:
控件的点击、长按指标
有了控件 ID 的生成规则,控件的点击和长按指标我们就能很方便的进行统计,因为在 Android 中,控件的点击和长按都有非常标准的回调函数,即 onClick(View v)
和 onLongClick(View v)
方法。在回调函数中调用 SDK 封装好的方法,传入被点击控件的 View 对象,通过 View 对象本身的特征信息,得到这个控件的唯一 ID,然后上传埋点,即可统计出我们想要的控件相关的点击、长按指标。
- 点击
- 长按
代码插桩
通过上文的描述,我们得到了页面和控件的 ID 的定义规则,也知道了只需要在相应的回调函数中写入 SDK 代码获得我们想要的对象,就能够计算出我们想要的指标,那么如何才能自动的往我们现有的工程中写入获得对象的代码。
在指定的切点插入指定的代码,这个业务场景可能很多同学都非常熟悉,我们常用 AOP 的方式来解决这类问题,将所有的代码插桩逻辑集中在一个 SDK 内处理,这样可以最大程度的不侵入业务。
Javassist
Javassist 是一个基于字节码操作的 AOP 框架,它允许开发者自由的在一个已经编译好的类中添加新的方法,或是修改已经存在的方法。但是和其他的类似库不同的是,Javassist 并不要求开发者对字节码方面具有多么深入的了解,同样的,它也允许开发者忽略被修改的类本身的细节和结构。一个简单的修改方法体的例子如下:
gradle 插件
Javassist 需要操作已经编译好的类,Android 的打包流程从下图可以了解,我们可以在 Java 编译器编译完工程代码,.class 文件转成 dex 之前使用 Javassist 来进行我们需要的代码插桩工作。
了解过 gradle 插件的同学可能知道,在 Android Gradle Plugin 版本在 1.5.0 及以上,我们可以使用官方提供的最新的 Transform API,在打包编译时 .class 打包成 dex 之前对 class 文件进行处理。具体的自定义插件过程不在赘述,我们只需要定义一个自己的 Transform,继承系统的 Transform,重写 transform 方法即可。
在 transform 方法的第二个参数里,我们可以获取到工程内所有的源码编译出来的 .class 文件以及所有依赖的 jar 包,我们挨个遍历所有的 .class 文件,以及解压缩所有的 jar 包,拿到 jar 包内的 .class 文件,即可实现对所有的文件进行代码插桩的需求,核心代码如下:
拿到 .class 文件之后,我们会按照上述 Javassist 的工作流程进行代码插桩:
- 先根据类名得到
CtClass
对象 - 再根据我们想要寻找的切入点,页面就找
onResume()
方法,控件就找onClick(View view)
方法 - 然后根据方法名和参数类型,得到
CtMethod
对象 - 调用
CtMethod
对象的编辑方法体的 API,在原始方法体之前插入就调用insertBefore
,之后就调用insertAfter
,传入需要插入的代码块 - 调用
CtClass
的writeFile()
方法,保存这次编辑
将项目中所有的源文件遍历一边后,我们就完成了整个项目代码的插桩,在我们想要的切入点(页面的曝光、控件的点击等回调函数),就成功的插入了相应捕获页面、控件对象的代码,在页面曝光或者控件点击时,就能够获得相应的对象,生成唯一 ID 并上报相应的埋点事件,完成整一个无痕埋点的流程了。
阶段二:可视化管理后台
完成阶段一的无痕埋点之后,我们可以通过接入一个 SDK 来轻松的实现页面曝光、控件点击等指标的数据获取,但是通过上文我们可以知道,我们定义的 ID 其实对于业务方(产品、运营、BI 等非业务开发人员)而言是不友好的,他们无法根据 ID 中的类名、Resource ID 等特征信息来关联到埋点具体的业务含义,因此我们需要通过一些工具来帮助他们将埋点元素 ID 和具体的业务含义进行关联,甚至是跨平台(Android、iOS 的自动埋点 ID 是不一致的)的关联。
从另外一个角度来说,有了这样的可视化管理后台,我们还可以通过下发配置表的方式来收集想要的埋点,这其实就是我们开篇说的可视化埋点。所以有了这样的管理后台并基于自动埋点的数据采集方式,我们可以根据具体的业务场景,灵活的选择是无痕埋点(全量采集)还是可视化埋点(根据配置表定向采集)。
一个简单的用户操作可视化管理后台的时序图如下:
从图中我们可以知道,可视化管理后台的核心内容就是上传手机界面截图及控件相关信息,可以让用户在后台对相关的页面、控件与自定义的业务 ID 进行绑定并在后台生成配置,界面实际效果如下:
在上图的可视化管理平台中,主要有这么几大块内容,最上方是当前和管理后台建立连接的设备信息,左下方是当前界面已经绑定过自定义业务 ID 的埋点元数据,右下方是手机当前界面在管理平台上的映射,并标记出界面内所有可埋点的控件,已绑定过自定义业务 ID 的控件标记绿色,未绑定的标记红色,这样用户就可以非常方便的选择自己想要的控件进行操作。
要实现上图这样的效果,我们只需要遍历当前页面,并上传所有可被埋点的控件信息,对于目前我们想要实现的数据指标而言,我们只关心控件的点击和长按事件,换句话说就是我们只需要找到当前页面内所有的可被点击或长按的控件即可。
上报控件信息
对于需要上报的控件需要满足以下几个条件:
- 可被点击或长按
- 在当前界面可见
对于控件是否可被点击或长按,我们没法直接通过系统的 API 来获取,但是通过源码我们可以看到,View 内部还是有私有变量来存储点击或长按的监听器的,在 API14 之前的 mOnClickListener 对象和 API14 之后的 mListenerInfo 对象,均可用来判断当前 View 对象是否被设置了点击监听函数,我们可以通过反射来拿到这些对象,并进行判断,长按的判断也同理,核心代码如下:
处理完可被点击或长按的条件后,我们要判断控件在当前界面是否可见,因为我们需要在截图上把控件全选出来,如果控件本身是不可见的也被圈出来,用户就会比较迷茫。通过一定的调研,我们发现满足以下几点条件,即表示该控件在屏幕内可见:
- 判断 View 本身可见性属性
View 本身可见性属性比较容易判断,我们只需要判断 View.isShown()
并且 View.getVisibility() == View.VISIBLE
即可。
- 判断 View 所处的位置是否在当前屏幕内
一个 Activity 加载了多 Fragment 的情况下,可能会出现控件本身可见性属性达标,但实际并不在屏幕内的情况。这种情况我们根据 View.getLocationOnScreen(int[] outLocation)
,然后通过判断 outLocation[0]
,是否大于等于 0 且小于等于屏幕宽度,就能判断控件是否在当前屏幕内。
- 判断控件是否被其他控件完全遮挡
遍历所有与该控件有关联的控件(同层控件、父控件、父控件的同层控件等),通过 View.getGlobalVisibleRect(Rect viewRect)
来得到控件所对应的 Rect 信息,然后通过 Rect.contains(Rect r)
来判断两个控件对应的 Rect 是否完全包含即可。
控件符合上述的可被点击或长按且在当前界面可见这两个条件,其信息就会被并上传至管理后台,用户就可以对这个控件进行编辑,绑定自定义的业务 ID,管理后台得到控件与自定义业务 ID 的关联关系后,即可生成配置表,并下发至 App。这样采集上来的埋点就会带上自定义业务 ID,用户在后续的数据使用过程中就可以非常方便的查看相应的业务指标。
可视化管理后台核心的逻辑就是上述的客户端和管理后台建立连接并上传相应信息,其他配置的生成、下发等都非常容易处理,就不在赘述。
阶段三:埋点DSL
文章开头我们有提到过,无论是无痕埋点还是可视化埋点,都是基于自动化采集埋点的方式来做的,在这样的采集方式下,我们无法通过埋点携带更多的信息,这也是我们面临的一个痛点。基于这样的需求之下,我们考虑可以用DSL来解决这个问题。
什么是DSL
DSL 即 Domain-specific language,翻译为领域特定语言,意为在特定领域解决特定任务的语言。
哪些场景下需要用到DSL
上文提到的自动埋点以页面和控件为切入点,hook 页面曝光和控件点击事件,并获取页面及控件相关信息作为特征值写入埋点。在简单的场景下,这样的逻辑尚可胜任,但在某些复杂的场景,比如典型的 banner 轮播、资源位曝光等,控件相同但实际内容不同的埋点,无法根据控件信息来区分。对于手动埋点而言,获取接口内的信息,然后传入埋点就能进行区分,但是自动埋点无法关联这部分接口信息,于是需要 DSL 来定义简单的规则,通过运行时的方式来获取内存中的这部分数据,从而写入埋点,进行更加精细的区分。
如何实现DSL
DSL 的构建与编程语言其实比较类似,想想我们在重新实现编程语言时,需要做那些事情;实现编程语言的过程可以简化为定义语法与语义,然后实现编译器或者解释器的过程,而 DSL 的实现与它也非常类似,我们也需要对 DSL 进行语法与语义上的设计。总结下来,实现 DSL 总共有这么两个需要完成的工作:
- 设计语法和语义,定义 DSL 中的元素是什么样的,元素代表什么意思
- 实现解释器,对 DSL 解析,最终通过反射(runtime)来执行
设计语法和语义
这部分其实是千人千面的,我们可以根据自己的业务需求来不断的迭代,但是核心思路是定义一些特殊的字符串,并对应调用各自的 API,一些简单的语法大致有以下这些:
- 用
.
来标识对象调用,比如test.a
表示实例test
中的a
字段 - 用
.()
来表示方法调用,比如test.test()
表示实例test
中的test()
方法调用 - 用
[]
来表示数组或列表
实现解释器
说是解释器,其实只是一段预先写好在 SDK 内的代码逻辑。通过预先约定好的语法和语义,业务开发者在可视化平台针对某个控件进行代码编写,然后下发这部分代码,SDK 根据规则解析这部分代码,然后通过反射(runtime)的方式来获取相应的数据并写入自动埋点。
平台配套
可视化平台在元素录入的时候或者后期编辑的时候,可以额外录入事件发生时想要获取的数据的路径,这部分内容需要由业务开发人员根据 SDK 这边给出的规则进行路径的录入。成功录入后,生成配置文件下发至 App。SDK 在事件发生时,获取到相应事件携带的数据路径,根据 DSL 约定的规则解析路径并获取相应的数据,存放至埋点相应字段内上传。
总结
最后
最后这里放上我这段时间复习的资料,这个资料也是偶然一位朋友分享给我的,里面包含了腾讯、字节跳动、阿里、百度2019-2021面试真题解析,并且把每个技术点整理成了视频和PDF(知识脉络 + 诸多细节)。
还有 高级架构技术进阶脑图、高级进阶架构资料 帮助大家学习提升进阶,也可以分享给身边好友一起学习。
一起互勉~
网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。**
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!