前言:学习笔记,如有不对,多指教
参考资料:
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的应用
在你的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();
}