Android性能优化-内部类引发的内存泄漏

Index

摘要:本文描述内部类与内部静态类区别以及内存泄漏问题。

首先看内部类示例用法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
/**
* 外部类
*/
public class OuterClass {
/**
* 内部类
*/
public class InnerClass {
}
/**
* 错误的内部类用法
*/
public static void testInnerClassError() {
// 错误提示,编译不过
InnerClass innerClass = new InnerClass();
}
/**
* 正确的内部类用法
*/
public static void testInnerClass() {
OuterClass outerClass = new OuterClass();
InnerClass innerClass = outerClass.new InnerClass();
}
}

testInnerClassError方法中会提示错误“No enclosing instance of type OuterClass is accessible. Must qualify the allocation with an enclosing instance of type OuterClass (e.g. x.new A() where x is an instance of OuterClass).”,这么提示是因为内部类实例必须关联一个外部类的实例。

testInnerClass方法首先实例化外部类Outer,然后通过该实例创建一个关联的内部类实例。由于内部类实例必须与外部类实例关联,即外部类实例引用了内部类实例,因此在当内部类实例被其它实例强引用时会导致外部类实例无法在某段时间内正常回收,从而导致了外部类实例的内存泄漏。

以下是一段短期内内存泄漏的示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class MainActivity extends Activity {
public class MyHandler extends Handler {
@Override
public void handleMessage(Message msg) {
}
};
private MyHandler mHandler = new MyHandler();
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mHandler.sendMessageDelayed(Message.obtain(), 60000);
finish();
}
}

mHandler.sendMessageDelayed将创建的Message对象强引用mHandler并放入到主线程Looper中的MessageQueue,因为主线程需要经过60秒后才处理该Message对象,所以Acitvity即使调用finish也无法被垃圾回收器回收。

在Android开发中建议使用静态内部类,首先看看静态内部类用法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/**
* 外部类
*/
public class OuterClass {
/**
* 静态内部类
*/
public static class StaticInnerClass {
}
/**
* 静态内部类用法
*/
public static void testStaticInnerClass() {
StaticInnerClass staticInnerClass = new StaticInnerClass();
}
}

使用静态内部类创建类实例时并不需要依赖外部类实例,因此不会因为了内部静态类实例被其它对象引用导致外部类实例无法释放。那么上一个Activity例子中怎么改写才能让垃圾回收期正常回收呢?答案是将MyHandler改写为静态内部类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class MainActivity extends Activity {
public static class MyHandler extends Handler {
@Override
public void handleMessage(Message msg) {
}
};
private MyHandler mHandler = new MyHandler();
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mHandler.sendMessageDelayed(Message.obtain(), 60000);
finish();
}
}

最后总结一点,内存泄漏是创建的实例在其它对象中存在一个生命周期更长的强引用导致。这里Java描述的内存泄漏和C描述的内存泄漏有些不一样,在C中当指向堆中的指针不被记录,那么这段内存将无法在被使用而导致内存泄漏,而在Java中只是在某段生命周期内无法回收,如果不再被强引用仍然能再次被使用。