Android内存泄露

什么是内存泄露?

内存泄漏(Memory Leak)是指程序中己动态分配的堆内存由于某种原因程序未释放或无法释放,造成系统内存的浪费,导致程序运行速度减慢甚至系统崩溃等严重后果。

什么是内存溢出?

内存溢出(out of memory)通俗理解就是内存不够,通常在运行大型软件或游戏时,软件或游戏所需要的内存远远超出了你主机内安装的内存所承受大小,就叫内存溢出。

Android出现内存泄露的几种情况

1)单例设计模式导致内存泄露

单例设计模式的静态特性会使它的生命周期和应用的生命周期一样长,当单例对象不被使用时,还保持着单例对象对该对象的引用,导致GC无法回收,从而导致内存泄露。

  1. public class AppManager {  
  2.     private static AppManager instance;  
  3.     private Context context;  
  4.     private AppManager(Context context) {  
  5.         this.context = context;  
  6.     }  
  7.     public static AppManager getInstance(Context context) {  
  8.         if (instance != null) {  
  9.             instance = new AppManager(context);  
  10.         }  
  11.         return instance;  
  12.     }  
  13. }

以上例子如果传入的context是Application就没有问题,因为Application的生命周期和应用的生命周期一样长。如果传入的是Activity,当Activity对象被销毁时,context也会被销毁,单例还是保持对该对象的引用,从而导致内存泄漏。

所以正确的单例模式的写法如下:

  1. public class AppManager {  
  2.     private static AppManager instance;  
  3.     private Context context;  
  4.     private AppManager(Context context) {  
  5.         this.context = context.getApplicationContext();  
  6.     }  
  7.     public static AppManager getInstance(Context context) {  
  8.         if (instance != null) {  
  9.             instance = new AppManager(context);  
  10.         }  
  11.         return instance;  
  12.     }  
  13. }  

2)资源未关闭导致内存泄漏

在使用完Cursor、IO流、BroadcastRecevier、ContentObserver、Bitmap等资源时,没有关闭就会导致内存泄漏。

3)线程导致内存泄漏

线程也是造成内存泄露的重要源头,下面结合以下例子来进行分析:

  1. //——————test1  
  2.         new AsyncTask<Void, Void, Void>() {  
  3.             @Override  
  4.             protected Void doInBackground(Void... params) {  
  5.                 SystemClock.sleep(10000);  
  6.                 return null;  
  7.             }  
  8.         }.execute();  
  9. //——————test2  
  10.         new Thread(new Runnable() {  
  11.             @Override  
  12.             public void run() {  
  13.                 SystemClock.sleep(10000);  
  14.             }  
  15.         }).start();  

在子线程里执行耗时操作,当开启线程后进行横竖屏切换操作时,一般会把老的Activity销毁,然后新建一个Activity,但是这里不会,

因为子线程是Activity的内部类,子线程还继续保存了对Activity的一个引用,子线程的还没有结束,导致Activity不会被销毁,从而导致内存泄漏。

正确的做法就是在线程内部采用弱引用保存Context引用或者使用静态内部类:

  1. static class MyAsyncTask extends AsyncTask<Void, Void, Void> {  
  2.         private WeakReference<Context> weakReference;  
  3.     
  4.         public MyAsyncTask(Context context) {  
  5.             weakReference = new WeakReference<>(context);  
  6.         }  
  7.     
  8.         @Override  
  9.         protected Void doInBackground(Void... params) {  
  10.             SystemClock.sleep(10000);  
  11.             return null;  
  12.         }  
  13.     
  14.         @Override  
  15.         protected void onPostExecute(Void aVoid) {  
  16.             super.onPostExecute(aVoid);  
  17.             MainActivity activity = (MainActivity) weakReference.get();  
  18.             if (activity != null) {  
  19.                 //...  
  20.             }  
  21.         }  
  22.     }  
  23.     static class MyRunnable implements Runnable{  
  24.         @Override  
  25.         public void run() {  
  26.             SystemClock.sleep(10000);  
  27.         }  
  28.     }  
  29. //——————  
  30.     new Thread(new MyRunnable()).start();  
  31.     new MyAsyncTask(this).execute();  

当然在Activity销毁时也要记得在OnDestry中调用AsyncTask.cancal()方法来取消相应的任务。避免在后台运行浪费资源。

4)Handler 导致的内存泄漏

先看一下不规范的Handler写法:

[java] view plain copy
  1. public class MainActivity extends AppCompatActivity {  
  2.     private Handler mHandler = new Handler() {  
  3.         @Override  
  4.         public void handleMessage(Message msg) {  
  5.             //...  
  6.         }  
  7.     };  
  8.     @Override  
  9.     protected void onCreate(Bundle savedInstanceState) {  
  10.         super.onCreate(savedInstanceState);  
  11.         setContentView(R.layout.activity_main);  
  12.         loadData();  
  13.     }  
  14.     private void loadData(){  
  15.         //...request  
  16.         Message message = Message.obtain();  
  17.         mHandler.sendMessage(message);  
  18.     }  
  19. }  

这里的handler也是一个非静态匿名内部类,他跟上面的一样,也会持有Activity的引用,我们知道handler是运行在

一个Looper线程中的,而Looper线程是轮询来处理消息队列中的消息的,假设我们处理的消息有十条,而当他执行到

第6条的时候,用户点击了back返回键,销毁了当前的Activity,这个时候消息还没有处理完,handler还在持有

Activity的引用,这个时候就会导致无法被GC回收,造成了内存泄漏。正确的做法是:

[java] view plain copy
  1. public class MainActivity extends AppCompatActivity {  
  2. //new一个自定义的Handler  
  3.     private MyHandler mHandler = new MyHandler(this);  
  4.     private TextView mTextView ;  
  5.   
  6. //自定义静态内部类继承自Handler  
  7.     private static class MyHandler extends Handler {  
  8.         private WeakReference<Context> reference;  
  9. //在构造函数中使用弱引用来引用context对象  
  10.         public MyHandler(Context context) {  
  11.             reference = new WeakReference<>(context);  
  12.         }  
  13.         @Override  
  14.         public void handleMessage(Message msg) {  
  15.             MainActivity activity = (MainActivity) reference.get();  
  16.             if(activity != null){  
  17.                 activity.mTextView.setText("");  
  18.             }  
  19.         }  
  20.     }  
  21.     
  22.     @Override  
  23.     protected void onCreate(Bundle savedInstanceState) {  
  24.         super.onCreate(savedInstanceState);  
  25.         setContentView(R.layout.activity_main);  
  26.         mTextView = (TextView)findViewById(R.id.textview);  
  27.         loadData();  
  28.     }  
  29.     
  30.     private void loadData() {  
  31.         //...request  
  32.         Message message = Message.obtain();  
  33.         mHandler.sendMessage(message);  
  34.     }  
  35.   
  36. @Override  
  37.   protected void onDestroy() {  
  38.       super.onDestroy();  
  39. //移除队列中所有的Runable和消息  
  40. //这里也可以使用mHandler.removeMessage和mHandler.removeCallBacks来移除指定的Message和Runable  
  41.       mHandler.removeCallbacksAndMessages(null);  
  42.   }  
  43. }  

创建一个静态内部类继承自handler,然后再在构造参数中对handler持有的对象做弱引用,这样在回收时就会回收了handler持有的对象,
这里还做了一处修改,就是当我 们的回收了handler持有的对向,即销毁了该Activity时,这时如果handler中的还有未处理的消息,
我们就需要在OnDestry方法中移除消息队列中的消息。

5)非静态内部类创建的静态实例造成的内存泄漏

有时候因为需求我们会去频繁的启动一个Activity,这时为了避免频繁的创建相同的数据源,我们通常会做如下处理:

[java]  view plain  copy
  1. public class MainActivity extends AppCompatActivity {  
  2.   
  3.     private static TestResource mResource = null;  
  4.   
  5.     @Override  
  6.   
  7.     protected void onCreate(Bundle savedInstanceState) {  
  8.   
  9.         super.onCreate(savedInstanceState);  
  10.   
  11.         setContentView(R.layout.activity_main);  
  12.   
  13.         if(mManager == null){  
  14.   
  15.             mManager = new TestResource();  
  16.   
  17.         }  
  18.   
  19.         //...  
  20.   
  21.     }  
  22.   
  23.     class TestResource {  
  24.   
  25.         //...  
  26.   
  27.     }  
  28.   
  29. }  

这样就在Activity中创建了非静态内部类,非静态内部类默认持有Activity类的引用,但是他的生命周期还是和应用程序一样长,所以当Activity销毁时,静态内部类的对象引用不会被GC回收,就会造成了内存溢出,解决办法:

1、将内部类改为静态内部类。

2、将这个内部类封装成一个单例,Context使用Application的Context

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值