Android内存泄漏以及解决办法

Android程序开发中,若是一个对象已经不须要被使用了,本该被回收时,而这时另外一个对象还在持有对该对象的引用,这样就会致使没法被GC回收,就会出现内存泄漏的状况。java

内存泄漏时Android程序中出现OOM问题的主要缘由之一。设计模式

Android开发中最多见的5个内存泄漏问题:ide

一:单例设计模式形成的内存泄漏:函数

单例设计模式我就很少说了,这个是最基本的设计模式,相信你们都会使用,可是时候咱们在使用单例设计模式时没有注意到其中的细节,就会形成内存泄漏。oop

单例设计模式的静态特性会使他的生命周期和应用程序的生命周期同样长,这就说明了若是一个对象不在使用了,而这时单例对象还在持有该对象的引用,这时GC就会没法回收该对象,形成了内存泄露的状况。this

 下面是错误的单例设计模式的代码:线程

public class AppManager {
    private static AppManager instance;
    private Context context;
    private AppManager(Context context) {
        this.context = context;
    }
    public static AppManager getInstance(Context context) {
        if (instance != null) {
            instance = new AppManager(context);
        }
        return instance;
    }
}

上面的代码是一个最普通的单例模式,可是须要注意两个问题:
一、若是咱们传入的Context是Application的Context的话,就没有任何问题,由于Application的Context生命周期和应用程序生命周期同样长。设计

二、若是咱们传入的Context是Activity的Context的话,这时若是咱们由于需求销毁了该Activity的话,Context也会随着Activity被销毁,可是单例还在持有对该类对象的引用,这时就会形成内存泄漏。code

因此,正确的单例模式写法应该是这样的:
 server

public class AppManager {
    private static AppManager instance;
    private Context context;
    private AppManager(Context context) {
        this.context = context.getApplicationContext();
    }
    public static AppManager getInstance(Context context) {
        if (instance != null) {
            instance = new AppManager(context);
        }
        return instance;
    }
}

这样的话无论咱们传入什么样的Context,最终使用的都是Application的Context,单例的生命周期和应用同样长,这样就不会形成内存泄漏了。

2、非静态内部类建立的静态实例形成的内存泄漏

有时候由于需求咱们会去频繁的启动一个Activity,这时为了不频繁的建立相同的数据源,咱们一般会作以下处理:

public class MainActivity extends AppCompatActivity {
 
    private static TestResource mResource = null;
 
    @Override
 
    protected void onCreate(Bundle savedInstanceState) {
 
        super.onCreate(savedInstanceState);
 
        setContentView(R.layout.activity_main);
 
        if(mManager == null){
 
            mManager = new TestResource();
 
        }
 
        //...
 
    }
 
    class TestResource {
 
        //...
 
    }
 
}

这样就在Activity中建立了非静态内部类,非静态内部类默认持有Activity类的引用,可是他的生命周期仍是和应用程序同样长,因此当Activity销毁时,静态内部类的对象引用不会被GC回收,就会形成了内存溢出,解决办法:

一、将内部类改成静态内部类。

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

3、Handler形成的内存泄漏:

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

public class MainActivity extends AppCompatActivity {
    private Handler mHandler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            //...
        }
    };
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        loadData();
    }
    private void loadData(){
        //...request
        Message message = Message.obtain();
        mHandler.sendMessage(message);
    }
}

这里的handler也是一个非静态匿名内部类,他跟上面的同样,也会持有Activity的引用,咱们知道handler是运行在一个Looper线程中的,而Looper线程是轮询来处理消息队列中的消息的,假设咱们处理的消息有十条,而当他执行到第6条的时候,用户点击了back返回键,销毁了当前的Activity,这个时候消息尚未处理完,handler还在持有Activity的引用,这个时候就会致使没法被GC回收,形成了内存泄漏。正确的作法是:
 

public class MainActivity extends AppCompatActivity {
//new一个自定义的Handler
    private MyHandler mHandler = new MyHandler(this);
    private TextView mTextView ;
 
//自定义静态内部类继承自Handler
    private static class MyHandler extends Handler {
        private WeakReference<Context> reference;
//在构造函数中使用弱引用来引用context对象
        public MyHandler(Context context) {
            reference = new WeakReference<>(context);
        }
        @Override
        public void handleMessage(Message msg) {
            MainActivity activity = (MainActivity) reference.get();
            if(activity != null){
                activity.mTextView.setText("");
            }
        }
    }
  
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mTextView = (TextView)findViewById(R.id.textview);
        loadData();
    }
  
    private void loadData() {
        //...request
        Message message = Message.obtain();
        mHandler.sendMessage(message);
    }
 
@Override
  protected void onDestroy() {
      super.onDestroy();
//移除队列中全部的Runable和消息
//这里也可使用mHandler.removeMessage和mHandler.removeCallBacks来移除指定的Message和Runable
      mHandler.removeCallbacksAndMessages(null);
  }
}

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

4、线程形成的内存泄漏

线程使用不恰当形成的内存泄漏也是很常见的,下面举两个例子:

//——————test1
        new AsyncTask<Void, Void, Void>() {
            @Override
            protected Void doInBackground(Void... params) {
                SystemClock.sleep(10000);
                return null;
            }
        }.execute();
//——————test2
        new Thread(new Runnable() {
            @Override
            public void run() {
                SystemClock.sleep(10000);
            }
        }).start();

上面是两个内部类,当咱们的Activity销毁时,这两个任务没有执行完毕,就会使Activity的内存资源没法被回收,形成了内存泄漏。

正确的作法是使用静态内部类:以下

static class MyAsyncTask extends AsyncTask<Void, Void, Void> {
        private WeakReference<Context> weakReference;
  
        public MyAsyncTask(Context context) {
            weakReference = new WeakReference<>(context);
        }
  
        @Override
        protected Void doInBackground(Void... params) {
            SystemClock.sleep(10000);
            return null;
        }
  
        @Override
        protected void onPostExecute(Void aVoid) {
            super.onPostExecute(aVoid);
            MainActivity activity = (MainActivity) weakReference.get();
            if (activity != null) {
                //...
            }
        }
    }
    static class MyRunnable implements Runnable{
        @Override
        public void run() {
            SystemClock.sleep(10000);
        }
    }
//——————
    new Thread(new MyRunnable()).start();
    new MyAsyncTask(this).execute();

这样就避免了内存泄漏,固然在Activity销毁时也要记得在OnDestry中调用AsyncTask.cancal()方法来取消相应的任务。避免在后台运行浪费资源。

5、资源未关闭形成的内存泄漏 在使用完BraodcastReceiver,ContentObserver,File,Cursor,Stream,Bitmap等资源时,必定要在Activity中的OnDestry中及时的关闭、注销或者释放内存, 不然这些资源不会被GC回收,就会形成内存泄漏。