Android - 编写优质程序从内存管理开始

前言

Java 没有指针;Java 自带内存回收机制,不用管理内存。
这大概是很多不负责任的大学老师教给同学们的错误概念。

虽然Java虚拟机有一套内存回收机制,但是我们写程序仍然需要注意内存管理,并不是传说中的只申请不释放,内存和性能是程序永恒的话题,Android开发中卡顿往往都是由于内存的问题。

最近在着手公司项目的内存优化,做个总结,下文全是干货。

Java对象的生命周期

上学的时候老师讲课为了让同学们容易理解把Java内存分为堆(Heap)和栈(Stack),在《深入Java虚拟机》书中讲到完整版应该是这样的,在Java世界中,内存分为:

  1. 虚拟机栈(java stacks)
  2. 堆(heap)
  3. 方法区(method area)
  4. PC寄存器/程序计数器(pc registers)
  5. 本地方法栈(native method stacks)

这里就不过多介绍每一个内存区域都是做什么用的,详情查看《深入Java虚拟机》一书。

出生

死亡

  1. 引用计数

  2. 根集算法

Java语言的特性

多态

1
List<String> strs = new ArrayList<String>();

父类引用指向子类对象,使其可以调用子类的方法。其原理是虚表,学过C++的同学对这个应该很熟悉。

JIT 影响for循环的执行效率

JIT - just in time,即时编译技术。使用该技术,能够加速java程序的执行速度。

1
2
3
4
5
static class Foo {
int mSplat;
}
Foo[] mArray = ...
1
2
3
4
5
6
public void zero() {
int sum = 0;
for (int i = 0; i < mArray.length; i++) {
sum += mArray[i].mSplat;
}
}
1
2
3
4
5
6
7
8
public void one() {
int sum = 0;
final Foo[] localArray = mArray;
int length = localArray.length;
for (int i = 0; i < length; i++) {
sum += localArray[i].mSplat;
}
}
1
2
3
4
5
6
public void two() {
int sum = 0;
for (Foo a : mArray) {
sum += a.mSplat;
}
}

有上面三个zero() one() two() 三个for循环的函数,其中:

  • zero() 最慢,因为JIT不能去优化mArray.length,无论有没有JIT都是最慢的;
  • one()zero() 快,无论有没有JIT;
  • two() 在没有JIT的情况下最快,而如果有JIT,其速度几乎和zero()一样慢。

一般情况下,应该使用two()这种增强型for循环,如果性能敏感的情况下,遍历ArrayList这样的线性结构的数据,应该采用one()

内部类

非静态内部类天生持有宿主类的一个引用,所以如果内部类泄露了会导致宿主类也跟着遭殃。

Android平台的特性

onTrimMemory()

当内存不足时,系统会主动触发onTrimMemory(),这时你应该主动回收掉一些不用的内存。

数据结构选择

Bitmap —> LruCache

HashMap — > ArrayMap / SparseArray

List —> LinkedList / ArrayList

不推荐使用:
Vector
Enum

findView

这个是个比较耗时的操作,所以应该避免一个View多次find。

Android平台内存调优方法

通过adb shell查看内存泄露

1
adb shell dumpsys meminfo 包名

Dump HPROF file

MAT(Eclipse Memory Analysis Tool)

常见案例

单例

这个比较常见,就是声明了单例,如果里面有大内存数据,在程序退出的时候应该主动去回收掉。

内部类 泄露

经典例子为 Handler 作为内部类时发生的泄露。

静态引用

1
2
3
4
5
6
7
8
9
public class ProgressHelper {
private static TextView mTextView;
public static void update(String text) {
mTextView.setText(text);
}
}

有时候业务需要使用静态应用会得到一定的便利,但是请注意这是很危险的,如果一定要这样做,请使用WeakReference包一层。

1
2
3
public class ProgressHelper {
private static WeakReference<TextView> mWeakTextView;
}

Context 泄露

一般来说等同于Activity泄露

Bitmap 泄露

  • Bitmap对象需要手动调用recycle
  • 切记不要把一张巨大的图,不缩放就放入控件里了。

    如果一张图是2000*2000,比手机分辨率还大,但是你程序的ImageView只有300*300的大小,你需要先把2000*2000的图缩小成300*300再设置给ImageView。这样一定不会出现OOM的情况。

飞线

一个类使用了另一个类的静态资源。

A.java
1
public static boolean isOk = false;
B.java
1
2
3
if (A.isOK) {
}

这种情况和静态引用一样,使用WeakReference包一层,即可。

内存抖动

内存的不停申请和释放会导致内存抖动,最可能出问题的就是频繁的使用 “” + “” 的方式生产新的字符串。

1
2
3
4
5
String newStr = "";
for (int i = 0; i < x.length; i++) {
newStr += x[i].str;
}

因为字符串相加得到的是一个新的字符串,所以如果遇到这种情况,应该使用StringBuilder

1
2
3
4
5
6
7
StringBuilder sb = new StringBuilder();
for (int i = 0; i < x.length; i++) {
sb.append(x[i].str);
}
String newStr = sb.toString();;

经验

开源库

相关资料

Gavin Liu wechat
欢迎您扫一扫上面的二维码,订阅我的微信公众号!