Android性能优化总结,UI优化+内存优化+网络优化+Bitmap优化

前言

Android的性能优化有很多的方面,本文章着重为大家讲解一下一下几点。

1.绘制优化

2.内存泄漏优化

3.Bitmap优化

4.网络优化

接来我们从这几个方面为大家简单介绍优化方案

一、绘制优化

在说绘制优化之前, 我们先来了解一下Android的渲染机制

android渲染机制每隔16ms Android系统每隔16ms就会发送一个VSYNC信号(VSYNC:vertical synchronization 垂直同步,帧同步),触发对UI进行渲染,如果每次渲染都成功,这样就能够达到流畅的画面所需要的正常帧率:60fps。一旦这时候系统正在做大于16ms的耗时操作,系统就会无法响应VSYNC信号,执行渲染工作,导致发生丢帧现象。

 

想要达到人眼觉得流畅的动画效果 我们要保证我们的帧率是60帧 16ms意味着着1000/60hz,相当于60fps。 所以我们要保证16ms内系统能够渲染完一帧图片 否则就会出现丢帧、卡顿现象。如果某一帧的图片渲染过于复杂布局层级嵌套 或者在onDraw方法中做了很多复杂的效果,导致系统在16ms内无法渲染完1张图片 就会挤压其他图片渲染的时间,造成丢帧卡顿现象。

所以我们想要避免页面卡顿罪魁祸首便是过度绘制

过度绘制是指屏幕上的某一像素在同一帧的时间内被绘制多次,比如我们将父容器RelativeLayout设置背景颜色为白色,他的子布局item的背景颜色是绿色,那子布局所占的像素就会被绘制两次,第一次绘制为白色,第二次绘制为绿色,所以这样会非常的浪费系统的资源,造成过度绘制,页面卡顿。最常见的过度绘制就是绘制无用的背景颜色。

 

那我们如何发现过度绘制

在我们的Android手机上开发者模式下

 

设置 -> 开发者选项 -> 调试GPU过度绘制 -> 显示GPU过度绘制

 

打开之后会显示不同屏幕会显示四种不同的颜色,代表该像素区域被绘制的次数,这时我们切换到我们想要测试的app应用程序中就可以发现被过度绘制的地方。

蓝色,淡绿,淡红,深红代表了4种不同程度的Overdraw情况,

蓝色: 意味着overdraw 1倍。像素绘制了两次。大片的蓝色还是可以接受的(若整个窗口是蓝色的,可以摆脱一层)。

绿色: 意味着overdraw 2倍。像素绘制了三次。中等大小的绿色区域是可以接受的但你应该尝试优化、减少它们。

淡红: 意味着overdraw 3倍。像素绘制了四次,小范围可以接受。

深红: 意味着overdraw 4倍。像素绘制了五次或者更多。这是错误的,要修复它们。

我们的目标就是尽量减少红色Overdraw,看到更多的蓝色区域。

 

解决问题的工具和方法

通过Hierarchy Viewer去检测渲染效率,去除不必要的嵌套

通过Show GPU Overdraw去检测Overdraw,最终可以通过移除不必要的背景。

 

本人对于UI优化理解来自于简书大神 大圣代 原文地址:https://www.jianshu.com/p/906cd1af2ce7

 

二、内存优化

内存泄漏

StackOverflowError(SOF)

内存泄漏表现为:该被回收的内存没有被回收,导致的内存浪费。

 

引起内存泄漏的根本原因是两个对象的生命周期不一样

也就是说 持有引用者的生命周期 > 被引用者的生命周期

 

发生内存泄漏的场景

 

在Handler中做延迟操作,及时Activity销毁了 因为Handler持有Activity该Activity也不会被销毁

非静态内部类,默认持有外部类的引用。要解决内存泄漏我们需要将内部类改为静态内部类+软引用的方法,防止内存泄漏。

 

对象没有及时清理引起的内存溢出,将对象存入集合中以后,如果不对集合进行清理,那么集合便会一直持有这些对象的引用,导致GC不会回收内存,出现内存泄漏。

 

如果堆内存中的内存与对象的引用断了该对象才能被回收。

比如 创建一个Student类 类中有年龄、身高等属性

Student stu = new Student();

Student stu1 = new Student();

 

stu=null;

Student类是在堆内存中,而stu/stu1便是指向Student类在堆内存中的地址

这时该对象还是不会被GC回收 因为他还存在其他的引用 这个引用就相当于指针,我们将对象置空就相当于切断了引用。

 

如果该对象的引用被别人持有 该对象在堆中的内存就算使用完了也不会被回收,造成内存泄漏。

GC 会通过可达性分析算法 来判断该对象是否被回收。

 

可以利用LeakCanary来监视一个即将被销毁的对象

 

内存溢出

OutOfMemoryError(OOM)

分为 堆溢出栈溢出

 

栈溢出指的是java虚拟机栈的栈溢出、java虚拟机栈是 方法运行时便会压栈(进栈) 当方法执行完成之后弹栈(出栈),所以当我们正常调用方法,就不会内存溢出,而有些特定的情况,比如方法中嵌套调用方法,比如递归,这个时候方法会不停的压栈,而栈的特性就是先进先出,所以当我们不停的压栈,直到超过java虚拟机的深度,便会出现内存溢出的异常。

 

堆溢出 因为系统给每个应用申请的内存是不一样的当我们使用RecyclerView 或者 ListView的时候,如果加载大量的图片 不进行优化缓存(删除最近最少使用) 便会超出系统为我们分配的内存从而导致溢出。当我们不断的new 对象也会溢出

 

三、BitMap优化

 

三级缓存

首先是从内存中查找 如果有就显示 否则就去查询磁盘

然后再磁盘中查找 如果有就显示 否则就去请求网络

请求网络然后重新 放入磁盘和内存

 

加载图片

 

先从服务器请求图片 然后再手机内存中开辟一块内存空间(但不直接下载到内存)

,然后直接下载到磁盘,然后再通过磁盘加载到内存中,磁盘

到内存的过程中有一个二次采样,对图片进行处理,

常见的图片加载框架如Glide Fresco 内部都做了三级缓存,而CPU为每一个APP

分配的内存都是有限的,所以手机缓存图片要进行一个算法的优化,如Glide等

内部使用了LinkedHashMap 相当于队列 每次加载新图片加入一个时间戳,

从对头放入队列,如果队尾或队中的图片被再次使用,我们就将它放到对头中,

并且定义一个变量 将每次加载图片的大小 加到size变量中,

我们设置一个图片最大占用内存值 每次放入图片都进行比较,如果大于规定的

最大内存,就将队尾的图片删除 ,直到小于规定的最大内存值,删除的就根据

图片的时间戳 和 最大内存值 将最近最少使用的图片移出内存,从而实现优化。

 

二次采样

 

我们从磁盘获取图片时,有时我们磁盘中的图片是一张比如400*400像素的而我们只需要显示

200*200像素的图片,所以将图片加载到内存中时 第一次只获取图片的宽高 然后第二次采样获

取图片 根据比例转换为我们需要的大小 再加载到内存中。

具体实现

BitmapFactory.Options

第一次采样我们将options.inJustDecodeBounds方法设置为true只是去读取图片的大小

在拿到图片的大小之后和要显示的大小做比较通过calculateInSampleSize()函数计算inSampleSize的具体值,得到值之后。options.inJustDecodeBounds设为false读图片资源。

 

四、网络优化

 

网络优化的目的就是为了节省流量,提高用户体体验。

 

如果没有经过网络优化 比如新闻类的列表 如果没有进行网络优化,那么每次我们要展示列表的时候就要去请求网络,每次刷新都要去请求网络,如果网络环境不好,这样既耗费流量,又慢,会让用户的体验极差所以我们要进行一个优化。

 

Http有多种缓存规则 我将其分为两大类(强制缓存,对比缓存)

已存在缓存数据时,仅基于强制缓存,请求数据的流程如下

已存在缓存数据时,仅基于对比缓存,请求数据的流程如下

那么如何对网络进行优化?

 

优化思路便是不要去重复的去请求已经请求过的数据,所以我们可以在每次加载列表的时候,加一个时间戳,或者是任意标记,让我们的后台在接口中添加一个时间戳的参数,我们每次请求完数据之后,获取一个时间戳,保存在本地,将请求到的数据也保存在磁盘中,设置一个时间比如两周以后自动删除缓存,服务器也保存时间戳,每次有新的数据的时候就更新时间戳,那我们客户端请求的时候就可以先与服务器的时间戳做对比,如果服务器的时间戳大于我们本地的时间戳 那我们就请求本地保存的时间戳到服务器最新的时间戳之间的数据,浏览过的数据因为已经保存在本地直接读取本地,然后将新获取的数据也保存在本地,这样就可以达到一个网络优化的效果。

 

浏览器第一次请求数据时,服务器会将缓存标识与数据一起返回给客户端,客户端将二者备份至缓存数据库中。
再次请求数据时,客户端将备份的缓存标识发送给服务器,服务器根据缓存标识进行判断,判断成功后,返回304状态码,通知客户端比较成功,可以使用缓存数据。

第一次访问

第二次访问

通过两图的对比,我们可以很清楚的发现,在对比缓存生效时,状态码为304,并且报文大小和请求时间大大减少。
原因是,服务端在进行标识比较后,只返回header部分,通过状态码通知客户端使用缓存,不再需要将报文主体部分返回给客户端。

 

HTTP协议中其实已经为我们提供了对比缓存所需要的标记


Last-Modified:

服务器在响应请求时,告诉浏览器资源的最后修改时间。

If-Modified-Since:

再次请求服务器时,通过此字段通知服务器上次请求时,服务器返回的资源最后修改时间。

Etag:

服务器响应请求时,告诉浏览器当前资源在服务器的唯一标识(生成规则由服务器决定)。

If-None-Match:

再次请求服务器时,通过此字段通知服务器客户段缓存数据的唯一标识。