1.什么是性能优化
![](https://i-blog.csdnimg.cn/blog_migrate/77db09a5e0744ed3819873ab001aff3d.jpeg)
百度百科:
性能优化(Optimize)
简而言之,就是在不影响系统运行正确性的前提下,使之运行地更快,完成特定功能所需的时间更短。
维基百科:
大多数系统会响应增加的负载而导致性能会有一定程度的下降,修改系统以处理更高负载就是性能优化。
总结就是,提高负载能力让程序运行更快,用更少的资源做更多的活就是性能优化。
2.为什么要性能优化
随着科技不断发展、移动互联网的迅猛发展,手机硬件不断进步以及使用手机的人口增多,这样就导致我们的程序的实际运行环境是无法控制的,除开程序本身的质量而言,我们不能完全抛弃低端手机用户群体,这是我们的人口红利,一句话简而概之,我们要提升用户留存,不能让程序在低端手机运行不流畅甚至ANR。
![](https://i-blog.csdnimg.cn/blog_migrate/1e54213390799ab2bbf60d5c16bb2ade.jpeg)
2015 年上半年,Pinterest 的工程师进行了一次实验,借此将移动 Web 首页的页面加载性能提升了 60%,同时移动注册转化率提升了 40%。然而该实验使用了一种极为烦琐的解决方案,用到了大量“抄近道”的方法,例如提供预先生成的 HTML 页面,而没有使用内部模版渲染引擎或其他通用资源(JS、CSS)。为了将实验学到的经验实用化,整个前端引擎、所有页面模版,以及通用元素都必须重写。
亚马逊近10年前的一项研究证明,即使在那时,页面加载时间每减少100毫秒,收入也会增加1%。最近的另一项研究强调了这样一个事实:超过一半的受访网站所有者表示,由于应用程序性能不佳,他们失去了收入或客户。
总结,产品的意义是解决现实生活的需求,一个好的产品必定有着优秀的性能,而优秀甚至极致的性能能够提升用户的主观感受,让用户愿意继续消费,也为后续的用户转化打下基础。
3.如何性能优化
至于如何具体的深入到项目中去进行性能优化呢
1、快速响应用户的触碰事件(不要在主线程干耗时操作)
2、设置动画或滚动时,在16毫秒以内生成帧
3、最大程度的减少内存分配,避免短时大量分配内存(频繁的GC会造成内存抖动,
JVM在进行回收时会发出stop world 指令,该指令会暂停所有线程,从而导致UI卡顿)
4、持续吸引用户,一个页面的数据尽可能在1000毫秒以内呈现交互内容
![](https://i-blog.csdnimg.cn/blog_migrate/1148aa1337c0e2b51af2c328e5fd5a5a.png)
Render
首先说说渲染方面,在正式开车之前,我们先了解一下其他的知识点,Android系统每隔16ms就重新绘制一次页面,就是说应用要在16ms内完成屏幕刷新,如果16ms内没有渲染完毕,那就是我们常说的卡顿、不跟手,专业的说法就是掉帧。至于安卓系统为什么设置每个16ms来触发渲染这就跟人眼的生理结构有关,人的眼睛可以感知每秒60帧的动画,如果低于60帧就不会认为它是连续性的。
![](https://i-blog.csdnimg.cn/blog_migrate/6a3bcb3b36d838f83a4f85f0e7192389.png)
举个例子,比如一个按钮 Button ,LayoutInflater遍历 XML 文件然后把 Button 实例加载到内存,内存里面保存了 width、height、left、top、right、bottom以及一些内外边距,CPU经过计算生成多维向量图形,最后将计算好的图形给GPU来进行栅格化像素填充。
![](https://i-blog.csdnimg.cn/blog_migrate/e779ed91938262088e8757d56e695842.png)
![](https://i-blog.csdnimg.cn/blog_migrate/959ba599b57ec016092f07ac959ee2f7.png)
所有在屏幕上我们看到的图片放大一百倍,可以发现就是一个个发光点,屏幕的颜色呈像原理是基于三基色RGB,所有的颜色的都是由RGB组成,或者再加上Alpha透明度,这也不难发觉为什么我们做图片优化的时将 ARGB8888改成ARGB4444、RGB565,一个8进制位是一个字节,一个ARGB8888像素点的呈像信息是4个字节,ARGB4444、RGB565将内存降低了一半,缩小宽高和矩阵压缩也是同理,通过控制宽高来降低内存开销。
具体在实际的安卓开发中,我们要尽量避免过度绘制、XML层级过深、测量耗时等
![](https://i-blog.csdnimg.cn/blog_migrate/006e2c7d534bbe98f6ab0d673e9021e9.png)
我们知道安卓是根据XML从上而下遍历渲染的,图中的蓝色区域是一次绘制,绿色区域的控件也有颜色,这样就造成了二次绘制,以此类推,这就是过度绘制。根据渲染的原理,CPU通过计算后交给GPU来栅格化,过度绘制就导致我们做了很多无用的计算。
关于XML层级过深的问题,这个无需多说,页面通过setContentView加载到页面是通过XML遍历处理的,层级越深速度越慢,一般情况下能够用 LinearLayout 就不要用 RelativeLayout ,RelativeLayout 的测量会触发两次,测量左右关系、然后上下关系,LinearLayout 在没有用 weight 时只测量一次就能够确定位置效率相对比较高,比如一个布局需要最左边和最右边都显示一个按钮,这种布局用 FrameLayout 最合适,因为FrameLayout是效率最高的 ViewGroup。
![](https://i-blog.csdnimg.cn/blog_migrate/25c226cfab73e318f4764ae150ba8b17.png)
![](https://i-blog.csdnimg.cn/blog_migrate/5ab45094ce0c55784c3da9dfa6888fc1.png)
以及一些组合类型的自定义View,当我们的自定义View继承了响应的 ViewGroup 时,然后XML里面又有一个父布局,这样多了一层嵌套,这个时候可以通过 merge 标签来去除。
Compute
计算方面的优化比较杂,举个例子
ArrayList list = new ArrayList();
for(int i = 0; i < list.size(); i++){
}
上面这个for 循环是低效的,尤其是大数据量的循环尤为明显,建议改为
ArrayList list = new ArrayList();
for(int i = 0, y = list.size(); i <y; i++){
}
在安卓系统中,谷歌为我们提供了一些平台比较高效的数据结构,android.util包下一共有如下几个类:SparseArray系列(SparseArray,SparseBooleanArray,SparseIntArray,SparseLongArray,LongSparseArray), SparseArray 在安卓平台上效率高于 HashMap,这些特定的数据结构从读取速度、内存消耗都有做特殊的优化,可以在合适的地方采用来提高效率。当然Java传统的数据结构也并不是一无是处,合适的数据运用在合适的业务场景看个人抉择,比如读取场景比较频繁的建议采用 ArrayList 线性队列,添加移除比较频繁的则选择 LinkedList ,建议使用 StringBuilder 来代替 + 拼接字符串,字符串转换建议使用String.valueOf() ,强转和字符串拼接转 String 比较低效率,因为 String.valueOf() JVM 对这个方法做了特定的性能优化,一些大数据量的计算建议放在子线程执行,最好用线程池来操作,直接使用线程不可控,可使用RxJava 的 IO 线程来处理大计算量,很多框架有做相关的优化,还有一些"计算优化"是属于特定的安卓下的计算优化,比如我们 RecycleView 上滑刷新数据时最好不要用 notifyDataSetChanged(); 建议使用 notifyItemInserted();还有item移除用 notifyItemRemoved();有很多 api 是可以高效率处理的,比如Gif动态图的加载,Glide虽然也可以加载,但是计算量和内存开销是比较大的,这个时候通过 JNI 通讯采用 giflib 来加载 Gif 动态图就比较高效了,giflib 是一个 native 的框架,这样做的好处就是 C 的运行速度比 Java 快,而且 Native 分配的内存不受限制,不会 OOM,建议使用直接类型 int > Integer,计算优化的方式千千万,看每个人实际的业务场景。
![](https://i-blog.csdnimg.cn/blog_migrate/da153feb3e703250f38ec72148d91d54.png)
Memory
内存优化,简单说图片优化不外乎宽高、质量、矩阵缩放,然后大图预览局部渲染,在实际的开发过程中有很多优秀的第三方框架已经帮我们做了优化,比如Glide的图片加载策略,先从内存中寻找,没有则去磁盘找,再没有则请求网络图片,当下载完毕保存到内存和磁盘,这里就要提到一个算法 LruCache,最近最少用到,简单的说就是,当内存不足时,最少被用的图片会被回收,像我们在开发中,如果不是非常有必要,建议不要使用反射,因为反射会生成大量的临时变量,生成变量开辟内存空间耗时,同时GC回收也耗时,有一些在程序运行之前就可以确定的数据建议直接根据数量初始化,避免浪费内存,比如首页有4个Fragment,那我要用一个队列来保存它,那就直接 ArrayList<Fragment> fragments = new ArrayList<>(4);
Network
网络优化
![](https://i-blog.csdnimg.cn/blog_migrate/82fd5d391fe43025ae2f34442835e12d.jpeg)
关于服务器端的网络优化不做过多解释,关于客户端的连接优化,IP直接链接,这个涉及到Http的原理,我们请求一个接口,实际上请求一台电脑的数据,每一个生产环境的地址都是需要通过NDS服务器来解析的,如果直接访问IP是可以优化连接速度,提高网络性能优化,很重要的一点就是降低延迟和提升响应速度。
通常我们在浏览器中发起请求的时候header部分往往是这样的
![](https://i-blog.csdnimg.cn/blog_migrate/d56f29c4f56efd5ff51bf7929843d9ab.png)
keep-alive
就是浏览器和服务端之间保持长连接,这个连接是可以复用的。在HTTP1.1中是默认开启的。
连接的复用为什么会提高性能呢?
通常我们在发起http请求的时候首先要完成tcp的三次握手,然后传输数据,最后再释放连接。三次握手的过程可以参考这里 TCP三次握手详解及释放连接过程
一次响应的过程
![](https://i-blog.csdnimg.cn/blog_migrate/27ba508a601b9fcd54069662080e155c.png)
在高并发的请求连接情况下或者同个客户端多次频繁的请求操作,无限制的创建会导致性能低下。
如果使用keep-alive
![](https://i-blog.csdnimg.cn/blog_migrate/035af90b3b4d3378063e018ce16dfd65.png)
在timeout空闲时间内,连接不会关闭,相同重复的request将复用原先的connection,减少握手的次数,大幅提高效率。并非keep-alive的timeout设置时间越长,就越能提升性能,长久不关闭会造成过多的僵尸连接和泄露连接出现。合理的复用时间能够提升效率,okttp也做了类似于keep-alive的链接复用机制。
合并请求就是能够一个接口返回的不要通过两三个接口去请求,同一个页面的数据初始化最好一个接口带回。并发连接很好理解,高效率利用CPU,避免CPU闲置。分优先级请求网络就是比如一个页面分为头部和尾部,首先展示给用户的是头部区域,那我们就先请求头部数据,等头部数据出来后再请求尾部数据,这属于策略请求优化。关于数据优化就是字面意思,不做解释。
Battery
电量优化,其实主要就是注意自己的一些代码问题,有些操作没有及时或正确的关闭会耗费大量的电量,我们可以在得到充电状态信息之后,有针对性的对部分代码做优化。比如我们可以判断只有当前 手机为 AC 充电状态时才去执行一些非常耗电的操作,像定位、传感器用完记得及时关闭,使用传感器,选择合适的采样率,越高的采样率类型则越费电。后台下载耗时任务建议使用JobScheduler,其工作方式有 利于用户在适当的时机执行正确的事情。应用可以在安排作业的同时允许系统基于内存、电源 和连接情况进行优化。JobSchedule 的宗旨就是把一些不是特别紧急的任务放到更合适的时机 批量处理。这样做有两个好处:避免频繁的唤醒硬件模块,造成不必要的电量消耗。避免在不合适的时间(例如低电量情况下、弱网络或者移动网络情况下的)执行过多的任务消耗电量。
![](https://i-blog.csdnimg.cn/blog_migrate/d2546cc9ae43988d01cdda44f128423e.jpeg)
1、并发的优化
2、单例模式的选择优化
3、自定义View的注意事项
4、动态代理、反射的优化
5、ART虚拟机和Dalvik的区别
6、View 的绘制流程
7、APK包的瘦身优化
8、应用的启动优化
9、dex文件的优化
......