安卓内存泄露几种常见形式及解决方案

来源:互联网 发布:创新创业网络课答案 编辑:程序博客网 时间:2024/05/20 14:25

一.前言

1.内存溢出与内存泄露
内存溢出(oom),是指程序在申请内存时,没有足够的内存空间供其使用,出现oom;比如申请了一个integer,但给它存了long才能存下的数,那就是内存溢出。

内存泄露 (memory leak),是指程序在申请内存后,无法释放已申请的内存空间,一次内存泄露危害可以忽略,但内存泄露堆积后果很严重,无论多少内存,迟早会被占光。

memory leak会最终会导致oom!

二.内存泄露的几种形式

1.匿名内部类的使用

a.Thread内存泄漏

这里最常见的形式就是使用new thread开启一个子线程.
子线程会对当前activity有一个隐式的强引用
当activity退出时候,如果子线程还在运行activity就不会释放.

          running = true;        new Thread(new Runnable() {            @Override            public void run() {                while (running) {                    SystemClock.sleep(1000);                    runOnUiThread(new Runnable() {                        @Override                        public void run() {                            SimpleDateFormat sDateFormat = new SimpleDateFormat("yyyy-MM-dd    hh:mm:ss");                            String date = sDateFormat.format(new Date());                            tv.setText(date);                        }                    });                }            }        }).start();

LeakCanary检测结果

这里写图片描述

解决办法:
调用onDestroy后结束子线程

  @Override    protected void onDestroy() {        super.onDestroy();        running = false;    }

b.Timer内存泄露

这里既然thread使用有问题,那么我们用hander+Timer的形式可以吗,我们来看看.
结果抱歉,使用Timer和Thread无论从原理还是结果上都与handler一样.

public class HandlerAndTimerErr extends AppCompatActivity {    Handler mhandler = new Handler() {        @Override        public void handleMessage(Message msg) {            super.handleMessage(msg);                tv.setText(msg.obj.toString());        }    };    private TextView tv;    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_thread_err);        tv = (TextView) findViewById(R.id.tv);        Timer timer = new Timer();        timer.scheduleAtFixedRate(new TimerTask() {            @Override            public void run() {                SimpleDateFormat sDateFormat = new SimpleDateFormat("yyyy-MM-dd    hh:mm:ss");                String date = sDateFormat.format(new Date());                Message message = new Message();                message.obj=date;                mhandler.sendMessage(message);            }        }, 1, 1000);    }}

LeakCanary检测结果
这里写图片描述

解决办法

    @Override    protected void onDestroy() {        super.onDestroy();        timer.cancel();    }

2.纯handler的错误使用

如果仅使用handler还可以这样写

public class HandlerErr extends AppCompatActivity {    Handler mhandler = new Handler();    private TextView tv;    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_thread_err);        tv = (TextView) findViewById(R.id.tv);       mhandler.postDelayed(new Runnable() {           @Override           public void run() {               SimpleDateFormat sDateFormat = new SimpleDateFormat("yyyy-MM-dd    hh:mm:ss");               String date = sDateFormat.format(new Date());               tv.setText(date);               mhandler.postDelayed(this,1000);           }       },1000);    }}

LeakCanary检测结果
这里写图片描述

解决办法

    @Override    protected void onDestroy() {        super.onDestroy();        //移除当前handler发送的请求        mhandler.removeCallbacksAndMessages(null);    }

3.context导致内存泄露

做一个单例的ToastUtils来显示toast是很多人会做的.你的ToastUtils是否这么写的?

public class ToastUtils {    private static Toast toast;    public static void ShowToast(Context context, String text){        if (toast==null) {            toast = Toast.makeText(context, text, Toast.LENGTH_LONG);        }else{            toast.setText(text);        }        toast.show();    }}

这样写其实有很大问题:
如果此时传入的是 Activity 的 Context,当这个 Context 所对应的 Activity 退出时,由于该 Context 的引用被单例对象所持有,其生命周期等于整个应用程序的生命周期,所以当前 Activity退出时它的内存并不会被回收,这就造成泄漏了。

LeakCanary检测结果
这里写图片描述

解决办法:
1.使用ApplicationContext代替Activity的Context
2.如果必须要Activity的Context请换种写法吧,哈哈.

4.fragment中使用getActivity().findViewById()导致内存泄露

使用getActivity().findViewById()会导致fragment内存中不释放,虽然退出Activity可以让其释放,但是比如使用viewpager+fragment无限滑动就会导致内存过大

解决办法:
使用fragment的findviewbyId

5.leackCannary使用

leackCannary使用其实非常简单
githup链接: https://github.com/square/leakcanary
1.首先在你的build.gradle添加引用:

 dependencies {   debugCompile 'com.squareup.leakcanary:leakcanary-android:1.5.1'   releaseCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.5.1'   testCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.5.1' }

2.在你的application中初始化
refWatcher = LeakCanary.install(this);

2.1监测fragment中的内存泄露
application中添加方法

public static RefWatcher getRefWatcher() {        return refWatcher;    }

baseFragment中调用

 @Override    public void onDestroy() {        super.onDestroy();        //检测fragment中的内存泄露        RefWatcher refWatcher = SmartInfoApplication.getRefWatcher();        refWatcher.watch(this);    }

3.提醒
这里其实就已经配置完成可以运行使用了.
注意,当你进入一个activity再退出之后等个三五秒如果有溢出就会提示的.

4.讲解
这里写图片描述
我们就拿这个例子来说
这个是告诉我们 ToastUtils下面有一个静态成员变量toast
它引用了一个context
这个context是ContextErr这个activity的一个实例(instance)
它导致了内存泄露.

5.源码地址

https://github.com/jin870132/memoryleakdemo

1 0
原创粉丝点击