一、内存泄露简介
1.什么是内存泄露?
内存泄露对于我们来说是不可见的,它是出现在堆内存中,我们可以通过LeakCanary,MAT等工具来检测程序中是否存在内存泄露。
2.内存泄露什么时候发生?
当一个对象已经不需要在使用了,本该被回收的时候,而又有另外一个正在使用的对象持有它的引用从而导致它不被回收,这就产生了内存泄露。
3.内存泄露有什么坏处?
内存泄露是产生OOM的主要原因之一,Android系统为每个应用程序分配有限的内存,当内存泄露发生的较多的时候,就难免会造成内存溢出导致应用Crash。
二、常见的内存泄露和处理办法
为了让大家能直观的直观的感受到内存泄露它的存在,下面我们使用leakcanary工具,给大家展示几种常见的内存泄露类型(这里我们基于《内存泄露检测神器—LeakCanary》章节集成LeakCanary后的代码,继续演示。对LeakCanary工具和接入等不太了解的同学,可以到相关章节进行学习)。
1.单例造成的内存泄露
简要说明
单例的静态成员特性使得单例对象和应用的生命周期一样长,如果对象持有了一个已经不需要使用的对象,那么该对象就不能被正常回收而导致内存泄露。
代码实例
summary\src\main\java\com\qandroid\summary\memoryleak\SingletonManager.java
![](https://img-blog.csdn.net/20170222113454506?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvcDEwNjc4Njg2MA==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center)
处理建议
在涉及到Context时先考虑ApplicationContext,当然它并不是万能的,对于有些地方则必须使用Activity的Context。这样就没有问题,因为它的生命周期也是和应用一样长。
简要说明
非静态内部类里面可以直接访问外部类的private成员,这是因为在非静态内部类对象里,保存了一个它寄存在外部类对象的引用。而这个时候如果你有使用了该内部类创建了一个静态的实例,那么该实例的生命周期就和应用一样长,导致该静态实例会间接持有Activity的引用,导致Activity页面退出的时候,无法被回收从而造成内存泄露。
代码实例
summary\src\main\java\com\qandroid\summary\memoryleak\InnerClassActivity.java
![](https://img-blog.csdn.net/20170222113558647?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvcDEwNjc4Njg2MA==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center)
处理建议
将该内部类设置为静态内部类,或者抽出来封装成一个单例
简要说明
在日常开发过程中,我们经常使用如下匿名内部类的形式实现简单的线程或者异步任务。由于匿名内部类内可以访问外部类的成员,同样包含外部类的殷实引用。当Activity页面退出后,线程和异步任务还未完成时,Activity无法被回收而导致内存泄漏。
代码实例
summary\src\main\java\com\qandroid\summary\memoryleak\ThreadAsyncActivity.java
处理建议
将线程类的实现,使用静态的内部类,这样就不会导致Activity内存无法被回收。在Activity销毁的时候取消未完成的任务执行,避免任务在后台执行浪费资源。
简要说明
匿名内部类的方式创建Handler的实例可能会造成内存的泄露。因为它持有外部类HandlerActivity的引用,当在Activity页面退出的时候,消息队列中还有未处理的消息或者正在处理的消息,而消息队列中的Messager持有Handler实例的引用,间接持有Activity页面的引用,无法被回收造成内存泄露。
代码实例
QAndroid\summary\src\main\java\com\qandroid\summary\memoryleak\HandlerActivity.java
![](https://img-blog.csdn.net/20170222113835663?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvcDEwNjc4Njg2MA==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center)
处理建议
将Handler修改为静态的内部类,并且使用弱引用持有当前Activity的上下文,这样就可以避免Activity不能被回收造成的内存泄露。但是当Looper线程的消息队列中还是可能又未处理的消息,故在Activity的onStop或者onDestroy中移除未处理的消息。
对于使用了BraodcastReceiver,ContentObserver,File,Cursor,Stream,Bitmap等资源的使用,应该在Activity销毁时及时关闭或者注销,否则这些资源将不会被回收,造成内存泄漏。
三、总结建议
1.编码总结
日常编写代码的时候,要保持对静态成员,非静态内部类实例,匿名内部和单例、上下文对象等的敏感性,要特别留意是否可能存在内存泄露情况。
2.编码建议
涉及到Context的时候,要优先考虑ApplicationContext,当然某些情况也是不行的要视具体情况而论(如启动一个Activity或Service,显示一个Dialog)。
对于需要在静态内部类中使用外部类成员变量(如:Context,View),可以在静态内部类中使用弱引用来引用外部类变量来避免内存泄露。
四、代码库
1.什么是内存泄露?
内存泄露对于我们来说是不可见的,它是出现在堆内存中,我们可以通过LeakCanary,MAT等工具来检测程序中是否存在内存泄露。
2.内存泄露什么时候发生?
当一个对象已经不需要在使用了,本该被回收的时候,而又有另外一个正在使用的对象持有它的引用从而导致它不被回收,这就产生了内存泄露。
3.内存泄露有什么坏处?
内存泄露是产生OOM的主要原因之一,Android系统为每个应用程序分配有限的内存,当内存泄露发生的较多的时候,就难免会造成内存溢出导致应用Crash。
二、常见的内存泄露和处理办法
为了让大家能直观的直观的感受到内存泄露它的存在,下面我们使用leakcanary工具,给大家展示几种常见的内存泄露类型(这里我们基于《内存泄露检测神器—LeakCanary》章节集成LeakCanary后的代码,继续演示。对LeakCanary工具和接入等不太了解的同学,可以到相关章节进行学习)。
1.单例造成的内存泄露
简要说明
单例的静态成员特性使得单例对象和应用的生命周期一样长,如果对象持有了一个已经不需要使用的对象,那么该对象就不能被正常回收而导致内存泄露。
代码实例
summary\src\main\java\com\qandroid\summary\memoryleak\SingletonManager.java
public class SingletonManager {
//单例的静态成员生命周期和应用一样长
private static SingletonManager instance;
//它持有的成员,Activity的上下文在Activity关闭的时候无法回收,故造成了内存泄露
private Context context;
private SingletonManager(Context context) {
this.context = context;
}
public static SingletonManager getInstance(Context context) {
if (instance == null) {
instance = new SingletonManager(context);
}
return instance;
}
}
summary\src\main\java\com\qandroid\summary\memoryleak\SingletonActivity.java
public class SingletonActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_singleton);
//将SingletonActivity上下文,传递给单例成员持有,在Activity退出时无法被回收
SingletonManager singletonManager = SingletonManager.getInstance(this);
}
}
运行日志处理建议
在涉及到Context时先考虑ApplicationContext,当然它并不是万能的,对于有些地方则必须使用Activity的Context。这样就没有问题,因为它的生命周期也是和应用一样长。
public class SingletonManager {
... ...
public static SingletonManager getInstance(Context context) {
if (instance == null) {
//在涉及到使用Context的地方,优先使用ApplicationContext。但是要视具体情况,有些地方则必须使用Activity的上下文
instance = new SingletonManager(context.getApplicationContext());
}
return instance;
}
}
2.非静态内部类创建静态实例造成的内存泄露简要说明
非静态内部类里面可以直接访问外部类的private成员,这是因为在非静态内部类对象里,保存了一个它寄存在外部类对象的引用。而这个时候如果你有使用了该内部类创建了一个静态的实例,那么该实例的生命周期就和应用一样长,导致该静态实例会间接持有Activity的引用,导致Activity页面退出的时候,无法被回收从而造成内存泄露。
代码实例
summary\src\main\java\com\qandroid\summary\memoryleak\InnerClassActivity.java
public class InnerClassActivity extends AppCompatActivity {
//使用静态成员,使得资源在此Activity中仅有一份
private static ResourceClass resourceClass;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_innerclass);
//在该资源在内存中不存在的情况下,在构造获取资源,避免资源内存的重复分配
if (resourceClass == null) {
//但是该资源未非静态内部类,它的实例隐式的含有外部类InnerClassActivity的引用,当Activity退出的时候,该Activity无法被回收导致内存泄露
resourceClass = new ResourceClass();
}
}
//保存资源的内部类
class ResourceClass {
... ...
}
}
运行日志处理建议
将该内部类设置为静态内部类,或者抽出来封装成一个单例
public class InnerClassActivity extends AppCompatActivity {
... ...
//修改为静态的内部类
static class ResourceClass {
... ...
}
}
3.Thread或者AsyncTask匿名内部或非静态实例对象实现造成的内存泄露简要说明
在日常开发过程中,我们经常使用如下匿名内部类的形式实现简单的线程或者异步任务。由于匿名内部类内可以访问外部类的成员,同样包含外部类的殷实引用。当Activity页面退出后,线程和异步任务还未完成时,Activity无法被回收而导致内存泄漏。
代码实例
summary\src\main\java\com\qandroid\summary\memoryleak\ThreadAsyncActivity.java
public class ThreadAsyncActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_threadasync);
//匿名内部类实例,包含ThreadAsyncActivity隐式的引用
new Thread(new Runnable() {
@Override
public void run() {
try {
//模拟异步任务执行,在ThreadAsyncActivity退出后还会继续执行,导致Activity无法被回收
Thread.sleep(100000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
//非静态内部类实例对象,包含ThreadAsyncActivity隐式的引用
new InnerAsyncTask().execute();
}
//非静态内部类实现异步任务
class InnerAsyncTask extends AsyncTask<Void, Void, Void> {
@Override
protected Void doInBackground(Void... voids) {
try {
//模拟异步任务执行,在ThreadAsyncActivity退出后还会继续执行,导致Activity无法被回收
Thread.sleep(100000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return null;
}
}
}
运行日志
处理建议
将线程类的实现,使用静态的内部类,这样就不会导致Activity内存无法被回收。在Activity销毁的时候取消未完成的任务执行,避免任务在后台执行浪费资源。
public class ThreadAsyncActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_threadasync);
new Thread(new MyRunnable()).start();
new InnerAsyncTask().execute();
}
//使用静态内部类,避免对外部类ThreadAsyncActivity实例的引用
static class InnerAsyncTask extends AsyncTask<Void, Void, Void> {
@Override
protected Void doInBackground(Void... voids) {
try {
Thread.sleep(100000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return null;
}
}
//使用静态内部类,避免对外部类ThreadAsyncActivity实例的引用
static class MyRunnable implements Runnable {
@Override
public void run() {
try {
Thread.sleep(100000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
4.Handler匿名内部类实现造成的内存泄露简要说明
匿名内部类的方式创建Handler的实例可能会造成内存的泄露。因为它持有外部类HandlerActivity的引用,当在Activity页面退出的时候,消息队列中还有未处理的消息或者正在处理的消息,而消息队列中的Messager持有Handler实例的引用,间接持有Activity页面的引用,无法被回收造成内存泄露。
代码实例
QAndroid\summary\src\main\java\com\qandroid\summary\memoryleak\HandlerActivity.java
public class HandlerActivity extends AppCompatActivity {
private TextView textView;
//使用非静态匿名内部类实例创建Handler对象,持有HandlerActivity的隐式引用
private Handler handler = new Handler(){
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
if(msg.what == 1){
Log.i("HandlerActivity","receive sendMessage");
textView.setText((CharSequence) msg.obj);
}
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_handler);
textView = (TextView) findViewById(R.id.textView1);
Log.i("HandlerActivity","start loaddata");
new LoadDataTread().start();
}
class LoadDataTread extends Thread{
@Override
public void run() {
try {
//延迟1秒钟,模拟异步加载数据
Thread.sleep(1000);
Log.i("HandlerActivity","done loaddata");
} catch (InterruptedException e) {
e.printStackTrace();
}
//处理完网络请求,延迟10秒发送消息,并退出HandlerActivity页面,此时消息队列还有消息没有处
//理或正在处理,而消息队列的message持有handler对象的引用,hander又持有Activity的引用,故造成了Activity无法被回收的内存泄露
Log.i("HandlerActivity","start sendMessage");
Message message = new Message();
message.what = 1;
message.obj = "receive data";
handler.sendMessageDelayed(message,10000);
}
}
}
运行日志处理建议
将Handler修改为静态的内部类,并且使用弱引用持有当前Activity的上下文,这样就可以避免Activity不能被回收造成的内存泄露。但是当Looper线程的消息队列中还是可能又未处理的消息,故在Activity的onStop或者onDestroy中移除未处理的消息。
public class HandlerActivity extends AppCompatActivity {
private TextView textView;
private MyHander myHander = new MyHander(this);
//创建静态的内部类,不会有对HandlerActivity实例的隐式引用,防止Activity退出后,消息为处理完Activity无法回收
private static class MyHander extends Handler{
//使用弱引用持有上下文,这样即使消息未处理完也可以回收Activity,避免内存泄露
private WeakReference<Context> contextWeakReference;
public MyHander(Context context){
contextWeakReference = new WeakReference<Context>(context);
}
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
if(msg.what == 1){
Log.i("HandlerActivity","receive sendMessage");
//从弱引用中获取上下文,更新视图对象
HandlerActivity activity = (HandlerActivity) contextWeakReference.get();
if(activity != null){
activity.textView.setText((CharSequence) msg.obj);
}
}
}
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_handler);
textView = (TextView) findViewById(R.id.textView1);
Log.i("HandlerActivity","start loaddata");
new LoadDataTread().start();
}
@Override
protected void onStop() {
super.onStop();
//在Activity退出页面将要被销毁的时候哦,移除消息队列的所有消息和Runnable,防止内存泄露
myHander.removeCallbacksAndMessages(null);
}
class LoadDataTread extends Thread{
@Override
public void run() {
try {
//延迟1秒钟,模拟异步加载数据
Thread.sleep(1000);
Log.i("HandlerActivity","done loaddata");
} catch (InterruptedException e) {
e.printStackTrace();
}
//处理完网络请求,延迟10秒发送消息,并退出HandlerActivity页面,此时消息队列还有消息没有处
//理或正在处理,而消息队列的message持有handler对象的引用,hander又持有Activity的引用,故造成了Activity无法被回收的内存泄露
Log.i("HandlerActivity","start sendMessage");
Message message = new Message();
message.what = 1;
message.obj = "receive data";
myHander.sendMessageDelayed(message,10000);
}
}
}
5.资源未关闭造成的内存泄露
对于使用了BraodcastReceiver,ContentObserver,File,Cursor,Stream,Bitmap等资源的使用,应该在Activity销毁时及时关闭或者注销,否则这些资源将不会被回收,造成内存泄漏。
三、总结建议
1.编码总结
日常编写代码的时候,要保持对静态成员,非静态内部类实例,匿名内部和单例、上下文对象等的敏感性,要特别留意是否可能存在内存泄露情况。
2.编码建议
涉及到Context的时候,要优先考虑ApplicationContext,当然某些情况也是不行的要视具体情况而论(如启动一个Activity或Service,显示一个Dialog)。
对于需要在静态内部类中使用外部类成员变量(如:Context,View),可以在静态内部类中使用弱引用来引用外部类变量来避免内存泄露。
四、代码库
QAndroid:https://github.com/QAndroid/QAndroid 分支:summary/memoryleak
新技术,新未来!欢迎大家关注
“1024工场”微信服务号
,时刻关注我们的最新的技术讯息!
(甭客气!尽情的扫描或者长按!)