文章目录
- 性能优化的目的及方向
- 流畅性
- 启动速度
- 页面显示速度
- 响应速度
- 稳定性
- ANR
- Crash
- 资源节省性
- 布局优化
- 选择耗费性能较少的布局
- 减少布局的层级(嵌套)
- 使用布局标签
- 尽量少用布局属性wrap_content
- include
- merge
- include与merge的区别
- ViewStub
- 内存泄露
- 常见内存泄露原因
- 集合类添加元素
- Static关键字修饰成员
- 非静态内部类 / 匿名类
- 资源对象使用后未关闭
- 内存优化
性能优化的目的及方向
性能优化的目的是为了让应用程序App 更快、更稳定 & 更省。具体介绍如下:
- 更快:应用程序 运行得更加流畅、不卡顿,能快速响应用户操作
- 更稳定:应用程序 能 稳定运行 & 解决用户需求,在用户使用过程中不出现应用程序崩溃(Crash) 和 无响应(ANR)的问题
- 更省:节省耗费的资源,包括 内存占有、电池量、网络资源等
针对上述目的,APP优化的指标就是:流畅性、稳定性、资源节省性。
具体实现如下:
流畅性
减少使用中的卡顿、响应时间久等问题,给用户一个操作流畅的体验,主要针对3个方面进行优化:
- 启动速度
- 页面显示速度
- 响应速度
启动速度
优化原因:初次加载应用,需要加载很多资源或者逻辑功能。
优化方案:采用异步加载(多线程)、分步加载、延期加载等策略,减少启动应用时的加载任务,从而提高启动速度。
页面显示速度
优化原因:页面需绘制的内容(布局或控件)太多,导致页面测量时间太长;页面绘制效率过低,导致绘制时间过长。
优化方案:布局优化与绘制优化
响应速度
优化原因:应用程序出现 ANR 情况,从而导致 应用程序响应速度慢
优化方案:使用多线程,将大量耗时操作放在工作线程中执行。
优化方案:使用多线程,将大量耗时操作放在工作线程中执行,比如AsyncTask,HandlerThread等等
稳定性
影响Android 应用稳定性的原因有很多,主要是:应用崩溃(Crash)、应用无响应(ANR)
ANR
程序出现ANR(Application Not Responding,应用程序无响应),导致应用程序响应慢或者屏幕卡死在一个页面。
常见的ANR原因:
- 应用在5s内未响应用户输入的事件(按键或触摸)。
- 广播接收器(BroadcastReceiver)在10s内未完成相关事件的处理。
- 服务(Service)在20s内无法处理完成。
优化方案:使用多线程,将大量耗时操作放在工作线程中执行,比如AsyncTask,HandlerThread等等
实际开发中,当一个进程发生了ANR,系统会在 /data/anr目录下创建一个文件 traces.txt,通过分析该文件可定位出ANR的原因
Crash
程序出现Crash(崩溃)很多情况是因为内存溢出即OOM。
Android系统为每个应用程序分配的内存有限,当应用程序中出现内存泄露较多,不正常使用内存等情况时,容易导致应用程序出现所需的内存超出系统能够为其分配的内存限额(OOM),从而导致程序崩溃。
解决Crash就是解决OOM,涉及到的工作就是内存优化。
资源节省性
优化原因:由于移动设备的硬件性能有限,故减少应用程序的资源消耗显得十分重要
优化方向:内存大小、安装包大小、耗电量 & 网络流量
主要手段包含:内存优化、减少安装包大小、减少网络流量、减少应用耗电量。
布局优化
布局性能的好坏主要影响Android中页面显示速度,布局影响性能的实质在于:页面的测量与绘制时间
选择耗费性能较少的布局
- 耗费低的布局 = 功能简单 =
FrameLayout、LinearLayout
- 耗费高的布局 = 功能复杂 =
RelativeLayout
减少布局的层级(嵌套)
- 原理:布局层级少 ->> 绘制的工作量少 ->> 绘制速度快 ->> 性能提高
- 优化方式:使用布局标签 & 合适选择布局类型
注意:宁愿选择1个耗费性能高的布局,也不采用嵌套多个耗费性能低的布局
使用布局标签
尽量少用布局属性wrap_content
布局属性wrap_content 会增加布局测量时计算成本,应尽可能少用;特别是在已知宽高为固定值时,不使用wrap_content。
include
include>标签可以实现在一个layout中引用另一个layout的布局,这通常适合于界面布局复杂、不同界面有共用布局的APP中,比如一个APP的顶部布局、侧边栏布局、底部Tab栏布局、ListView和GridView每一项的布局等,将这些同一个APP中有多个界面用到的布局抽取出来再通过
<include>
标签引用,既可以降低layout的复杂度,又可以做到布局重用(布局有改动时只需要修改一个地方就可以了)。
merge
merge标签是用来帮助在视图树中减少重复布局的,当一个layout包含另外一个layout时。
merge使用的要点:
- merge必须放在布局文件的根节点上
- merge并不是一个ViewGroup,也不是一个View,它相当于声明了一些视图,等待被添加。
- merge标签被添加到A容器下,那么merge下的所有视图将被添加到A容器下。
- 因为merge不是View,所有它没法设置标签属性,或者说设置了是无效的。
- 因为merge标签并不是View,所以在LayoutInflate.inflate方法渲染的时候,第二个参数必须指定一个父容器,且第三个参数必须为true,也就是必须为merge下的视图指定一个父亲节点。
实例:
不使用merge:
layout1.xml
<FrameLayout>
<include layout="@layout/layout2"/>
</FrameLayout>
layout2.xml
<FrameLayout>
<TextView />
</FrameLayout>
实际效果:
<FrameLayout>
<FrameLayout>
<TextView />
</FrameLayout>
</FrameLayout>
使用merge:
layout1.xml
<FrameLayout>
<include layout="@layout/layout2"/>
</FrameLayout>
layout2.xml
<merge>
<TextView />
</merge>
实际效果:
<FrameLayout>
<TextView />
</FrameLayout>
include与merge的区别
从上边的例子中可以看到,include实际上是将另外一个layout完整的嵌入到需要使用的地方。由于所有的组件都必须在ViewGroup中,这就导致了,使用include标签,嵌入时,会多一层ViewGroup,而使用merge则相当于声明了一个虚拟的ViewGroup,使得在include时它可以完全融入merge到需要使用的地方。
ViewStub
ViewStup是一个轻量级的view,之所以说它是轻量级的view是因为它在页面加载渲染的过程中不会去绘制,只是在你需要的时候再去绘制。
ViewStub标签的作用是用于懒加载布局,当系统碰到ViewStub标签的时候是不进行绘制处理(如measure、layout等),比设置View隐藏、不可见更高效。
使用ViewStub的好处:
- 可以避免ViewStub所对应的视图measure、layout等性能消耗,只有在使用时才会执行,例如在APP启动时提升页面显示速度。
- 对比invisible和gone的用法,当设置为invisible,view对象会占用位置,只是状态不可见,对象还是会被创建、初始化、占用资源。如果设置为gone,view在layout布局文件中不占用位置,但对象还是会被创建、初始化、占用资源。
ViewStub的缺点在于不可使用<merge>
标签,它在使用上类似于<include>
标签
内存泄露
从机制上讲:Java存在GC,理应不存在内存泄露,出现内存泄露的原因仅仅是外部人为原因,无意识地持有对象引用,使得持有引用者的生命周期 > 被引用者的生命周期
常见内存泄露原因
集合类添加元素
内存泄露原因:集合类 添加元素后,仍引用着 集合元素对象,导致该集合元素对象不可被回收,从而 导致内存泄漏
// 通过 循环申请Object 对象 & 将申请的对象逐个放入到集合List
List<Object> objectList = new ArrayList<>();
for (int i = 0; i < 10; i++) {
Object o = new Object();
objectList.add(o);
o = null;
}
// 虽释放了集合元素引用的本身:o=null)
// 但集合List 仍然引用该对象,故垃圾回收器GC 依然不可回收该对象
解决方案:
由于1个集合中有许多元素,故最简单的方法 = 清空集合对象 & 设置为null
// 释放objectList
objectList.clear();
objectList=null;
Static关键字修饰成员
被 Static 关键字修饰的成员变量的生命周期 = 应用程序的生命周期.
泄露原因:被 Static 关键字修饰的成员变量 引用耗费资源过多的实例(如Context),则容易出现该成员变量的生命周期 > 引用实例生命周期的情况,当引用实例需结束生命周期销毁时,会因静态变量的持有而无法被回收,从而出现内存泄露
public class ClassName {
// 定义1个静态变量
private static Context mContext;
//...
// 引用的是Activity的context
mContext = context;
// 当Activity需销毁时,由于mContext = 静态 & 生命周期 = 应用程序的生命周期,故 Activity无法被回收,从而出现内存泄露
}
解决方案:
- 尽量避免 Static 成员变量引用资源耗费过多的实例(如 Context)。
- 使用 弱引用(WeakReference) 代替 强引用 持有实例
非静态内部类 / 匿名类
非静态内部类持有外部类的引用,导致外部类无法被释放。
常见情况有:
- 非静态内部类中创建静态的实例
- 多线程:AsyncTask等,当工作线程正在处理任务,它持有外部类的引用,导致外部类无法被GC
- 消息传递机制:Handler,Handler类持有外部activity的引用,当消息队列中还有未处理的消息时,消息队列中的
Message持有Handler实例的引用。这种引用依赖关系,导致Activity无法被销毁。
资源对象使用后未关闭
泄露原因:对于资源的使用(如广播、文件流、数据库操作的游标、图片资源BitMap),若在Activity销毁时,没有及时关闭或注销这些资源,从而导致内存泄露。
解决方案:
在Activity销毁时:及时关闭 / 注销资源