基本概念
- 内存泄露,即MemeryLeak,是平时Coding非常容易忽视的点..像我之前对泄露的认识和针对性都不强,写的代码马马虎虎就可以,毕竟Leak并不像OOM那样,小白都可以轻易发现且容忍度为0。对泄露的定义,参考Wiki:
内存泄漏指由于疏忽或错误造成程序未能释放已经不再使用的内存。内存泄漏并非指内存在物理上的消失,而是应用程序分配某段内存后,由于设计错误,导致在释放该段内存之前就失去了对该段内存的控制,从而造成了内存的浪费。
- 应该说优秀的程序设计完全可以避免内存泄露。以下是我对Android中内存泄露的简单理解和常见情景分析。
简单举例
类描述如下
class A{ B b; void setB(B b){ this.b = b } } class B{ }
A可以通过setB方法,持有B的引用。这时B对象若要销毁,但因为A还保存着B的强引用,所以GC无法回收,内存(对象B)泄露。
容易Leak的场景
- Context
- Android开发中许多场景都要用到Context,但是Context由于占用内存大/传递频繁,所以是造成内存泄露的大户。比如一后台Service需要拿到Context,传入当前Activity,但是由于Service的生命周期往往大于Activity,当退出该Activity时这部分内存无法回收,造成泄露。
匿名内部类
- 会默认持有外部类的引用,同Context类似,如果该内部类生命周期大于外部类,内部类还未销毁/释放资源时外部类试图销毁,就会造成外部类这部分内存泄露。
最常见的就是Handler的使用。多数写法为:
Handler mHandler = new Handler(){ @Override public void handleMessage(Message msg) { super.handleMessage(msg); } };
应改为内部持有weakreference
。 protected MyHandler mHandler; public static class MyHandler extends Handler { WeakReference<BaseActivity> mWeakReference; public MyHandler(BaseActivity mAct) { this.mWeakReference = new WeakReference<>(mAct); } @Override public void handleMessage(Message msg) { BaseActivity act = mWeakReference.get(); if (act != null) { act.handleMessage(msg);//转调Activity方法 } } }
- 心得
- 使用匿名内部类时要记住,内部类生命周期一定要小于外部类。
- 使用Context时尽量考虑使用ApplicationContext,它的生命周期和整个进程所绑定。
- 使用Weakreference持有引用,这样不会影响GC。
使用LeakCanary
Square出品,见https://github.com/square/leakcanary,使用起来超级方便。只需要在Application中onCreate添加一句话:
LeakCanary.install(this);
不过这里只考虑了监听Activity,因为内存泄露影响最大的就是Activity的泄露,这块内存大而且泄露后会一定程度造成程序卡顿,除非退出该进程;如需单独监视一块内存,可以使用LeakCanary.install(this)返回的RefWatcher对象,调用其watch方法。
一个简单的使用Demo,LeakActivity开线程跑任务,退出后会提示Leak,如图
public class LeakActivity extends BaseActivity implements View.OnClickListener { @InjectView(R.id.btn_1) Button mBtn1; @InjectView(R.id.btn_2) Button mBtn2; @InjectView(R.id.btn_3) Button mBtn3; private BackgroundTask mBackgroundTask; private Thread mBackgroundThread; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); LeakCanary.install(this); setContentView(R.layout.activity_leak); ButterKnife.inject(this); mBtn1.setOnClickListener(this); mBtn2.setOnClickListener(this); mBtn3.setOnClickListener(this); } @Override public void onClick(View v) { switch (v.getId()) { case R.id.btn_1: //开启后台线程,持有该Activity if (mBackgroundTask == null) { mBackgroundTask = new BackgroundTask(); mBackgroundTask.setLeakObject(this); mBackgroundThread = new Thread(mBackgroundTask); mBackgroundThread.start(); } break; case R.id.btn_2: //开启后台线程,持有该Activity的弱引用 if (mBackgroundTask == null) { mBackgroundTask = new BackgroundTask(); mBackgroundTask.setWeakObj(new WeakReference<Object>(this)); mBackgroundThread = new Thread(mBackgroundTask); mBackgroundThread.start(); } break; case R.id.btn_3: //停止线程 mBackgroundTask.stop = true; mBackgroundThread.interrupt(); break; } } } public class BackgroundTask implements Runnable { private WeakReference<Object> weakObj; private Object object; private int count; public boolean stop; public void setLeakObject(Object leakObject) { this.object = leakObject; } public void setWeakObj(WeakReference<Object> weakObj) { this.weakObj = weakObj; } @Override public void run() { try { while (!stop) { Thread.sleep(1000); count += 1; L.d("background task count:" + count); } } catch (InterruptedException e) { e.printStackTrace(); } } }
- 更详尽的使用,可参考使用方法的译文http://www.liaohuqiu.net/cn/posts/leak-canary-read-me/