项目背景
1、开屏广告,是商业客户端维护的最重要模块,有,,,,,等特点。
2、商业早期开屏广告功能单一,实现随意,多处违背基本设计原则,更没有系统的进行架构设计,80%的代码堆砌在少数几个类中。
3、随着,,,人力无法满足业务deadline时的,,。目前开屏模块已接近无法维护,烟囱化严重,研发测试成本翻倍,质量濒临失控,性能肉眼可见的日益劣化。
重构目标
核心意图:管理复杂度,建设为独立商业能力,角色由面向页面的,转向面向接口的。
重构收益:
1、提供开屏服务,独立组件、可独立运行。
2、可插拔式UI插件库设计。
3、调试速度大幅提升,从运行一次应用10分钟左右,提升至30秒左右,提升95%。
4、无用、过期代码下线,包体积缩小100k左右。
5、去烟囱化,多套开屏逻辑统一,独立组件高速调试,开屏开发周期缩短30%以上,故障率减少50%以上,QA测试周期缩减50%以上。
6、优化开屏耗时200ms,开屏展示率提升3%。
7、独立商业能力之一,可为任何业务,任何知乎app,直接提供插屏广告能力。
开屏模块现状
现有问题&代码性能评估
重构前梳理:Android 开屏广告梳理
对梳理过程中,代码现存具体问题进行归类,总结,从而进一步评估代码性能现状。
结论:现有开屏架构不能满足商业日渐复杂的开屏场景,与日俱增的开屏收入。重构时需重点关注可读性、可维护性、可复用性。
可读性
可拓展性
可维护性
可复用性
代码性能
设计原则匹配
结合代码性能现状,评估设计原则匹配度,找出重构侧重点及方向。
结论:重构设计中,重点关注SRP、LKP、OCP、DIP四个指导性原则上的优化。
代码结构
核心类 LaunchAdFragmentDelegation 现状,70%功能堆叠在一个类中,20%功能与该类有关
现有架构
页面级开发,即每个页面有自己的一套开屏逻辑,一个需求点,多倍工作量,且难以保持一致性。
纵向与横向(架构与切面)
纵向:
如上面简易架构图,目前纵向纵深不够,整体是一个拍平的状态。基本的UI层、数据层、逻辑层、核心层结构都不具备。UI层和逻辑层在一处,且或多或少包含数据层及核心层逻辑。
横向:
目前关键节点间途径若干方法或回调,其间散落了大量各方面逻辑,不成体系,难以理解,且任何一处出错都会打断开屏逻辑,没统一管理。
架构设计
理念
1、核心思想为将开屏广告子系统「SDK化」,抽象为一种商业能力,面向API开发、维护。
2、SDK内部只包含通用功能,高稳定性,高可拓展性,不关注具体业务场景。
3、纵向架构层级上讲究「动静分级」。
4、横向切面分割上讲究「统一精准」。
5、于使用方而言,内部实现黑盒,高度可配置,可插拔,可自定义。
商业整体架构图
开屏模块架构图
SDK 层:
抽象出和业务完全无关的 SDK 层,面向接口设计,可为知乎App,来鸭App,知乎日报App或外公司App提供开屏广告接入支持。
基建层:
基础能力,日常开发时无需修改。
框架层:
将通用业务、共性代码等低频率修改代码独立出来,形成框架层,这层代码是可由专人维护,其他业务线同学无法修改。
拓展层(Api):
处于组件api中间件之中,对外提供两套接口,一套为命令式,控制开屏广告;一套为响应式,接收开屏广告各类回调。
接口设计:
ZHSplashAd:
- 构造方法:ZHSplashAd(Activity activity, String posId, ZHSplashAdListener adListener)
- fetchAd() 拉取广告,配合showAd使用,实现和fetchAndShowIn相同的功能。
- showAd(ViewGroup container) 展示广告,配合fetchAdOnly使用
- addViewPlugin(SplashPlugin plugin),添加自定义插件
- removeViewPlugin(),删除任意开屏插件
- isLaunchAdShow() 开屏广告展示条件是否满足
ZHSplashAdListener:
- onNoAD(AdError error) 广告加载失败,error 对象包含了错误码和错误信息,错误码的详细内容可以参考文档第5章
- onADDismissed() 广告关闭时调用,可能是用户关闭或者展示时间到。此时一般需要跳过开屏的 Activity,进入应用内容页面
- onTimeOut()熔断时间内,开屏广告未就绪
- onADClicked() 广告被点击时调用,不代表满足计费条件(如点击时网络异常)
- onADExposure() 广告曝光时调用
- onADTick(long millis) 倒计时回调,返回广告还将被展示的剩余时间,单位是 ms
- onADLoaded(long expireTimestamp) 广告加载成功的回调,在此方法中调用SplashAD.showAd(ViewGroup container) 方法,即可展示广告。
业务层:
SDK层的使用者。如超级首映,冷启动开屏,热启动开屏,端内开屏等,知乎创新产品App等。
组件化设计
重构前开屏代码并不独立,处于ad组件中,,,其他业务线组件直接调用,,且。
重构前组件图:
重构后新增launch组件,并伴生luanch-api中间件,面向接口编程,与其他业务线解耦,可独立编译(开发现状:跑一次主工程10分钟。可独立编译后,开屏需求跑一次只需要1分钟,大幅提升人效)
重构后组件图:
具体实现
响应式编程
重构前,由于开屏场景,多线程,阻塞,等待,数据转化场景复杂且丰富,皆采用回调的方式实现,造成了现在的“回调地狱”,代码可读性,可维护性很差。
重构后,采用rxjava,响应式编程方案。基于观察者的实现原理,丰富的操作符支持,使得开屏逻辑主干流程在100行代码以内实现,一目了然。
主流程时序图
开屏展示条件
重构前:
跳过方式:实现接口的方式跳过开屏
重构后:
跳过方式:采用apt注解处理器的方式,利用编译期来标记不展示开屏的Activity,实现跳过开屏
数据流、数据结构设计
重构前:
多套数据结构来回转换,重复赋值,容易造成内存泄漏,可读性差,浪费性能。
重构后:
单数据结构Advert一以贯之
图层插件设计
开屏插件库:
开屏广告所需UI元素,皆以自定义view形式构建,并加入插件库,随时插拔。
默认渲染流程图:
默认渲染的插件:
自定义插件默认处于最上层,可设置事件是否透传。
发射井设计
下发的多个开屏广告同时并发执行,抽象为发射井模型,设计上关注匹配、优先级、发射、返回队列、时限等。
流程图
等待队列设计
类图
作者:知乎商业移动端团队-于铠瑞
出处:https://zhuanlan.zhihu.com/p/593000564