内存泄漏:Android程序开发中,如果一个对象已经不需要被使用了,本该被回收时,而这时另一个对象还在持有对该对象的引用,这样就会导致无法被GC回收,就会出现内存泄漏的情况。内存泄漏时Android程序中出现OOM问题的主要原因之一。所以我们在编写代码时,一定要细心处理好这一类的问题

单例设计模式造成的内存泄漏

  • 单例设计模式的静态特性会使他的生命周期和应用程序的生命周期一样长,这就说明了如果一个对象不在使用了,而这时单例对象还在持有该对象的引用,这时GC就会无法回收该对象,造成了内存泄露的情况

    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被销毁,但是单例还在持有对该类对象的引用,这时就会造成内存泄漏。正确的单例模式写法应该是这样的:
    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,单例的生命周期和应用一样长,这样就不会造成内存泄漏了

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

  • 有时候因为需求我们会去频繁的启动一个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

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方法中移除消息队列中的消息

线程造成的内存泄漏

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

    //——————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()方法来取消相应的任务。避免在后台运行浪费资源。

资源未关闭造成的内存泄漏

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

posted on 2022-09-07 16:02  幺幺零零  阅读(154)  评论(0编辑  收藏  举报