工作小结-2018八月(2)

这个月都没有写小结,发生了很多事情,都没有来的及记录。

首先是公司进入了a轮的加速期,为了提高转化率使投资人满意,所有人都进入了8月的倒计时奋战期。其实和之前差不多,唯一变的是每周一开需求大会,之前都会假意征求一下开发的意见,现在这个流程也省略了。

其次是由于好几个前端跑路,导致之前做的很多动态页面需要本地化,也相当于增加了业务量。

这个月基本上是围绕着优化,订单来工作的。

首先讲讲优化。

  • ui优化

app在oppor9m上面贼卡,一方面是过度绘制比较严重,二方面是内存泄漏比较严重。之前写布局很多地方都用的relativylayout或者framelayout,嵌套比较严重,红色过度绘制的区域十分多,查看gpu绘制,发现滑动的时候绘制较多,在专题详情页面尤其明显。

– 过度绘制

因此对主要流程,主要流程的卡顿页面做了调整,完全按照constraintlayout的设计规则,无论多复杂的布局,统一使用一层或者2层(带scrollview)的布局结构。写的十分顺畅,使用的过程中完善了我对于constraintlayout的理解,包括guideline,group,基准点等。constraintlayou有一个问题,就是背景问题,以往的情况下,习惯性的使用linearlayout,或者部分布局包装,然后针对包装布局进行背景设置,但是在constraintlayout下面,其实单层布局不是很好设置背景,只可以使用单独的一层view来在之前设置,但是view的下方padding就有一些问题,尤其是在提前布局的情况下。针对这个情况,我通过guideline和viewtip来解决,不过这样多了很多奇怪的点,这些点也只可以通过group来进行布局的绑定。

整个constraintlayout给我的感觉,是牺牲了逻辑效率,带来布局效率,很多的逻辑牵扯在的xml中,而代码中我们一般指判断view是否显示,但是这样就会导致xml中的部分逻辑在不显示的情况下,产生了偏差。因此我觉得在大多数遇到逻辑复杂的布局排列中,还是不要轻易强行单层布局,逻辑偏差带来的时间损失,绝对比使用嵌套布局之后在进行代码优化的多。

– bitmap大图

由于我们的项目就是围绕着人脸,身材,妆容这些来进行的,这些无一例外都是bitmap的使用者。

整合人脸,整合身材,整合妆容之后形成的完整的人模型,原本需要占用大约5m的内存。在优化之前,很多地方使用的是fragmentpageradapter,每个fragment持有很多的bitmap,当时算了一下,一个framgent如果在不上滑加载更多的时候,会持有8个bitmap对象,就是约40m内存。

在2.1.3版本上面,anr的发生率提高到了14%,绝大多数都是在首页滑动的时候产生的卡死。

针对这个问题,也做了针对性的优化。首先是使用fragmentstatepageradapter,这样可以在fragment进入销毁流程的时候,释放引用而让他自然销毁。这样首先是切换的时候内存占用问题解决了。

其次是单fragment下滑导致的内存占用问题。刚开始使用的recyclerview,为了让用户有比较好的体验(雾),而将最大item设为了20(大雾)。这下子用户上滑超过20个,光bitmap 的占用率就飙升到了100m,还不谈item的其他内存占用问题。在一般的华为和oppo手机上面,光这项就吃掉了分给app的50%以上的内存。基本上这个时候如果系统在吃点内存,app就anr了。

因此针对这个问题,想到的是动态持有,根据可用内存堆分配来实时更新缓存堆的内容。使用的是比较常见的lrucache,但是这个lrucache只缓存了bitmap的信息,item其余的信息就直接释放了,这样动态缓存1/4最大内存堆的内存,同时也保证了bitmap加载不会有太多的问题。

使用缓存有个小问题,就是强制刷新的时候不注意的话其实是无法清除的,强制刷新的时候得clearcache。

– bimap色深

在解决了缓存占用问题之后,基于bitmap的内存问题其实得到了很大的解决。但是仍然有一个巨大的问题,就是bitmap的加载速度。

即使缓存到lrucache里面,也得首先下载下来,在普通4g网大约200K下载速度的时候,一张基准图下载需要大约0.5-1秒,但是经过渲染和加载,总共大约需要1.5秒的样子才可以呈现,这个速度实在是很慢。

处理这个的问题一般就是从图片质量上面入手,图片存储方式一般是写死的,虽然我们使用的是oss,可以通过传递参数到服务器,来获取不同的图片大小,但是在做到这个地步之后,就是图像的渲染,也就是从网上下载的数据映射到bitmap上面的这个过程。

一般的bitmap是rgb888,一个像素占3Byte,我用的手机5.5英寸,1080x1920像素,一般图片设置大约140dp宽高,也就是约420x420像素,占用内存就是420x420x3 = 516K,jpg的压缩率我不是很清楚,大约在10%-90%之间,也就是这么一个呈现为516k的图片,大约需要下载5-50k之间的样子,专题详情页面的大图,宽度是130dp,高度是400dp,就一下子扩了将近3倍,下载如果在200k就要全速一秒了。

流量并不是很关心的事情,下载其实也不是很关心的事情,因为我无法决定用户的网速,那是framework的事情,我只可以确保下载的内容到本机能够快速加载出来。因此采取的措施是压缩色深,从rgb888,压缩到rgb565,然后压缩到了rgb444。图片质量压缩到如此之低,基本上是能接受的极限了。改完之后速度大约快了一点,加载的时候下载完的条结束就可以出来了。

– 主线程任务过多引起的卡顿

在sku详情页面,逻辑是这样的,2线并行,1线先请求人体数据,人体数据的返回值会引起整个页面结构的初始化,2线后行,直接请求sku的数据,进行数据的web填充。问题出现在这两条线都涉及到了ui的修改,所以都跑在主线程,还有个问题是双线有依赖,会导致加载的时候互相调用,而在安卓中很少对于view进行上锁措施,因此此时的操作极为繁琐,而且卡顿及其严重。

针对此问题主要是优化主线程任务措施,rxjava中其实还是比较好做的,一来使用concat操作符,二来在数据初始化的步骤可以放到子线程,之后实现的操作可以放到主线程,逻辑复杂的部分可以放在子线程,结果传到主线程。(此处mark一下,需要对rxjava的原理做个完整的学习)

双线我通过整合成为单线,然后线程切换的方式,加载速度,滑动速度响应都足够了。

  • 性能优化

– 内存泄漏

之前项目中整合了lecancary,每次gc和泄漏的时候会弹出提示,但是在项目中的表现并不好,一来是fragment和activity的多层引用,基类中有很多资源持有引用,而这些引用是后来自己手动释放的,但是在lecancary中就会爆这些引用泄漏了,其二是我们所有的网络代码通过clean的结构,在最顶层使用dagger来解耦,因此造成了在ondestroy过程中会销毁,但是切换过程中并不会销毁。切换的过程需要等待,但是这个过程已经造成了泄漏。

采取的措施比较粗暴,就是直接在切换的时候触发presenter的destroy方法,这样切换的时候,会先关闭引用,之后在切换,切换之后销毁自己,这就是比较正常的过程了。

– oom

oom的问题就如同bitmap大图的问题,掠过不讲

  • 订单流程

整个订单流程进行了完整的更新,大部分计算价格的部分给予后台来处理,不在由前端显示相关内容,对于我们来讲逻辑更新容易很多。出问题的地方是由于都给予后台显示了,前端势必要判断是否成功,失败的处理逻辑等等。因此针对网络失败的地方做了处理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
private void handleHttpError(Throwable e){
if (e instanceof RuntimeException){
if (e.getCause() instanceof ConnectException){
mEventBus.post(new OnHttpErrorEvent("连接失败,请检查网络后再试"));
}
if (e.getCause() instanceof SocketTimeoutException){
mEventBus.post(new OnHttpErrorEvent("请求超时,请检查网络后再试"));
}
if (e.getCause() instanceof UnknownHostException){
mEventBus.post(new OnHttpErrorEvent("域名连接失败,请检查网络后再试"));
}
if (e.getCause() instanceof NetworkErrorException){
mEventBus.post(new OnHttpErrorEvent("网络异常,请检查网络后再试"));
}
}
if (e instanceof ConnectException){
mEventBus.post(new OnHttpErrorEvent("连接失败,请检查网络后再试"));
}
if (e instanceof SocketTimeoutException){
mEventBus.post(new OnHttpErrorEvent("请求超时,请检查网络后再试"));
}
if (e instanceof UnknownHostException){
mEventBus.post(new OnHttpErrorEvent("域名连接失败,请检查网络后再试"));
}
if (e instanceof NetworkErrorException){
mEventBus.post(new OnHttpErrorEvent("网络异常,请检查网络后再试"));
}
}

处理方式如上,主要是针对rxjava网络异常的各个问题进行鉴别性操作,然后根据不同的类型显示不同的页面。

大致分了2种页面,一种是正常的失败,没有数据的那种,叫emptyview,还有一种就是网络失败的,叫errorview,都是可以抽出来单独做的。

除此之外由于大多都是业务方面的内容,可总结的比较少。

能够抽出来说的有一点,是优惠卷的ui处理方面,一般是使用recyclerview,但是由于用户的优惠卷比较少,我使用的是普通的linearlayout,通过addview来进行加载视图,问题是无法通过recyclerview那种通过一张表来更改数据和view。我需要维护2个表,一个是选中的view表,一个是所有的view表,每次点击的时候需要从选中表中进行遍历,然后处理一些关于选中优惠卷的县骨干内容,本次版本主要是互斥卷和叠加卷的逻辑问题。处理完之后会重新刷新选中表。这样一定程度上是牺牲了代码简洁,而照顾了加载速度。其实recyclerview也有很多bug,比如说每次notifydatasetchanged,数据源变更会强制触发重新刷新的功能,这就不适合数据源并不多的情况。而且recycerview本身作为一个较为重型的控件,处理单纯的几个view,有些大材小用。

大致如上,四周都没什么时间好好整理一下,做项目的时候总是想着要总结总结,但是到了要总结的时候,仿佛踩过的坑,处理过的问题,都一下子不见了。脑袋里就剩下了最近处理的一些问题。