前言:學(xué)習(xí)筆記,如有不對(duì),多指教
參考資料:
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
內(nèi)存泄露和內(nèi)存溢出的區(qū)別:
- 內(nèi)存泄露:
對(duì)象擁有有限的生命周期,當(dāng)這個(gè)對(duì)象做完了他們應(yīng)該做的事,我們希望它被回收掉,從而釋放它占用的內(nèi)存。但是,如果有一系列對(duì)這個(gè)對(duì)象的引用,這個(gè)對(duì)象就不會(huì)被回收,它所占用的內(nèi)存就不可用。這就是內(nèi)存泄露,內(nèi)存泄露和硬件沒有關(guān)系,它是軟件的設(shè)計(jì)缺陷。內(nèi)存泄露如果不及時(shí)解決最終會(huì)導(dǎo)致內(nèi)存溢出。 - 內(nèi)存溢出:
每一個(gè)應(yīng)用程序在啟動(dòng)時(shí)都需要申請(qǐng)一塊內(nèi)存,比如系統(tǒng)分配給你一塊2G的內(nèi)存,但是你非要用這2G內(nèi)存裝3G的數(shù)據(jù),這時(shí)就會(huì)導(dǎo)致內(nèi)存溢出。
LeakCanary的應(yīng)用
在你的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包中,如果有內(nèi)存泄露,leakcanary就會(huì)自動(dòng)發(fā)送通知。
出現(xiàn)內(nèi)存泄露的場(chǎng)景
場(chǎng)景1:靜態(tài)變量導(dǎo)致的內(nèi)存泄露
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;
}
}
當(dāng)我們destroy ManiActivity時(shí)候,這時(shí)候mainactivity本應(yīng)該被gc回收釋放內(nèi)存,但是由于靜態(tài)變量仍然持有對(duì)它的引用,所以它不能被回收,導(dǎo)致內(nèi)存泄露。
這里要說下靜態(tài)變量的生命周期問題,靜態(tài)變量什么時(shí)候銷毀呢?
參考文檔:http://blog.csdn.net/ctcwri/article/details/8858414
靜態(tài)變量的生命周期:類的加載——類的卸載
類在什么時(shí)候被加載?
當(dāng)我們啟動(dòng)一個(gè)app的時(shí)候,系統(tǒng)會(huì)創(chuàng)建一個(gè)進(jìn)程,此進(jìn)程會(huì)加載一個(gè)Dalvik VM的實(shí)例,然后代碼就運(yùn)行在DVM之上,類的加載和卸載,垃圾回收等事情都由DVM負(fù)責(zé)。也就是說在進(jìn)程啟動(dòng)的時(shí)候,類被加載,靜態(tài)變量被分配內(nèi)存。靜態(tài)變量在類被卸載的時(shí)候銷毀。
類在什么時(shí)候被卸載? 在進(jìn)程結(jié)束的時(shí)候。
說明:一般情況下,所有的類都是默認(rèn)的ClassLoader加載的,只要ClassLoader存在,類就不會(huì)被卸載,而默認(rèn)的ClassLoader生命周期是與進(jìn)程一致的,本文討論一般情況。
場(chǎng)景2:錯(cuò)誤使用單例造成內(nèi)存泄露
由于單例的靜態(tài)特性使得其生命周期跟應(yīng)用的生命周期一樣長(zhǎng),所以如果使用不恰當(dāng)?shù)脑挘苋菀自斐蓛?nèi)存泄漏。比如下面一個(gè)典型的例子:(由于單例中g(shù)etInstance的方法是靜態(tài)的,所以成員變量mInstance必須是靜態(tài)的,這樣這個(gè)變量的生命周期就和整個(gè)應(yīng)用的生命周期相同)
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() {
}
}
當(dāng)我們?cè)谠谝粋€(gè)Activity中調(diào)用,再關(guān)閉這個(gè)Activity時(shí)就會(huì)出現(xiàn)內(nèi)存泄露。
LoginManager.getInstance(this).dealData();
這是一個(gè)普通的單例模式,當(dāng)創(chuàng)建這個(gè)單例的時(shí)候,由于需要傳入一個(gè)Context,所以這個(gè)Context的生命周期的長(zhǎng)短至關(guān)重要:
- 如果此時(shí)傳入的是 Application 的 Context,因?yàn)?Application 的生命周期就是整個(gè)應(yīng)用的生命周期,所以這將沒有任何問題。
- 如果此時(shí)傳入的是 Activity 的 Context,當(dāng)這個(gè) Context 所對(duì)應(yīng)的 Activity 退出時(shí),由于該 Context 的引用被單例對(duì)象所持有,其生命周期等于整個(gè)應(yīng)用程序的生命周期,所以當(dāng)前 Activity 退出時(shí)它的內(nèi)存并不會(huì)被回收,這就造成泄漏了。
正確的寫法有兩種:
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 中添加一個(gè)靜態(tài)方法,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() {
}
}
場(chǎng)景3:屬性動(dòng)畫導(dǎo)致的內(nèi)存泄露
從Android3.0開始,google提供了屬性動(dòng)畫,屬性動(dòng)畫中有一類無限循環(huán)的動(dòng)畫,如果在Activity中播放此類動(dòng)畫,且沒有在onDestroy中取停止動(dòng)畫,那么動(dòng)畫會(huì)一直播放下去。盡管已經(jīng)無法在界面上看到動(dòng)畫效果了,但此時(shí)Activity的view被動(dòng)畫持有,而View又持有Acitity,最終Activity無法釋放。導(dǎo)致內(nèi)存泄露,解決的方法是在Activity的onDestroy中調(diào)用animator.cancle()來停止動(dòng)畫。
@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();
}