08. Android内存泄露检测工具Leakcanary

前言:学习笔记,如有不对,多指教

参考资料:
https://www.liaohuqiu.net/cn/posts/leak-canary-read-me/
https://www.liaohuqiu.net/cn/posts/leak-canary/
http://www.cnblogs.com/whoislcj/p/6001422.html

内存泄露和内存溢出的区别:
  • 内存泄露:
    对象拥有有限的生命周期,当这个对象做完了他们应该做的事,我们希望它被回收掉,从而释放它占用的内存。但是,如果有一系列对这个对象的引用,这个对象就不会被回收,它所占用的内存就不可用。这就是内存泄露,内存泄露和硬件没有关系,它是软件的设计缺陷。内存泄露如果不及时解决最终会导致内存溢出。
  • 内存溢出:
    每一个应用程序在启动时都需要申请一块内存,比如系统分配给你一块2G的内存,但是你非要用这2G内存装3G的数据,这时就会导致内存溢出。
LeakCanary的应用

github:https://github.com/square/leakcanary

在你的build.gradle:

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

在你的Application类中:

public class ExampleApplication extends Application {

  @Override public void onCreate() {
    super.onCreate();
    if (LeakCanary.isInAnalyzerProcess(this)) {
      // This process is dedicated to LeakCanary for heap analysis.
      // You should not init your app in this process.
      return;
    }
    LeakCanary.install(this);
    // Normal app init code...
  }
}

这样就ok了,在我们的debug包中,如果有内存泄露,leakcanary就会自动发送通知。

出现内存泄露的场景

场景1:静态变量导致的内存泄露

public class MainActivity extends AppCompatActivity {

    private static Context sContext;
    
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        sContext = this;
    }
}

当我们destroy ManiActivity时候,这时候mainactivity本应该被gc回收释放内存,但是由于静态变量仍然持有对它的引用,所以它不能被回收,导致内存泄露。

这里要说下静态变量的生命周期问题,静态变量什么时候销毁呢?
参考文档:http://blog.csdn.net/ctcwri/article/details/8858414
静态变量的生命周期:类的加载——类的卸载

类在什么时候被加载?

当我们启动一个app的时候,系统会创建一个进程,此进程会加载一个Dalvik VM的实例,然后代码就运行在DVM之上,类的加载和卸载,垃圾回收等事情都由DVM负责。也就是说在进程启动的时候,类被加载,静态变量被分配内存。静态变量在类被卸载的时候销毁。

类在什么时候被卸载? 在进程结束的时候。

说明:一般情况下,所有的类都是默认的ClassLoader加载的,只要ClassLoader存在,类就不会被卸载,而默认的ClassLoader生命周期是与进程一致的,本文讨论一般情况。

场景2:错误使用单例造成内存泄露
由于单例的静态特性使得其生命周期跟应用的生命周期一样长,所以如果使用不恰当的话,很容易造成内存泄漏。比如下面一个典型的例子:(由于单例中getInstance的方法是静态的,所以成员变量mInstance必须是静态的,这样这个变量的生命周期就和整个应用的生命周期相同)

public class LoginManager {
    private static LoginManager mInstance;
    private Context mContext;

    private LoginManager(Context context) {
        this.mContext = context;
    }

    public static LoginManager getInstance() {
        if (mInstance == null) {
            synchronized (LoginManager.class) {
                if (mInstance == null) {
                    mInstance = new LoginManager(mContext);
                }
            }
        }
        return mInstance;
    }

    public void dealData() {
    }
 
}

当我们在在一个Activity中调用,再关闭这个Activity时就会出现内存泄露。

LoginManager.getInstance(this).dealData();

这是一个普通的单例模式,当创建这个单例的时候,由于需要传入一个Context,所以这个Context的生命周期的长短至关重要:

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

正确的写法有两种:

public class LoginManager {
    private static LoginManager mInstance;
    private Context mContext;

    private LoginManager(Context context) {
        this.mContext = context.getApplicationContext();   // 使用Application 的context
    }

    public static LoginManager getInstance() {
        if (mInstance == null) {
            synchronized (LoginManager.class) {
                if (mInstance == null) {
                    mInstance = new LoginManager(mContext);
                }
            }
        }
        return mInstance;
    }

    public void dealData() {
    }
 
}

或者,在你的 Application 中添加一个静态方法,getContext() 返回 Application 的 context

//在application的oncreate方法中
context = getApplicationContext();
//提供公共的方法
public static Context getContext(){
     return context;
}


public class LoginManager {
    private static LoginManager mInstance;
    private Context mContext;

    private LoginManager() {
         this.mContext = MyApplication.getContext();
    }

    public static LoginManager getInstance() { 
        if (mInstance == null) {
            synchronized (LoginManager.class) {
                if (mInstance == null) {
                    mInstance = new LoginManager();
                }
            }
        }
        return mInstance;
    }

    public void dealData() {
    }
 
}

场景3:属性动画导致的内存泄露
从Android3.0开始,google提供了属性动画,属性动画中有一类无限循环的动画,如果在Activity中播放此类动画,且没有在onDestroy中取停止动画,那么动画会一直播放下去。尽管已经无法在界面上看到动画效果了,但此时Activity的view被动画持有,而View又持有Acitity,最终Activity无法释放。导致内存泄露,解决的方法是在Activity的onDestroy中调用animator.cancle()来停止动画。

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        mButton = (Button)findViewById(R.id.button);
        ObjectAnimator animator = ObjectAnimator.ofFloat(mButton,"rotation",0,360).setDuration(2000);
        animator.setRepeatCount(ValueAnimator.INFINITE);
        animator.start();
    }
    @Override
    protected void onDestroy() {
        super.onDestroy();
        animator.cancle();
    }

结束……

你可能感兴趣的:(08. Android内存泄露检测工具Leakcanary)