一款APP除了要有惊艳的功能和夸张的交互,在性能上也要追求丝滑,才能更好的提升用户体验。以下是我在工作中所经历的性能优化的一些总结。按照故事的发展路线分为5个部分,分别是:常见的性能问题;性能问题的一些可能原因;性能问题的解决例程;针对潜在性能问题的代码建议和故障排除项目。如果看不清大图,下面会有拆解。1.首先,我们了解一下性能问题是什么。1.内存泄漏。通俗地说,内存泄漏不仅会导致应用程序内存占用过多,还会导致应用程序卡顿,从而导致用户体验不佳。至于为什么一个“小”的内存泄漏会导致应用卡死,就得让这张图说明一切了。没错,这就是Android开发童鞋需要了解的GenerationalHeapMemory模型。这里我们只关心当对象在YoungGeneration存活一段时间,如果没有被杀死,就会被移动到OldGeneration。同样,***将被移至永久代。然后用脚想想就知道,如果内存泄漏了,那么,不好意思,随着时间的推移,你的那块内存自然会进入PermanentGeneration。不过,记忆不是白菜,想吃多少就吃多少。因为是盒子机制,你的应用程序分配的内存肯定有这样一个限制值,你不能超过(有人笑了,不是有大堆吗?玩家们都看好),好吧,你造成内存泄漏的对象占用上厕所不拉屎,其他玩物的剩余内存空间少;比如舞台小,演员想上台表演。如果没有多余的空间,他只能等其他演员下来,才能表演。等待时间无法连续执行,所以卡住了。2.频繁GC呵呵,频繁GC会造成卡顿。想必你已经知道为什么经过上面的洗礼了,是的,当然也是因为“舞台空间不够,需要新演员先让表演下来”。那么造成这种现象的原因是什么?A。内存泄漏,好吧,你懂的,不用多说,这肯定是有可能的。b.大量对象在短时间内被创建,并且“需要”在短时间内被释放。注意这里的需要。事实上,这是必要的。为什么,也是因为“舞台空间不够”。比如在onDraw对象中new,因为onDraw会在16ms左右执行一次(等等,你能确认什么是16ms左右吗,sorry,不,不是掉帧,哪怕掉一点点)。下定决心,每秒创建60个左右的对象,嗯,骚年,你以为YoungGeneration是白菜吗,你想拿多少就拿多少,不好意思,这个是有限的,这里用完了,快来申请吧,我我是否必须回收一些然后回来。我需要时间来回收。耗时。好吧,onDraw等待错过了接下来的16ms的执行。如果是这样,用户似乎被卡住了。3、耗电问题km上有一个很尖锐的问题。据说在微视上看个小视频,手机就会发热。所以一直以来用户都很关心耗电问题,但是很抱歉,我们的app目前还没有遇到严重的耗电问题。虽然我没有遇到过严重的耗电问题,但这并不代表我不需要了解此类问题的解决方案。我的结论是:没有特别重要的信息,比如钱到账,电话到,100元是真正的无门槛代金券方式等,请不要打扰用户,不要频繁叫醒用户,否则,结果只能是卸载,或者关闭所有通知。b.适当做本地缓存,避免频繁请求网络数据。在这里,说起来容易,做起来却不容易。需要配合好的缓存策略,区分哪些在一段时间内不会改变,哪些是绝对不可缓存的,这一点很重要。C。对于一些执行时间比较长的同步操作,都是在用户充电有wifi的情况下完成的,除非用户强制同步。等等,不多说了,因为后面还有很多内容。4.OOM问题呵呵,这个问题肯定是经过了前面1和2的洗礼,你应该已经明白是什么原因造成的了。你可以想一想:“一个要上台的演员是个大胖子,就算他不表演,所有的演员都下来了,他还是挤不进去,怎么办,演出一塌糊涂,还能怎么办,直接崩溃,退场!”这个问题可能是有原因的你可以从这里入手)a.内存泄漏,我想你会一笑了之。b.大量不可见的对象占用内存。这其实很常见,只是你可能不会一直关心它。比如请求接口返回一个列表,有100条数据,每条数据有100个字段。其中,你的用户显示的数据只有十几条,但是你解析的时候,剩下的99条不知不觉就吃光了你的内存。胖子要记忆力的时候,呵呵,打嗝。C。另一个非常常见的场景是页面上有多个图像的场景。明明每张图片只需要加载一张100_100,但是你用原始尺寸(1080_1980)或者更大,一下子加载几十张图片你能搞定吗?所以了解一下inSampleSize,或者,如果图片是你自己上传管理的,你可以使用万象优图,它会帮你剪出不同尺寸的图片,这样你就不用在客户端放大了。.两个以上了解一些性能问题,这里,简单罗列一下导致这些性能问题的原因1.在ui线程中人为的做了一个稍微耗时的操作,导致ui线程卡死好吧,很多朋友都不会不是这样想的,以为在onCreate里,读取什么是pref,什么是解析json数据,但是实际情况不是这样的。正确的做法是将这些操作异步封装起来。rxjava的小伙伴可以了解一下,现在***版本已经是rxjava2了,不知道怎么用的可以google一下。2.布局太复杂,16ms内无法完成渲染。很多小伙伴深有体会。这里做一个简单的理解。我们简单的把渲染分为“布局”、“测量”、“绘制”几个阶段。当然,不要以为实际情况是一样的。好吧,层次复杂,layout和measure可能会占用不该用的时间。自然,留给平局的时间可能不够用,那就悲剧了。所以之前给的很多建议都是用RelativeLayout代替LinearLayout,说是可以减少布局层级。但是,请不要建议其他人现在使用RelativeLayout,因为ConstraintLayout是一个更高性能的消除布局层级的神器。ConstraintLayout基于Cassowary算法,Cassowary算法的优点是在求解线性方程时效率极高。事实证明,线性方程是非常适合定义用户界面元素的参数。由于人们对图形非常敏感,因此UI的渲染速度非常重要。因此,在2016年,iOS和Android都基于Cassowary算法开发了自己的布局系统。下面是ConstraintLayout与传统布局RelativeLayout、LinearLayout的性能对比。不过,这里是老外的测试数据。原文可以参考这里。demo还提供了测试方法,有兴趣的朋友可以尝试一下。测量/布局(单位:毫秒,100帧的平均值)3.同时执行的动画过多,导致CPU或GPU负载过重。这里主要是因为动画一般会频繁改变view的属性,导致displayList失效。重新创建一个新的显示列表。如果动画太多,开销可想而知。如果您想了解更多详细信息,我建议您阅读这篇文章。知识点在Section5。4.viewoverdrawing的问题。视图过度绘制的问题可以说是我们在写布局时最常遇到的问题之一。可以说一个overdrawing是不留神写的,通常是在一个嵌套的viewgroup中,比如你给他设置了一个不必要的背景。解决这个问题并不难。我们可以通过手机设置中的开发者选项开启ShowGPUOverdraw的选项,轻松发现这些问题,然后尝试靠近蓝色。5.gc过多的问题这里不再赘述。上面的内容非常简单。6、资源加载导致执行缓慢。有时避免加载一些资源。这里有两种方案,使用的场景不同。A。预加载,即在到达路径之前,提前加载。哎,x5内核好像是姜子。b.使用的时候确实需要加载,请给个进度条,不要让用户等待,不知道什么时候结束,造成用户体验不好。7、worker线程的优先级设置不正确,导致cpu时间被ui线程抢占。使用Rxjava的朋友要注意这一点。设置任务的执行线程可能会对你的性能产生较大的影响。没用过的朋友不要大意。8.静态变量。嘿嘿,想必大家都有在应用程序中设置静态变量的经历吧。当年为了克服Intent只能传输1M以下数据的坑,我在两个Activity的应用中设置了一个静态变量来“传输(分享)数据”,但是要注意,数据中包含前一个活动的尾巴,所以它被泄露了。不仅是这样的例子,而且只是几个:你用过静态集合来保存数据吗?b.有没有遇到过某个单例Manger,比如管理AudioManger?下来,如何用各种刀棒棒剑自然解决这些问题1.GPU透支,定位透支区这里直接在开发者选项里,打开ShowGPUOverdraw,就可以看到效果,轻松搞定找到哪一块需要Optimization,那么具体怎么优化a,减少layout层级,上面说了用ConstraintLayout代替传统的layout方式。如果你对ConstraintLayout一无所知,没关系,本文15分钟教你如何使用ConstraintLayout。b.检查是否有多余的背景色设置。我们通常会犯一些低级错误——设置被覆盖父视图的背景。在大多数情况下,这些背景是不必要的。2.查看主线程的耗时操作。A。启用严格模式,这样主线程的耗时操作就会以告警的形式呈现在logcat中。b.直接在疑似对象中添加@DebugLog,查看方法的执行时间。DebugLog注解需要引入插件hugo,这是Android大神JakeWharton的早期作品。非常方便监控函数的执行时间。可以通过直接在函数上加注解来实现,但是有一个缺点,JakeWharton发布的最后一个版本不支持release版本用空方法替换监控代码。因此,我向公司发布了一个maven仓库。参考方法和官网类似,只是地址是??:'com.tencent.tip:hugo-plugin:2.0.0-SNAPSHOT'。3、测量和排版耗时过长的问题,一般这种问题是由于排版太复杂造成的。现在因为有了ConstraintLayout,强烈推荐使用ConstraintLayout来降低布局层级。一般都可以解决问题。如果发现仍然存在性能问题,可以使用traceView观察耗时的方法来定位具体原因。4.Leakcany是内存泄漏监控的灵丹妙药。每个人应该都用过。需要提醒的是,要注意dependencies{debugImplementation'com.squareup.leakcanary:leakcanary-android:1.5.4'releaseImplementation'com.squareup。leakcanary:leakcanary-android-no-op:1.5.4'}引入方法,releaseImplementation保证在release包中去掉监控代码,否则会产生不停的catch内存快照,本身就会影响性能。5、在onDraw中写代码的时候需要注意onDraw优于每16ms执行一次,所以相当于一个forloop。如果你在里面创建新的对象,你会在不知不觉中在短时间内创建和释放大量的对象,这么频繁的GC,嗯,内存抖动,所以就卡住了。所以,正确的做法是把对象放到外面,new出来。6.json反序列化问题。JSON反序列化是指将json字符串转化为对象。如果这里的数据量很大,尤其是字符串比较多的时候,解析起来不仅费时,而且很耗内存。解决办法是:精简字段,与后台协商,去掉相关界面上不需要的字段。保证最少可用原则。b.使用流解析。之前考虑过json解析优化,在StackOverflow上找到了这个。于是了解到Gson.fromJson可以这样玩,可以提高25%的解析效率。7.viewStub&merge的使用。这里的merge和viewStub想必是大家非常熟悉的两个布局组件。对于只在特定条件下才会显示的组件,推荐使用viewStub包裹起来。同理,如果一个布局包括它的根布局和引入它的父布局是一致的,建议使用merge对其进行包裹。如果担心预览效果,这里完全没有必要,因为可以使用tools:showIn=""属性,这样就可以正常显示预览了。8.加载优化里面没有太多的技术点。无非是将耗时的操作封装成异步。不过,有一点不得不提的是,你要注意多进程的问题。如果你的应用是多进程的,你应该意识到你的应用的oncreate方法会被执行多次,你一定不想多次加载资源,所以只在主进程加载,如果出现一些坑,它有可能其他进程需要那个资源,然后他的进程缺少对应的资源,然后他就打嗝了。9.刷新优化。这一点在我之前的文章中已经提到,这里举两个例子。A。对于列表中item的操作,比如给item点赞,此时不应该刷新整个list,而应该只刷新item。相对于熟练使用recyclerView的你,应该明白怎么操作,没懂的请看这里,你就会明白什么叫recyclerView的局部刷新。b.对于比较复杂的页面,我个人建议不要写在一个activity中,而是用几个fragment进行拼装。这样,模块的改变只能刷新特定的片段,而不是刷新整个页面。但是问题来了,fragment之间如何共享数据呢?好吧,让我们看看我是怎么做的。Activity将这部分数据抽象成一个LiveData,将数据提交给一个LiveDataManger进行管理。然后每个Fragment通过Activity的上下文从LiveDataManger中获取LiveData,进行操作,通知Activity数据变化等等。哈哈,你没看错,这个确实有点像谷歌的LiveData,当然如果你想用谷歌的也没问题,不过这是一个简化版。项目'com.tencent.tip:simple_live_data:1.0.1-SNAPSHOT'的介绍10.动画优化这里主要是利用硬件加速进行优化,但是需要注意的是,动画播放完毕后,关闭硬件加速,因为开启了硬件加速本身就是一种消耗。下面有一张图,第二张对比图和第一张图是对比一下硬件加速开启和不开启时的动画效果。可以看到开启硬件加速后渲染速度明显加快。开启硬件加速就万事大吉了吗?第三张图其实说明,如果你的视图继续失败,也会出现性能问题。第三张图可以看到曲线蓝色部分有一定程度的提升,说明displaylist在不断变化。Invalidate和recreate,如果你想了解更多细节,可以查看这里。//SetthelayertypetohardwaremyView.setLayerType(View.LAYER_TYPE_HARDWARE,null);//SetuptheanimationObjectAnimatoranimator=ObjectAnimator.ofFloat(myView,View.TRANSLATION_X,150);//Addallistenerthatdoescleanupanimator.addListener(newAnimatorListenerAdapter(){undefined@OverridepublicvoidonLaunimationEnd){myViewanimation.(View.LAYER_TYPE_NONE,null);}});11功耗优化在这里只是一个建议;A。当定位精度不高时,使用wifi或移动网络定位,不需要开启GPS定位。b.先验证网络是否可用,再发送网络请求。比如用户在2G状态下,此时的操作是查看大图,下载后可能会超过200K甚至更大。这个Request我们不用发了,让用户静候那菊花。四下面的内容比较简单,都是一些代码建议。这里就不细说了,直接挑标示的部分。pb->model的优化这里不再赘述,前面讲过如何优化。那么推荐使用SparseArray而不是Google推荐的HashMap,因为SparseArray比HashMap更节省内存,并且在一定条件下性能更好,主要是避免了key的自动装箱,比如(int转Integer类型),它内部通过两个数组存储数据,一个存储键,一个存储值。为了优化性能,它还采用压缩的方式来表示稀疏数组的数据,从而节省内存空间。除非万不得已,否则不要用wrap_content,建议用match_parent,或者固定大小,用gravity="center",哈哈,你应该懂的。那为什么这样更好。因为在测量过程中,match_parent和固定宽高对应的是EXACTLY,而wrap_content对应的是AT_MOST,比AT_MOST耗时更多。五总结这是我在工作中遇到的性能问题及其解决方法的总结。性能优化设计的方面太多了。这篇文章不可能把所有的性能问题都概括清楚。可能还存在或多或少的瑕疵,如有错误欢迎指出补充。
