简述内存泄漏及解决方式

来源:互联网 发布:matlab 最优化工具箱 编辑:程序博客网 时间:2024/06/16 17:11

本人还在培训期,对网络上的知识点进行归纳、总结,然后再分享给大家,有时候也会加入了一些个人的看法和见解,如果有一些谬误欢迎留言。

从我目前学习的情况看来,内存泄漏的意思大概就是:
一个代码不规范导致的,内存空间使用之后无法及时回收的问题。

案例以及分析

(案例选自百度文库)

一、非静态内部类创建静态实例

错误案例:

public class MainActivity extends Activity {    static Demo sInstance = null;    @Override    public void onCreate(BundlesavedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_main);        if (sInstance == null) {            sInstance = new Demo();        }    }    class Demo {        void doSomething() {            //...        }    }}

分析:

1、非静态内部类默认会持有外部类的引用。
2、使用非静态内部类创建了一个静态的实例,该实例的生命周期和应用的一样长。
结果:内部类始终持有该Activity的引用,内部类不被释放,Activity的内存资源也不能正常回收。

二、单例模式持有外部引用

错误案例:

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;    }}

分析:
1、单例是静态的,生命周期和应用一样长。
2、在单例类中传入一个Context、或者Activity、或者Fragment。
结果:单例类持有了他们的引用,单例类不能被释放,那些传入的对象无法被释放

正确的单例应该修改为下面这种方式:

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.getApplicationContext(),传入一个应用的上下文,Application生命周期跟应用相同,因此不能释放也无所谓。
注:
在单例类中使用ApplicationContext弹出Dialog似乎会报错,那时候试试回调,或者弱引用。

三、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的内存泄漏,细心一点的应该早就知道了,因为编译的时候,Android Studio直接就黄色警告了。)
1、首先,主线程中Looper负责消息轮询,它是一个单例类(单例的方式与众不同,这里就不增加理解难度了)。
2、Looper拥有MessageQueue(消息队列)的引用。
3、MessageQueue又拥有Handler的引用。
4、只要消息队列中的消息还没被处理,Handler就无法被释放。

解决方式(使用弱引用):

public class MainActivity extends AppCompatActivity {    private MyHandler mHandler = new MyHandler(this);    private TextView mTextView    private static class MyHandler extends Handler {        private WeakReference<Context> reference;        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);    }}

分析:
创建一个静态Handler内部类,然后对Handler持有的对象使用弱引用,这样Handler持有的对象就能被回收了。
缺陷:
这样虽然避免了Activity泄漏,不过Looper线程的消息队列中还是可能会有待处理的消息,所以我们在Activity的Destroy时或者Stop时应该移除消息队列中的消息,

解决方式Plus(弱引用+消息队列清除):

public class MainActivity extends AppCompatActivity {    private MyHandler mHandler = new MyHandler(this);    private TextView mTextView    private static class MyHandler extends Handler {        private WeakReference<Context> reference;        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();        mHandler.removeCallbacksAndMessages(null);    }}

有以下方式可供选择:

一、mHandler.removeCallbacksAndMessages(null)移除消息队列中所有消息和所有的Runnable。
二、mHandler.removeCallbacks()移除回调。
三、mHandler.removeMessages()移除消息。

HandlerThread可供选择的方法:

mThread.getLooper().quit();**

四、线程造成的内存泄漏

错误案例(2个):

    public void test() {        //AsyncTask错误案例        new AsyncTask<Void, Void, Void>() {            @Override            protected Void doInBackground(Void... params)            {                SystemClock.sleep(10000);                return null;            }        }.execute();        //Thread错误案例        new Thread(new Runnable() {            @Override            public void run() {                SystemClock.sleep(10000);            }        }).start();    }

分析:
原因跟Handler类似,我们要知道Thread是可以长时间执行的,一旦持有外部引用,那么那个对象就无法被回收。

而上面的异步任务和Runnable都是一个匿名内部类,因此它们对当前Activity都有一个隐式引用。

正确的做法还是使用静态内部类的方式:

//AsyncTask正确用法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) {            //...                    }    }}new MyAsyncTask(this).execute();//Thread正确用法static class MyRunnable implements Runnable {    @Override    public void run() {        SystemClock.sleep(10000);    }}new Thread(new MyRunnable()).start();

这样就避免了Activity的内存资源泄漏,当然在Activity销毁时候也应该取消相应的任务,避免任务在后台执行浪费资源。

五、注册后未反注册(观察者模式)

观察者模式:

这里强调一下观察者模式,我们要知道:它的作用的松耦合,但是关系是强引用
在观察者模式中,所谓的注册,就是将观察者(Observe)放到被观察者(Subject)的集合当中,这就形成了观察者和被观察者之间的强引用关系。
安卓的广播是观察者模式的产品之一,所以注册广播接收器,必须解注册;在无法把握的时候,严禁将Activity、Context作为观察者。

六、Android特殊组件或者类忘记释放

比如:
1、数据库处理,Cursor忘记销毁。
2、网络编程,Socket忘记close。
3、自定义控件,TypedArray忘记recycle。
4、callback忘记remove,具体例子不好找,我记得有个网络框架有这个缺陷,但是人家改好了。
5、图片处理Bitmap忘记recycle,还有用的Bitmap就别回收了,不然程序会崩溃。
等等……

七、集合中对象没清理造成的内存泄露

  某个集合类(List)被一个static变量引用,同时这个集合类没有删除自己内部的元素,这一点比较容易发现,注意不用的时候clear一下就好了。

如何阻止内存泄漏?

一、注意集合类

例如HashMap,ArrayList,等等。因为它们是内存泄漏经常发生的地方,但是发现起来也比较容易,注意clear就好了

二、弱引用、软引用的使用

就比如说,你需要APP好几处需要弹窗,而且弹窗的内容完全相同,你会希望将它封装成一个方法。
虽然我们很想避免使用Context,但是一旦封装成方法,又得考虑上Context值等问题,这个时候就可以考虑使用弱引用、或者软引用。

这里我就不展开介绍了,给出一个其他人博客的链接:Java:对象的强、软、弱和虚引用

三、观察者模式下记得解注册

解决了类间的耦合问题,但是也要记住引用问题,及时解注册,或者清空观察者集合。

三、管理自身内存的类

如果一个类管理它自己的内存,程序员应该对内存泄漏保持警惕。很多时候当一个对象的成员变量指向其他对象时,不再使用时需要被置为null。
(“置为null”这一点待考证,以前这么写过,然后被嘲笑了,但是想想也对,置为null,就不再拥有那个引用了)。

四、注意非静态内部类会持有外部类的引用

首先我们要明确:非静态内部类默认会持有外部类的引用,一旦内部类不能被释放,外部类也无法被释放,避免非静态内部类的静态实例。

五、及时停止耗时操作

这一点主要体现在Handler和Thread以及一些耗时操作上上,由于事务不能及时处理完毕,当它拥有外部引用的时候,将导致资源无法回收。

六、及时释放安卓的特殊组件

这一点体现在位图、回调对象、连接对象、游标等组件的释放上,学习过程一定反复接触过这些内容,养成良好的变成习惯,完全可以避免内存泄漏问题。

0 0
原创粉丝点击