从最初的移动端运营活动到深度链接(deep link),再到现在的移动端原生广告,魔窗sdk经历了多个版本的迭代之后,功能逐步完善,开始步入4.x版本的时代。
去年五月份的时候,我写过一篇文章《移动端SDK的优化之路》,现在回过头再来看看发现过去一年多的时间里我们又做了很多事情,所以有了新的一篇文章。
一. deep link
1.1 支持deep link和deferred deep link
早在2015年下半年就开始做deep link的功能,当时的版本已经支持了deep link以及deferred deep link(场景还原)的功能。
所谓deep link,是解决各个App之间信息孤岛的问题,实现App之间能够像网页一样能够自由跳转。
deferred deep link 是指用户打开一个h5页面的时候并没有安装对应的 app,在安装 app 以后可以直接通过 deep link 到 app 对应的内容。
场景还原.png
从后台的数据分析显示,大多数的客户对我们deferred deep link(场景还原)更感兴趣。为此,我们也一直在努力提高场景还原的匹配度。
1.2 为了能从微信朋友圈回流到App,Android版本使用应用宝跳转
iOS能够借助Universal Link从微信朋友圈跳转到App的具体页面,Android就没有这么幸运了,虽然谷歌早就提出了App Links但是国内很多手机并不支持,我们借助应用宝的链接来跳转到App的具体页面。
应用宝跳转原理跟 deferred deep link 是一样的,并不会100%的准确匹配,但绝大多数情况是可以成功跳转的。
1.3 iOS10之后,第一时间优化WebView的跳转
iOS 10之后,用户在WebView中使用uri scheme做应用间的跳转时,必须把目标App的uri scheme加到Info.plist中。
对于那些在 WebView 中使用魔窗的短链接客户而言,如果仅仅是做应用内的跳转,那是不需要把自己的Scheme放到Info.plist就可以直接调用。魔窗的短链会自动匹配操作系统版本和浏览器信息,在支持Universal Link的浏览器中自动使用Universal Link,如果不支持Universal Link的浏览器则用Scheme进行跳转。
1.4 去年11月初,sdk跟服务端通信的接口全面使用https
2016年的WWDC规定在2017.1.1之后iOS App必须全面支持https协议。我们在2016年10月的版本开始做支持https协议的功能,android 和 iOS两个平台的sdk都支持了https。赶在了11月初上线,给开发者留足时间,让他们替换新版本上架。
二. 原生广告
魔窗广告.png
我们的原生广告是基于魔窗位的,魔窗位可以埋在App的任意位置包括开机画面、Banner位、任意文字或图片的地方等等。
在新版本中,我们还新增了信息流广告。
三. 信息流广告
什么是信息流广告?不了解信息流广告的童鞋可以看我之前的文章《对信息流广告以及未来移动端广告的简单思考》
我们的sdk支持原生的信息流广告。提供原生的控件给到开发者,屏蔽了其中的技术细节,方便开发者直接使用到项目中(或者feed流中)。
信息流广告样式.jpg
原生控件能够给用户带来更好的体验,无缝地插入到App Native的页面中。除了原生控件之外,还支持将信息流广告的metadata返回给开发者,供开发者自行渲染。
信息流展示的策略,可以在后台进行配置。
信息流广告配置策略.JPG
四. sdk的设计原则和架构
4.1 模块化设计
从最初的所有代码都在一个主工程,到现在拆分成多个module,结构更加清晰。
按模块划分.jpeg
在下一个版本中,android 和 iOS 都会考虑将原生控件的功能拆分成一个单独的sdk。
4.2 面向对象的设计原则
在设计sdk时,我们一定会遵循面向对象的法则。
4.2.1 单一职责原则(Single responsibility principle)
单一职责原则是指:对一个类而言,应该仅有一个引起它变化的原因。简单来说,一个类中应该是一组相关性很高的函数、数据的封装。
sdk的网络框架并没有使用android 、iOS流行的okhttp、retrofit、AFNetworking等。因为需要考虑到sdk包大小的问题,我们使用对应操作系统底层的API来实现。因此,在android和iOS我们都做了一套简化的框架,大致流程是这样的:
http框架.png
我们遵循了单一职责原则,它主要由四个部分组成:Request、RequestQueue、NetworkExecutor和ResponseDelivery,每一个部分只负责自己的工作。
Request是各种请求类型。
RequestQueue是消息队列,维护了提交给网络框架的请求列表,并且根据相应的规则进行排序。
NetworkExecutor是网络的执行者,从消息队列中取出Request,请求完成之后将结果投递给UI线程。
最后,由ResponseDelivery来封装Response的投递,保证Response执行在UI线程。
4.2.2 接口隔离原则(Interface Segregation Principle)
接口隔离原则是指:建立单一接口,不要建立庞大臃肿的接口,尽量细化接口,接口中的方法尽量少。
为各个类建立专用的接口,而不要试图去建立一个很庞大的接口供所有依赖它的类去调用。 在程序设计中,依赖几个专用的接口要比依赖一个综合的接口更灵活。接口是设计时对外部设定的“契约”,通过分散定义多个接口,可以预防外来变更的扩散,提高系统的灵活性和可维护性。
sdk中广告相关的类图.png
我们在处理广告时,对外只暴露AdManager类。广告展示类(AdDisplay)被AdManager所依赖,不对外开放。对于不同的广告,可以通过setAdStrategy()方法来设置不同的广告策略,进行广告展示。广告策略(AdStrategy)也是一个单独的接口。
4.2.3 迪米特法则(Law of Demeter)
迪米特法则又叫最少知道原则,一个类对自己依赖的类知道的越少越好。对于被依赖的类来说,无论逻辑多么复杂,都尽量地的将逻辑封装在类的内部,对外除了提供的public方法,不对外泄漏任何信息。
所以,sdk对外暴露出去的方法需要严格控制,只给开发者足够使用的API,无关的方法是不会开放出来。
4.2.4 常用的设计模式
除了上述原则之外,sdk还采用了多种设计模式,工厂和单例模式就不必说了,例如:
魔窗位或者广告位的点击,需要传递的参数众多,为了避免混淆这些参数采用了Builder模式。
sdk内部处理多种类型的广告时,对不同的广告需要使用不同的策略,因此采用了策略模式。
还有空对象模式,因为sdk内部的代码也存在着链式调用,如果链式调用出现了空指针那绝对是灾难,必然会导致App Crash。可以参考之前写的文章《为了程序的健壮性,我们可以使用空对象模式》
总之,在设计sdk时,尽量会采用符合高内聚、低耦合以及开闭的原则。
4.3 懂得取舍
处理取与舍是一个哲学的问题,能够不断地舍弃原先的代码的人,才能写出更好的代码。有人会说,那不就是重构吗?经常我们所舍弃的代码并不是不好,而不是最合适的解决方案。
曾经有一段时间我特别喜欢RxJava的风格,甚至考虑在sdk4.0中引入Rx的写法。在去年,我写了几个简单的操作符比如map、flatMap、forEach等来模拟RxJava的写法,并引入到sdk中使用,后来我理解了Java 8的lambda表达式以后,立刻明白完全没必要自己在sdk中写这些东西,果断删除相关的代码。
五. 测试
5.1 静态代码分析工具
sdk每一次发布之前,都需要先使用静态代码分析工具查找代码的缺陷。静态代码工具还能给出提示让开发者纠正不正确的写法。
在android平台上我们使用的工具有findbugs、pmd、checkstyle、facebook infer。
在iOS平台上我们使用Xcode自带的静态分析工具Analyze 和 facebook infer。
纠正完这些工具所提示的缺陷,才会交给测试进入下一轮的测试阶段。
quality.jpeg
5.2 内存泄漏分析
说实话,自从有客户给我们报sdk的内存泄漏bug之后,我们就特别重视这一块的问题。一方面,使用专业的工具来进行测试。目前在android平台使用的工具是LeakCanary,在iOS平台还是使用Analyze。另一方面,多做code review,基于经验来查找可能存在潜在的内存泄漏的地方。所以,sdk对每次开放出去的callback接口都会非常谨慎。
未来,在android版本的sdk中会考虑采用类似glide的方式,内部的Request可以随Activity或Fragment的onStart而resume,onStop而pause,onDestroy而clear,从而节约流量和内存,并且防止内存泄露。
5.3 后台收集sdk的bug
sdk遇到最大的困难可能不是来自功能上的,而是一个bug在我们这儿无法重现,但是在客户的手机上却能100%地稳定重现。
我曾经让平安wifi的研发同学寄出一台能够重现sdk bug的Android手机给到我们,我们debug并修复完之后,再寄还给他们。
除了这些,也经常会遇到一些奇奇怪怪无法想象的bug,比如之前《记录两个神奇的android bug》。
虽然,sdk本身能够上报bug到后台,但是最初仅限于客户能够看到自己的app crash相关信息。作为sdk的开发者,我们也无法看到这些信息。后来,终于有了一个单独的系统能够专门过滤出属于魔窗sdk bug的信息,供sdk开发人员进行查询。每次发版前,我们都会先修复上一个版本存在的bug,然后交给测试。
六. 总结
本文是对将近两年来移动端sdk开发的小结,此过程可谓是踩坑无数,但是sdk的开发还要继续,未来也远远不止于移动端的原生广告,sdk还会提供更多的优质内容给到开发者。