前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >从体积到耗电,全方位解析SDK超级优化方法

从体积到耗电,全方位解析SDK超级优化方法

作者头像
BestSDK
发布2018-03-01 18:07:09
1.4K0
发布2018-03-01 18:07:09
举报
文章被收录于专栏:BestSDKBestSDK

sdk优化过程,是一段血泪史,可以吐槽的地方无数。移动端sdk不像app一样方便,sdk发布后出现任何问题,都会影响到很多家的app。

sdk的优化,最大的痛点是它的大小。每次对接客户,他们都会问我们sdk的大小是多少?每当提到iOSsdk时,他们都会说还蛮大的,他们自己家的app都已经几十M了,接入我们的sdk会增加他们app的大小。所以,不得不开始痛苦的sdk优化之路。

我们主要从以下几个方面进行优化sdk:

1. 脚本构建

2. 极限优化(网络、日志上报、图片格式等方面优化)

3. 第三方组件替换

4. 小版本稳步迭代

脚本构建

我们从开始开发sdk到目前正在开发中的3.8版本,一直推崇借助脚本进行自动化打包,例如android使用gradle。借助脚本的好处在于:

1)androidsdk混淆

2)自动生成文档,便于开发者查阅,例如android可以很方便的生成javadoc文档

3)androidsdk上传aar包,iOSsdk发布到cocoa-pods,便于开发者集成

4)节省人工时间,减少出错

脚本通常能帮助我们实现很多自动化的事情,能提高工作效率的方法是一定会被采纳的。

接下来我们来看看借助gradle如何实现sdk混淆,核心的task是proguardJar这个task。

极限优化

所谓极限优化,是指从多个角度、维度对sdk进行优化,重点是考虑网络优化以及电量消耗优化。能够做到代码精简,低网络流量,微能耗而不仅仅是低能耗。

香农定理是所有通信制式最基本的原理,我们知道C=B lb(1+S/N)

其中:C是信道支持的最大速度或者叫信道容量,B是信道的带宽,S是平均信号功率,N是平均噪声功率,S/N即信噪比。

从最初的1G网络到现在的4G网络,都是在利用这个公式提高速度。要么充分利用频道资源,要么提高整体带宽。但是频段资源都是有限的,所以不得不制定出更优秀的策略来提高资源的利用率。结合网络情况、手机电量等因素,我们采取以下几种方式进行优化:

1)合并网络请求,减少服务器压力和dns请求时间,减少手机的网络流量。

2)数据缓存到本地,最省电的方式就是不使用移动网络,数据缓存能大大减少网络请求的次数。

3)日志上报策略,批量非实时上报。日志生成后,首先存储在RAM中,基础策略是满30条发送,每隔一分钟轮询一次。为了满足客户定制需求,发送策略可通过后台配置。如果遇到异常情况,比如网络异常或者crash等,我们会将日志存储在本地sqlite中,在程序下次启动后,根据发送策略再次发送。

为了减少app的网络流量消耗,我们还将活动的图片新增了WebP的格式。

WebP格式的图片好处是什么?举个例子,做一个简单的测试对比PNG 原图、PNG 无损压缩、PNG 转WebP(无损)、PNG 转WebP(有损)的压缩效果。

可以得出结论:PNG 转WebP 的压缩率要高于PNG 原图压缩率,同样支持有损与无损压缩。

转换后的WebP 体积大幅减少,图片质量也得到保障(同时肉眼几乎无法看出差异)。转换后的WebP 支持Alpha 透明和 24-bit 颜色数,不存在 PNG8 色彩不够丰富和在浏览器中可能会出现毛边的问题。

WebP 的优势体现在它具有更优的图像数据压缩算法,能带来更小的图片体积,而且拥有肉眼识别无差异的图像质量。除此之外,国内外很多知名的应用已经使用了WebP格式,这也是我们使用它的原因之一。

在3.8版本的sdk中,用于活动的Marketing接口会返回PNG和WebP两种格式的图片。对于Android而言,如果操作系统版本在4.0以及4.0之后,它天生支持WebP格式,sdk会优先加载这种格式,加载不成功才会去加载PNG的图片。如果是Android 4.0以下,sdk只加载PNG图片。

对于iOS而言,目前iOS本身不支持WebP格式(但愿iOS10会支持它:(),要借助第三方库才能支持,比如SDWebImage。但是iOS sdk已经足够大了,不可能把SDWebImage集成到sdk。所以,目前iOSsdk不会像androidsdk一样存在imageloader,iOSsdk把图片加载的权利交给开发者。当然以后,我们肯定会给iOSsdk提供类似android的imageloader的功能。

借助Webp,我们替用户节省了流量,节省了手机内存和CPU资源。

未来,网络请求还会进一步优化。会考虑使用protobuf协议替换现在的返回json格式。protobuf返回的数据更小,而且是二进制的格式。从安全性的角度上说,在一定程度上能够防止被恶意抓取数据包进行分析。

第三方组件替换

对于移动端sdk的开发者来说,移动端其余的开发人员都是幸福的。他们可以尝试使用无数的第三方库,在github上每天都会诞生很多优秀的第三方库。sdk的开发者不得不自己去实现很多功能,因为考虑到sdk大小的问题。

对于sdk的开发者来说“这是一个最好的时代,也是一个最坏的时代”。他们必须自己去“造轮子”,但是会给他们带来更多收获,无论是接触到os的底层还是设计模式,都会比普通的开发者了解更多。

我们魔窗的sdk包括Androd、iOS版本在不断迭代的过程中,都经历过第三方组件的替换。以android为例,我们替换了json解析器和网络框架等等。

最初,我们使用fastjson,它是由阿里巴巴的工程师编写的,性能和稳定性都很好。我自己写app时,也会首选它作为json的解析器。但是它明显增大了sdk的体积,于是我们使用gson替换了fastjson。用了一段时间后,觉得gson还是很大。

最终,我们考虑重写jsonparser。重写的jsonparser,必须能兼容原先gson的一些api,避免sdk工程做太大的改动,这是我们重写的一个目标。

重写jsonparser之前,我们先对反射做了一次封装。传统的反射是这样写的:

封装之后的写法是这样的,基于流式API:

依托于简洁的反射,实现了自己的jsonparser。除此之外,还需要将http请求返回的结果借助自己的json工具类转换成对象、对象数组。类似于这样:

借助这个反射我们还获得的额外好处是,在android4.0以后的版本能够随时获取到App的ApplicationContext,以前还担心获取不到ApplicationContext,这样一来还能防止memory leak。因为,Activity的Context使用不当经常会引起内存泄露。

另一个被替换的第三方组件是volley。它是google开发的网络框架,便于android应用操作网络。替换volley的原因,是它功能太强大了,简直就是一个“全家桶”。我们用不到那么多功能,sdk需要的是一个符合自身业务需求的网络框架。同样,替换的准则是能够兼容原先volley的大部分api。于是我们做了一个简化版本的volley,它大致的流程如下图所示:

它最主要的四个部分是:Request、RequestQueue、NetworkExecutor和ResponseDelivery。

Request,即各种请求类型。包括StringRequest和ImageRequest,分别表示返回的数据是字符串和网络图片的请求。Request支持Get、Post请求,支持header、支持请求缓存、支持postbody、支持请求的重试机制。Request类还包含了一个回调处理的接口ResponseListener。

第二部分为消息队列RequestQueue,消息队列维护了提交给网络框架的请求列表,并且根据相应的规则进行排序。默认情况下更具优先级和进入队列的顺序来执行,该队列使用的是线程安全的PriorityBlockingQueue,因为我们的队列会被并发的访问,因此需要保证访问的原子性。

第三部分是NetworkExecutor,它是网络的执行者。该Executor继承自Thread,在run方法中循环访问第二部分的请求队列,请求完成之后将结果投递给UI线程。为了更好的控制请求队列,例如请求排序、取消等操作,这里我们并没有使用线程池来操作,而是自行管理队列和Thread的形式,这样整个结构也变得更为灵活。它的主要代码是这样的:

其中,doRequest()方法用于真正的网络请求和分发网络请求返回的Response。doRequest()支持重试机制,它的大致流程如下图所示:

第四部分是ResponseDelivery,在第三部分的Executor中执行网络请求,Executor是Thread,但是我们并不能在主线程中更新UI,因此我们使用ResponseDelivery来封装Response的投递,保证Response执行在UI线程。

总之,每个部分都符合单一职责的原则,便于日后的独立维护。

我们再看看怎么借助这个网络框架如何调用httppost请求。

一. NeteaseAPM是什么

对于普通的app开发来说,小版本快速迭代几乎是不可或缺的方法论。而对于sdk开发而言,“小步快跑,快速迭代”的策略不再适用。我们必须采取相对稳健的更新策略。

sdk是面向所有的开发者使用的,高版本必须向下兼容api。如果某个api确实需要过期的时候,至少保留几个版本后再删除过期的api,并附有详细的说明文档。

对于sdk而言,版本发布也不宜频繁,否则会让开发者会感觉自己是“小白鼠”。这样的体验,对于开发者是相当不友好的。

对于每一个小版本除了新增的功能之外,我们都会集中精力优化好某一块地方。每一个小版本都是“小步迭代”,但是经过几个版本的迭代之后,还是能够实现量变。下面的表格是我开始接手魔窗sdk之后,androidsdk体积的大小的变化。

版本号

大小

备注

3.0

460K+

接手之前的版本

3.1

180K+

去掉原先的gson,使用自己开发的json parser

3.2

200K

一些细小的优化、调整sdk包的结构

3.66

224K

增加mLink(deep link)功能

3.7

183K

去掉原先的volley,使用自己开发的http框架

从3.0到3.7版本,android sdk的大小,总体趋势是不断减少的。其实功能不断增加的,sdk的稳定性也得到提升,这就是我们采用小版本不断迭代带来的好处。

未来,sdk拆分

关于未来,我们追求的是在保证sdk稳定的前提下,继续努力减少sdk的大小。将我们的sdk拆分成多个组件,供用户挑选自己想要的各个组件。我们目前sdk的模块如下图所示。

sdk最核心的部分是sdkcore,它是sdk必不可少的组成部分。它有以下几部分组成:

1)http组件,是我们自己开发的http模块,符合自己的业务需求。

2)imageloader组件,在sdk中显示活动图片的组件,是自己开发的模块。

3)domain,是sdk所需要的对象,包括http返回的对象以及业务模型。

4)config组件,是sdk必须的配置组件。

5)jsonparser组件,json解析器,是我们自己开发的模块。

6)utils,sdk中各种帮助工具类。

7)sqlite组件,操作数据库的相关类,把一些数据缓存到sqlite数据库。

其余的组件虽然没那么重要,但是可以通过自由组合的方式,组成开发者想要的功能。这是我们未来1-2月的努力方向——sdk的拆分。将sdk拆成更小更细粒度的模块,开发者也能更好地选择他们想要的模块。

比如一个开发者只想要tracking功能,那么他只需使用sdkcore包和tracking包。再比如一个开发者只想要mLink(基于deeplink深度改造)的功能,那么他会需要sdkcore包、tracking包、magicwindowview包和mLink包这几个包。

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2017-10-23,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 BestSDK 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
相关产品与服务
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档