Android性能优化学习记录(二)稳定性
这里写目录标题
一、稳定性
影响Android
应用稳定性的原因有很多,主要是:应用崩溃(Crash)
、应用无响应(ANR)
这2个错误的结果将导致程序无法使用
ApplicationNotResponding
ApplicationNotResponding
(应用程序无响应)是卡死在一个页面。Application(应用)在5s内未响应用户输入事件(如按键or触摸),广播接收器(BroadcastReceiver)在10秒内未完成相关的处理,服务(Service)在20秒内无法处理完成,会出现对话框让用户选择等待或强制关闭。
- 优化原理
尽量避免应用程序出现ANR
情况 - 优化方案
使用多线程,将大量 & 耗时操作放在多线程中执行
- 多线程的方式 包括:
AsyncTask
、继承 Thread类
、实现 Runnable接口
、Handler消息机制
、HandlerThread
等
- 注:实际开发中,当一个进程发生了ANR后,系统会在 /data/anr目录下创建一个文件
traces.txt
,通过分析该文件可定位出ANR的原因
Crash
- 优化原理
应用崩溃Crash
很多情况是因为 内存溢出,即OOM
;故 需避免出现OOM
现象
内存溢出 简介:OOM(Out Of Memory)指应用程序所需的内存 超出了 系统为其分配的内存限额的现象。当应用程序中出西安内存泄露较多、不正常使用内存等情况时,容易导致应用程序所需的内存 超出了 系统为其分配的内存限额。会导致应用崩溃(Crash)。
解决方案:内存优化
稳定性总结
二、内存优化
定义:优化处理 应用程序的内存使用、空间占用
作用
避免因不正确使用内存 & 缺乏管理,从而出现 内存泄露(ML)
、内存溢出(OOM)
、内存空间占用过大 等问题,最终导致应用程序崩溃(Crash
)
1. 储备知识:Android 内存管理机制
1.1 简介
Android 内存管理机制 : 是对 进程、对象、变量 进行的内存 分配和回收 的机制。
Android 内存管理机制中负责 进程内存 的角色是 Application Framework
和 Linux内核
。负责 对象、变量内存 的角色是 Dalvik虚拟机
。
下面,将针对回收 进程、对象 、变量的内存分配 & 回收进行详细讲解
1.2 针对进程的内存策略
a. 内存分配策略
由 ActivityManagerService
集中管理 所有进程的内存分配
b. 内存回收策略
- 步骤1:
Application Framework
决定回收的进程类型
Android中的进程 是托管的;当进程空间紧张时,会 按进程优先级低->>高的顺序 自动回收进程
Android将进程分为5个优先等级,具体如下:
- 步骤2:
Linux
内核真正回收具体进程
ActivityManagerService
对 所有进程进行评分(评分存放在变量adj
中)- 更新评分到
Linux
内核 - 由
Linux
内核完成真正的内存回收
此处仅总结流程,这其中的复杂过程,需要研究系统源码ActivityManagerService.java
1.3 针对对象、变量的内存策略
Android
的对于对象、变量的内存策略同Java
- 内存管理 = 对象 / 变量的内存分配 + 内存释放
下面,将详细讲解内存分配 & 内存释放策略
a. 内存分配策略
- 对象 / 变量的内存分配 由程序自动 负责
- 共有3种:静态分配、栈式分配、 & 堆式分配,分别面向静态变量、局部变量 & 对象实例
- 具体介绍如下
内存分配策略 | 使用的内存空间 | 存储的数据 | 分配策略描述 |
---|---|---|---|
静态分配 | 方法区(静态存储区) | 已经被虚拟机加载的 类信息、常量、静态变量 | 程序编译时已分配好 并存在于程序的整个运行期间(不回收) |
栈式分配 | 栈区(Stack) | 方法执行时的局部变量(数据类型、对象的引用)(以帧栈的形式) | 1. 方法执行时,定义局部变量由程序在栈中分配内存 |
- 方法执行结束/超出变量域时,帧栈自动释放该部分内存
- 效率高(因为栈内存分配运算内置于处理器的指令集中)
- 分配的内存容量有限 |
| 堆式分配(动态内存分配) | 堆区(Heap) | Java对象的实例 与 实例内的成员变量
也就是关键字new出来的对象
实例的成员变量 = 基本数据类型、引用和引用的对象实体 | 1. 创建对象实例时,由程序分配(由Java垃圾回收管理器自动管理,不使用时则回收) - 在堆中创建1个对象或数组与在栈中定义一个引用变量 等于这个数组或对象在堆内存中的首地址
- 通过引用变量来访问堆内存中的对象或数组 |
注:用1个实例讲解 内存分配
public class Sample {
// 该类的实例对象的成员变量s1、mSample1 与 指向对象存放在堆内存中
int s1 = 0;
Sample mSample1 = new Sample();
// 方法中的**局部变量s2、mSample2存放在 栈内存**
// 变量mSample2所指向的**对象实例存放在 堆内存**
public void method() {
int s2 = 0;
Sample mSample2 = new Sample();
}
}
// 变量mSample3的**引用存放在栈内存中**
// 变量mSample3所指向的**对象实例存放在堆内存**
// 该实例的成员变量s1、mSample1也存放在堆内存中
Sample mSample3 = new Sample();
b. 内存释放策略
- 对象 / 变量的内存释放 由Java垃圾回收器(GC) / 帧栈 负责
- 此处主要讲解对象分配(即堆式分配)的内存释放策略 = Java垃圾回收器(GC)
静态分配不需释放、栈式分配仅 通过帧栈自动出、入栈,
- Java垃圾回收器(GC)的内存释放 = 垃圾回收算法,主要包括:
- 标记-清除算法
- 复制算法
- 标记-整理算法
- 分代收集算法
- 具体介绍如下
算法名称 | 算法思想 | 优点 | 缺点 | 应用场景 |
---|---|---|---|---|
标记-清除算法 | 标记阶段:标记出所有需要回收的对象 | |||
清楚阶段:统一清除(回收)所有被标记的对象 | 实现简单 | 效率问题:标记和清除两个过程效率不高 | ||
空间问题:标记-清楚后,会产生大量不连续的内存碎片 | 对象存活率较低 并且 垃圾回收行为频率低(如老年代) | |||
复制算法 | 将内存分为大小相等的两块,每次使用其中一块 | |||
当使用的这块内存用完,就将这块内存上还存活的对象复制到另一块内存上 | ||||
将使用的那块内存一次清理掉 | 解决了标记清除算法清除效率低的问他,(每次仅回收内存一半区域 ) | |||
解决了标记清除算法空间产生不连续内存碎片的问题,(将已使用内存上的存活对象移动到栈顶的指针,按顺序分配内存) | 每次使用内存缩小为原来的一半 | |||
当对象存活率较高的情况下需要做很多复制操作,效率会变低 | 对象存活率较低 并且 需要频繁进行垃圾回收的区域(如新年代) | |||
标记整理算法 | 标记阶段:标记出所有需要回收的对象 | |||
整理阶段:所有存活对象都向一端移动 | ||||
清除阶段:统一清除(回收)端以外的对象 | 解决了标记清除算法中清楚效率低等问题:一次清除端外区域 | |||
解决了标记清除算法中空间产生不连续内存碎片问题:将已使用内存上的存活对象移动到栈顶的指针,按顺序分配内存即可 | 步骤繁多:标记、整理、清除 | 对象存活率较低并且垃圾回收行为频率低(如老年代) | ||
分代收集算法 | 根据对象存货周期不同将Java堆内存分为:新生代与老年代 | |||
新生代:对象存活率低 并且垃圾回收行为频率高 | ||||
新生代:采用复制算法 | ||||
老年代:采用标记-清除算法、标记-整理算法 | ||||
效率高、空间利用率高;根据不同区域特点选择不同垃圾收集算法 | 虚拟机基本都采用这种算法 |
2. 常见的内存问题 & 优化方案
常见的内存问题如下
- 内存泄露
- 内存抖动
- 图片Bitmap相关
- 代码质量 & 数量
- 日常不正确使用
下面,我将详细分析每项的内存问题 & 给出优化方案
2.1 内存泄露
即 ML (Memory Leak
),指 程序在申请内存后,当该内存不需再使用 但 却无法被释放 & 归还给 程序的现象
对应用程序的影响:容易使得应用程序发生内存溢出,即 OOM
内存溢出 简介:
OOM(Out Of Memory)指应用程序所需的内存 超出了 系统为其分配的内存限额的现象。当应用程序中出现内存泄露较多、不正常使用内存等情况时,容易导致应用程序所需的内存 超出了 系统为其分配的内存限额。会导致应用崩溃(Crash)。
- 发生内存泄露的本质原因:本该回收的对象因为某些原因而不能被回收,从而继续停留在堆内存中。
”本该被回收“指的是该对象已不需再被使用。
”某些原因“指的是有另外一个正在使用的对象持有它的引用,即无意识地持有对象。
**内存溢出本质原因是:**持有引用者 的生命周期>被引用者的生命周期,从而当后者需结束生命周期去销毁时,无法被正确回收。
- 常见内存泄露原因
- 集合类
Static
关键字修饰的成员变量- 非静态内部类 / 匿名类
- 资源对象使用后未关闭
- 优化方案:内存泄露
2.2 图片资源Bitmap相关
- 优化原因即 为什么要优化图片
Bitmap
资源?
因为Android系统分配给每个应用程序的内存有限,图片资源(Bitmap)非常消耗内存,很多情况下,图片所占的内存占整个App内存的大部分 - 如果对
Bitmap
的使用或内存管理稍有不当,就可能引发内存溢出(OutOfMemoryError),容易造成应用崩溃(Crash)
优化方向
主要 从 以下方面优化图片Bitmap资源的使用 & 内存管理
2.3 内存抖动
- 简介
内存抖动:内存大小不断浮动的现象。
是由于程序频繁地分配内存 与 垃圾收集器(GC)频繁地回收内存造成的。深层次原因是大量、临时的小对象频繁创建。
由于大量、临时的小对象频繁创建会导致内存碎片,使得当需分配内存时,虽总体上还是有剩余内存可分配,但这些内存不连续,导致无法整块分配。系统视为内存不够,会导致卡顿,甚至导致内存溢出OOM。
- 优化方案尽量避免频繁创建大量、临时的小对象
2.4 代码质量 & 数量
- 优化原因
代码本身的质量(如 数据结构、数据类型等) & 数量(代码量的大小)可能会导致大量的内存问题,如占用内存大、内存利用率低等 - 优化方案主要从代码总量、数据结构、数据类型、 & 数据对象引用 方面优化,具体如下
优化方向 | 优化方案 | 具体描述 | 备注 |
---|---|---|---|
代码数据总量 | 不必要的类、对象和功能库会带来巨大内存开销 | 减少不必要的类和对象 | |
减少引入不必要的库 | |||
使用代码混淆 | 代码混淆的作用:去除无用的代码 并 通过语义模糊 重命名类、字段和方法。从而缩小、优化代码,使得代码更简洁、更少量的RAM映射页。 | ||
尽可能减少数据体积大小 | 通过序列化数据从而减少数据体积大小(Google出品的ProtocolBuffer,可节省30%的数据大小) | 慎用Sharedprefercnce | |
因对于同一个sp,会将整个xml文件载入内存,容易出现为了读一个配置,就将几百k的数据读进内存。 | |||
数据结构 | 使用性能高的数据结构 | 利用Android优化后的数据容器取代传统的HashMap | ArrayMap和SparseArray是Android的系统API |
专门为移动设备定制,用于取代HashMap达到节省内存的目的 | |||
(a.传统的HashMap在内存上的实现十分低效的原因,需为map中每项在内存中建立映射关系 | |||
b.SparseArray类高效的原因,避免系统中需自动封箱(autobox)的key) | |||
数据类型 | 使用占用内存小的数据类型 | 尽量避免使用枚举类型 | 枚举变量占用内存大,比直接使用int类型多占用两倍2内存 |
数据对象引用 | 根据不同的应用场景,选择不同的引用类型(强、软、弱、虚) | 强引用:该变量不希望被垃圾回收器回收 | |
软引用:缓存机制(实现内存敏感的数据缓存,如图片、网页缓存等) | |||
弱引用:防止内存泄露、保护对象引用 | |||
虚引用:跟踪对象被垃圾回收器回收的活动 |
2.5 常见使用
- 优化原因
一些常见使用也可能引发大量的内存问题,下面详细介绍。 - 优化方案
注:
还有1个内存优化的终极方案:调大 虚拟机Dalvik
的堆内存大小
即 在AndroidManifest.xml
的application
标签中增加一个android:largeHeap
属性(值 =true
),从而通知虚拟机 应用程序需更大的堆内存
但不建议 & 不鼓励该做法
额外小技巧
- 技巧1:获取当前可使用的内存大小
调用 **ActivityManager.getMemoryClass()
**方法可获取当前应用可用的内存大小(单位 = 兆) - 技巧2:获取当前的内存使用情况
在应用生命周期的任何阶段,调用onTrimMemory()
获取应用程序 当前内存使用情况(以内存级别进行识别),可根据该方法返回的内存紧张级别参数 来释放内存
Android 4.0 后提供的一个API
- 技巧3:当视图变为隐藏状态时,则释放内存
当用户跳转到不同的应用 & 视图不再显示时, 应释放应用视图所占的资源
注:此时释放所占用的资源能显著的提高系统的缓存处理容量
具体操作:实现当前Activity
类的onTrimMemory()
后,当用户离开视图时会得到通知;若得到返回的参数 = TRIM_MEMORY_UI_HIDDEN
即代表视图变为隐藏状态,则可释放视图所占用的资源.
内存优化总结
参考资料
https://carsonho.blog.csdn.net/article/details/79708444
Android开发艺术探索