Android性能优化前因后果总结

WeTest 导读

一款app除了要有使人惊叹的功能和使人发指交互以外,在性能上也应该追求丝滑的要求,这样才能更好地提升用户体验。php



如下是本人在工做中对经历过的性能优化的一些总结,依据故事的发展路线,将其分为了5个部分,分别是:常见的性能问题;产生性能问题的一些可能缘由;解决性能问题的套路;代码建议及潜在性能问题排查项。html


如看不清大图,下文会有拆解
java



一 首先,咱们先了解一下都有哪些性能问题



一、内存泄露。android

通俗来说,内存泄露不只仅会形成应用内存占用过大,还会致使应用卡顿,形成很差的用户体验,至于,为何一个“小小的”内存泄露会形成应用卡顿,我不得不拿这幅图来讲说话了。git



没错,这就是Android开发童鞋须要了解的Generational Heap Memory模型,这里咱们只关心当对象在Young Generation中存活了一段时间以后,若是没被干掉,那么会被移动到Old Generation中,同理,最后会移动到Permanent Generation中。那么用脚想想就知道,若是内存泄露了,那么,抱歉,你那块内存随时间推移天然而然将进入Permanent Generation中,然鹅,内存不是白菜,想要多少就有多少,这里,由于沙盒机制的缘由,分配给你应用的内存固然是有那么一个极限值的,你不能逾越(有人笑了,不是有large heap么,固然我也笑了,我并无看到这货被宗师android玩家青睐过),好了,你那块形成泄露内存的对象占着茅坑不拉屎,剩下来能够供其余对象发挥的内存空间就少了;打个比方,舞台小了,演员要登台表演,没有多余空间,他就只能等待其余演员下来他才能表演啊,这等待的时间,是无法连续表演的,因此就卡了嘛。
github


二、频繁GC算法

呵呵,频繁GC会形成卡顿,想必你通过上面的洗礼,已经知道了为何,不错,固然也是由于“舞台空间不足,新的演员上台表演须要先让表演完的下来”。那么形成这种现象的缘由是什么呢?json


a、内存泄露,好的,你懂了,不用讲了,这个必须有可能会形成。数组

b、大量对象短期被建立,又在短期内“须要”被释放,注意这里的须要,实际上是不得不,为何,一样是由于“舞台空间不够了”,举个例子,在onDraw中new 对象,由于onDraw大约16ms会执行一次(wait,你可否肯定一下,什么是大约16ms,对不起,不能,掉帧了就不是,哪怕掉那么一点点)。脑补一下,每秒中建立大约60个对象,嗯,骚年,你觉得Young Generation是白菜么,想拿多少就拿多少,对不起,这里是限量的,这里用完了,在来申请,我就得去回收一些回来,我回收总得耗时间吧,耗时间,好吧,onDraw 等着等着就错过了下一个16ms的执行了,如是,用户看起来就卡了。缓存


三、耗电问题

km上有一个问题很尖锐,说是微视看小视频看一会手机就会发烫,因此,用户一直就很关注耗电问题,不过很差意思,咱们的app至今尚未遇到过严重的耗电问题,虽然没有遇到比较严重的耗电问题,不表明就不须要去了解这样的问题的解决办法,我总结有:


a、没有什么特别重要的信息,好比,钱到帐,电话来了,100元实打实无门槛代金券方法,等等,请不要打扰用户,不要频繁唤醒用户,不然,结果只能是卸载,或者关闭一切通知。

b、适当的作本地缓存,避免频繁请求网络数据,这里,提及来容易,作起来并不是三刀两斧就能搞定,要配合良好的缓存策略,区分哪些是一段时间不会变动的,哪些是绝对不能缓存的很重要。

c、对某些执行时间较长的同步操做在用户充电且有wifi的时候在作,除非用户强制同步..等等,就不扯太多,由于后面还有不少内容。


四、OOM问题

呵呵,这个问题,想必通过前面一、2的洗礼,你应该已经明白这个什么缘由致使的,你能够想一想一下"舞台上将要上的一个演员是一个巨大胖子,即使不表演的演员都下来了,他仍是挤不上去,怎么办,演砸了,还能怎么办,直接崩溃,散场!"形成这个问题的缘由,可能有,(呵呵,保险起见,只能说可能,分析的时候能够从这里出发)


a、内存泄露了,想必你会心一笑。

b、大量不可见的对象占据内存,这个其实,很常见,只是你们可能一直不太关心罢了,好比,请求接口返回了列表有100项数据,每项数据好比有100个字段,其中你用户展现数据的只有10几个而已,可是,你解析的时候,剩下的99个不知不觉吃了你的内存,当,有个胖子要内存时,呵呵,嗝屁了。

c、还有一种很常见的场景是一个页面多图的场景,明明每一个图只须要加载一个100*100的,你却使用原始尺寸(1080*1980)or更大,并且你一会儿还加载个几十张,扛得住么?因此了解一下inSampleSize,或者,若是图片归大家上传管理,你能够借助万象优图,他为你作了剪切好不一样尺寸的图片,这样免得你在客户端作图片缩放了。


二 以上了解了一些性能问题,这里,简单的串一串致使这些性能问题的缘由



一、人为在ui线程中作了轻微的耗时操做,致使ui线程卡顿

嗯,不少小伙伴不觉得然,觉得在onCreate中读一下pref算什么,解析下json数据算得了什么,可实际状况是并非这样的,正确的作法是,将这些操做使用异步封装起来,小伙伴能够了解一下rxjava,如今最新版本已是rxjava2了,若是不清楚使用方式,能够Google一下。


二、layout过于复杂,没法在16ms完成渲染

这个不少小伙伴深有体会了,这里简单的了解下,咱们先简单的把渲染大概分为"layout","measure""draw"这么几个阶段,固然你不要觉得实际状况也是如此,好,层级复杂,layout,measure可能就用到了不应用的时间,天然而然,留给draw的时间就可能不够了,天然而然就悲剧了。那么之前给出的不少建议是,使用RelativeLayout替换LinearLayout,说是能够减小布局层次,然鹅,如今请不要在建议别人使用RelativeLayout,由于ConstraintLayout才是一个更高性能的消灭布局层级的神器。ConstraintLayout 基于Cassowary算法,而Cassowary算法的优点是在于解决线性方程时有极高的效率,事实证实,线性方程组是很是适合用于定义用户界面元素的参数。因为人们对图形的敏感度很是高,因此UI的渲染速度显得很是重要。所以在2016年,iOS和Android都基于Cassowary算法来研发了属于本身的布局系统,这里是ConstraintLayout与传统布局RelativeLayout,LinearLayout实现时的性能对比,不过这里是老外的测试数据,原文能够参考这里。demo中也提供了测试的方法,感兴趣的小伙伴能够尝试一下咯。


测量/布局(单位:毫秒,100 帧的平均值)


三、同一时间执行的动画过多,致使CPU或者GPU负载太重

这里主要是由于动画通常会频繁变动view的属性,致使displayList失效,而须要从新建立一个新的displayList,若是动画过多,这个开销可想而知,若是你想了解得更加详细,推荐看这篇咯,知识点在第5节那里。


四、view过分绘制的问题。

view过分绘制的问题能够说是咱们在写布局的时候遇到的一个最多见的问题之一,能够说写着写着一不留神就写出了一个过分绘制,一般发生在一个嵌套的viewgroup中,好比你给他设置了一个没必要要的背景。这方面问题的排查不太难,咱们能够经过手机设置里面的开发者选项,打开Show GPU Overdraw的选项,轻松发现这些问题,而后尽可能往蓝色靠近。



五、gc过多的问题,这里就不在赘述了,上面已经讲的很是直接了。


六、资源加载致使执行缓慢。

有些时候避免不要加载一些资源,这里有两种解决的办法,使用的场景也不相同。


a、预加载,即尚未来到路径以前,就提早加载好,诶,好像x5内核就是酱紫哦。

b、实在是要等到用到的时候加载,请给一个进度条,不要让用户干等着,也不知道何时结束而形成很差的用户体验。


七、工做线程优先级设置不对,致使和ui线程抢占cpu时间。

使用Rxjava的小伙伴要注意这点,设置任务的执行线程可能会对你的性能产生较大的影响,没有使用的小伙伴也不能太过大意。


八、静态变量。

嘿嘿,你们必定有过在application中设置静态变量的经历,遥想当年,为了越过Intent只能传递1M如下数据的坑,我在application中设置了一个静态变量,用于两个activity“传递(共享)数据”,然而,一步当心,数据中,有着前一个activity的尾巴,所以泄露了。不光是这样的例子,随便举几个:


a、你用静态集合保存过数据吧?

b、某某单例的Manger,好比管理AudioManger遇到过吧?


三 既然遇到问题分析也有了,那么接下来,天然而然是如何使用各类刀棒棍剑来解决这些问题了



一、GPU过分绘制,定位过分绘制区域

这里直接在开发者选项,打开Show GPU Overdraw,就能够看到效果,轻松发现哪块须要优化,那么具体如何去优化


a、减小布局层级,上面有提到过,使用ConstraintLayout替换传统的布局方式。若是你对ConstraintLayout不了解,没有关系,这篇文章教你15分钟了解如何使用ConstraintLayout。

b、检查是否有多余的背景色设置,咱们一般会犯一些低级错误--对被覆盖的父view设置背景,多数状况下这些背景是没有必要的。


二、主线程耗时操做排查。

a、开启strictmode,这样一来,主线程的耗时操做都将以告警的形式呈现到logcat当中。

b、直接对怀疑的对象加@DebugLog,查看方法执行耗时。DebugLog注解须要引入插件hugo,这个是Android之神JakeWharton的早期做品,对于监控函数执行时间很是方便,直接在函数上加入注解就能够实现,可是有一个缺点,就是JakeWharton发布的最后一个版本没有支持release版本用空方法替代监控代码,所以,我这里发布了一个到公司的maven仓库,引用的方式和官网相似,只不过,地址是:'com.tencent.tip:hugo-plugin:2.0.0-SNAPSHOT'。


三、对于measure,layout耗时过多的问题

通常这类问题是优于布局过于复杂的缘由致使,如今由于有ConstraintLayout,因此,强烈建议使用ConstraintLayout减小布局层级,问题通常得以解决,若是发现还存在性能问题,可使用traceView观察方法耗时,来定位下具体缘由。


四、leakcany

这个是内存泄露监测的银弹,你们应该都使用过,须要提醒一下的是,要注意

dependencies {
debugImplementation 'com.squareup.leakcanary:leakcanary-android:1.5.4'
releaseImplementation 'com.squareup.leakcanary:leakcanary-android-no-op:1.5.4'
}

引入方式,releaseImplementation保证在发布包中移除监控代码,不然,他自生不停的catch内存快照,自己也影响性能。


五、onDraw里面写代码须要注意

onDraw优于大概每16ms都会被执行一次,所以自己就至关于一个forloop,若是你在里面new对象的话,不知不觉中就知足了短期内大量对象建立并释放,因而频繁GC就发生了,嗯,内存抖动,因而,卡了。所以,正确的作法是将对象放在外面new出来。


六、json反序列化问题

json反序列化是指将json字符串转变为对象,这里若是数据量比较多,特别是有至关多的string的时候,解析起来不只耗时,并且还很吃内存。解决的方式是:


a、精简字段,与后台协商,相关接口剔除没必要要的字段。保证最小可用原则。

b、使用流解析,以前我考虑过json解析优化,在Stack Overflow上搜索到这个。因而了解到Gson.fromJson是能够这样玩的,能够提高25%的解析效率。



七、viewStub&merge的使用。

这里merge和viewStub想必是你们很是了解的两个布局组件了,对于只有在某些条件下才展现出来的组件,建议使用viewStub包裹起来,一样的道理,include 某布局若是其根布局和引入他的父布局一致,建议使用merge包裹起来,若是你担忧preview效果问题,这里彻底没有必要,由于你能够

tools:showIn=""属性,这样就能够正常展现preview了。


八、加载优化

这里并无过多的技术点在里面,无非就是将耗时的操做封装到异步中去了,可是,有一点不得不提的是,要注意多进程的问题,若是你的应用是多进程,你应该认识到你的application的oncreate方法会被执行屡次,你必定不但愿资源加载屡次吧,因而你只在主进程加载,如是有些坑就出现了,有可能其余进程须要那某份资源,而后他这个进程缺没有加载相应的资源,而后就嗝屁了。


九、刷新优化。

这点在我以前的文章中有提到过,这里举两个例子吧。


a、对于列表的中的item的操做,好比对item点赞,此时不该该让整个列表刷新,而是应该只刷新这个item,相比对于熟练使用recyclerView的你,应该明白如何操做了,不懂请看这里,你将会明白什么叫作recyclerView的局部刷新。

b、对于较为复杂的页面,我的建议不要写在一个activity中,建议使用几个fragment进行组装,这样一来,module的变动能够只刷新某一个具体的fragment,而不用整个页面都走刷新逻辑。可是问题来了,fragment之间如何共享数据呢?好,看我怎么操做。



Activity将数据这部分抽象成一个LiveData,交个LiveDataManger数据进行管理,而后各个Fragment经过Activity的这个context从LiveDataManger中拿到LiveData,进行操做,通知activity数据变动等等。哈哈,你没有看错,这个确实和Google的那个LiveData有点像,固然,若是你想使用Google的那个,也天然没问题,只不过,这个是简化版的。项目的引入


'com.tencent.tip:simple_live_data:1.0.1-SNAPSHOT'


十、动画优化

这里主要是想说使用硬件加速来作优化,不过要注意,动画作完以后,关闭硬件加速,由于开启硬件加速自己就是一种消耗。下面有一幅图,第二幅对比第一幅是说开启硬件加速和没开启的时候作动画的效果对比,能够看到开启后的渲染速度明显快很多,开启硬件加速就必定万事大吉么?第三幅图实际上就说明,若是你的这个view不断的失效的话,也会出现性能问题,第三图中能够看到蓝色的部曲线图有了必定的转机,这说明,displaylist不断的失效并重现建立,若是你想了解的更加详细,能够查看这里


// Set the layer type to hardware

myView.setLayerType(View.LAYER_TYPE_HARDWARE, null);


// Setup the animation

ObjectAnimator animator = ObjectAnimator.ofFloat(myView,View.TRANSLATION_X, 150);


// Add a listener that does cleanup

animator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
myView.setLayerType(View.LAYER_TYPE_NONE, null);
}
});

11耗电优化

这里仅仅只是建议;


a、在定位精度要求不高的状况下,使用wifi或移动网络进行定位,没有必要开启GPS定位。

b、先验证网络的可用性,在发送网络请求,好比,当用户处于2G状态下,而此时的操做是查看一张大图,下载下来可能都200多K甚至更大,咱们不必去发送这个请求,让用户一直等待那个菊花吧。


四 接下来的一些内容就比较轻松了,是关于一些代码的建议



这里不一一细讲了,仅仅挑标记的部分说下。


pb->model这里的优化就不在赘述,前面有讲如何优化。


而后建议使用SparseArray代替HashMap,这里是Google建议的,由于SparseArray比HashMap更省内存,在某些条件下性能更好,主要是由于它避免了对key的自动装箱好比(int转为Integer类型),它内部则是经过两个数组来进行数据存储的,一个存储key,另一个存储value,为了优化性能,它内部对数据还采起了压缩的方式来表示稀疏数组的数据,从而节约内存空间。


不到不得已,不要使用wrap_content,,推荐使用match_parent,或者固定尺寸,配合gravity="center",哈哈,你应该懂了的。


那么为何说这样会比较好。


由于 在测量过程当中,match_parent和固定宽高度对应EXACTLY ,而wrap_content对应AT_MOST,这二者对比AT_MOST耗时较多。


五 总结

这是以上关于我在工做中遇到的性能问题的及处理的一些总结,性能优化设计的方方面面实在是太多太多,本文不可能将所有的性能问题所有总结的清清楚楚,或许还多多少少存在一些纰漏之处,有不对的地方欢迎指出补充。




参考资料
http://developers.googleblog.cn/2017/09/constraintlayout.html
http://hukai.me/android-performance-patterns
https://juejin.im/entry/59396e01fe88c2006afc3862
https://github.com/JakeWharton/hugo
https://stackoverflow.com/questions/15509544/optimizing-gson-deserialization
https://medium.com/livefront/recyclerview-trick-selectively-bind-viewholders-with-payloads-4b28e3d2cce8
github.com/hehonghui/a…


腾讯WeTest是腾讯官方出品的一站式质量开放平台。致力于品质标准建设、产品质量提高,历经千款腾讯产品磨砺。平台包含兼容测试、云真机、性能测试、安全防御、企鹅风讯等优秀工具,覆盖产品在研发、运营各阶段的测试需求。金牌专家团队,10余年品质管理经验,5大维度,41项指标,360度保障产品质量。

目前,咱们为WeTest平台的认证用户提供无偿使用机会,详情点击wetest.qq.com/cloud/index…

若是使用当中有任何疑问,欢迎联系腾讯WeTest企业QQ:800024531

相关文章
相关标签/搜索