内存泄漏:对象已经不用了,却被别的对象还握着引用,内存收不回去。慢慢堆积最终 OOM。写代码时就要避开这类写法,提高健壮性。

一、静态变量
Java 里静态变量从类加载活到类卸载;Android 里就是进程活着它就在。进程不杀,静态变量不会被回收。若它强引用了某个 Activity 的对象,Activity 即使走了 onDestroy 也释放不了(不要把执行 onDestroy 和被回收划等号)。
对策:1. 找生命周期差不多的替代对象;2. 找不到就把强引用改弱引用。典型:单例拿了 Activity 的 Context。
public class IMManager {
private Context context;
private static IMManager mInstance;
public static IMManager getInstance(Context context) {
if (mInstance == null) {
synchronized (IMManager.class) {
if (mInstance == null)
mInstance = new IMManager(context);
}
}
return mInstance;
}
private IMManager(Context context) {
this.context = context;
}
}
getInstance 若传入 Activity 的 context,单例不释放,Activity 也不释放。应传入 Application 的 context,生命周期与单例相当:
public class IMManager {
private Context context;
private static IMManager mInstance;
public static IMManager getInstance(Context context) {
if (mInstance == null) {
synchronized (IMManager.class) {
if (mInstance == null)
//将传入的context转换成Application的context
mInstance = new IMManager(context.getApplicationContext());
}
}
return mInstance;
}
private IMManager(Context context) {
this.context = context;
}
}
二、非静态内部类
非静态内部类实例会引用外围实例。若它做了耗时操作,外围对象收不掉。对策:1. 改成静态内部类;2. 强引用 Activity 属性则改弱引用;3. 业务允许时在 onDestroy 结束耗时任务。
内部线程
public class LeakAty extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.aty_leak);
test();
}
public void test() {
//匿名内部类会引用其外围实例LeakAty.this,所以会导致内存泄漏
new Thread(new Runnable() {
@Override
public void run() {
while (true) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}).start();
}
}
把方法改成 static,匿名内部类不再持有 Activity:
public class LeakAty extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.aty_leak);
test();
}
//加上static,变成静态匿名内部类
public static void test() {
new Thread(new Runnable() {
@Override
public void run() {
while (true) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}).start();
}
}
Handler
public class LeakAty extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.aty_leak);
fetchData();
}
private Handler mHandler = new Handler() {
public void handleMessage(android.os.Message msg) {
switch (msg.what) {
case 0:
// 刷新数据
break;
default:
break;
}
};
};
private void fetchData() {
//获取数据
mHandler.sendEmptyMessage(0);
}
}
匿名 Handler 会引用 LeakAty.this;Activity 退出时若还有消息要处理,Activity 就不会被回收。正确写法:静态内部类 + 弱引用 + onDestroy 清消息。
public class LeakAty extends Activity {
private TextView tvResult;
private MyHandler handler;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.aty_leak);
tvResult = (TextView) findViewById(R.id.tvResult);
handler = new MyHandler(this);
fetchData();
}
//第一步,将Handler改成静态内部类。
private static class MyHandler extends Handler {
//第二步,将需要引用Activity的地方,改成弱引用。
private WeakReference<LeakAty> atyInstance;
public MyHandler(LeakAty aty) {
this.atyInstance = new WeakReference<LeakAty>(aty);
}
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
LeakAty aty = atyInstance == null ? null : atyInstance.get();
//如果Activity被释放回收了,则不处理这些消息
if (aty == null || aty.isFinishing()) {
return;
}
aty.tvResult.setText("fetch data success");
}
}
private void fetchData() {
handler.sendEmptyMessage(0);
}
@Override
protected void onDestroy() {
//第三步,在Activity退出的时候移除回调
super.onDestroy();
handler.removeCallbacksAndMessages(null);
}
}
三、资源未关闭
用了 BroadcastReceiver、File、Cursor、Bitmap 等,不用时要及时释放,否则泄漏。
四、Adapter 没用 convertView
ListView 的 BaseAdapter.getView(int position, View convertView, ViewGroup parent) 给每一项提供 View。一开始按屏幕实例化一批并缓存;向上滚时,最上面的 item View 被回收,拿去构造新出现的最下面一项。convertView 就是缓存下来的那块(初始化没有缓存时为 null)。每次 getView 都 new 一个 View,既浪费时间,也制造内存垃圾,回收跟不上就迫使 VM 再给进程加内存。
五、不良代码造成内存压力
有些代码不一定泄漏,但对没用的内存释放不及时,或频繁申请新对象,同样会把 VM 压高。Bitmap 尤其要注意:
-
及时销毁:系统最终会回收 Bitmap,但占用太大容易超 Java 堆。用完 recycle,不能保证立刻释放,但会提示虚拟机「这张图可以放了」
-
设采样率:显示区域很小就不必加载整图。inSampleSize = 2 时宽高各一半,像素约为原来的 1/4
private ImageView preview;
BitmapFactory.Options options = new BitmapFactory.Options();
options.inSampleSize = 2;//图片宽高都为原来的二分之一,即图片为原来的四分之一
Bitmap bitmap = BitmapFactory.decodeStream(cr.openInputStream(uri), null, options);
preview.setImageBitmap(bitmap);
六、清单
| 场景 |
做法 |
| 静态单例 |
用 Application Context,或弱引用 |
| 非静态内部类 / 线程 / Handler |
改静态 + 弱引用,onDestroy 停任务、清消息 |
| Cursor / 文件 / 广播 / Bitmap |
谁创建谁关闭,Activity 销毁时注销 |
| ListView Adapter |
复用 convertView |
| 大图 |
recycle + inSampleSize |
-
对 Activity 等组件的引用应控制在其生命周期内;不行就 getApplicationContext / getApplication
-
尽量不要在静态变量或静态内部类里用非静态外部成员(含 context);要用就适时置空,或弱引用
-
生命周期比 Activity 长的内部类:改静态内部类 + 弱引用外部成员
-
Handler 持有的对象用弱引用;onStop / onDestroy 取消 Message 和 Runnable
-
不用的对象显式赋 null;Bitmap 先 recycle 再 null;数组 clear 后 null;谁创建谁释放
-
BroadcastReceiver、ContentObserver、File、Cursor、Stream、Bitmap 在销毁时关闭或注销
-
一句话总结:泄漏几乎都是「短命对象被长命引用抓住」:单例用 Application、内部类改静态加弱引用、资源用完就关。