前言
內存泄漏(Memory Leak)是指程序中己動態(tài)分配的堆內存由于某種原因程序未釋放或無法釋放,造成系統(tǒng)內存的浪費,導致程序運行速度減慢甚至系統(tǒng)崩潰(OOM)等嚴重后果
Android發(fā)生內存泄漏的常見情況
靜態(tài)變量
靜態(tài)變量的生命周期和應用的生命周期一樣長。如果靜態(tài)變量持有某個Activity的context,則會引發(fā)對應Activity無法釋放,導致內存泄漏。如果持有application的context,就沒有問題(以下例子是指Activity銷毀時沒有釋放的情況)
常見的有:
- 單例模式:內部實現(xiàn)是靜態(tài)變量和方法
- 靜態(tài)的View:view默認持有Activity的context
- 靜態(tài)Activity
package com.example.testmemoryleak;
import android.content.Context;
import android.os.Handler;
import android.os.Message;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import com.squareup.leakcanary.LeakCanary;
public class MainActivity extends AppCompatActivity {
private Button btn;
private Button btn1;
private static Context StaticVarible;
private Handler mHandler;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
btn = (Button)findViewById(R.id.button);
btn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
MainActivity.this.finish();
}
});
StaticVarible = this;
LeakCanary.install(getApplication());
}
private static class NoLeakHandler extends Handler{
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
}
}
}
這里我用了開源框架LeakCanary來檢測內存泄露,結果如下:

再ActivityDestory時講靜態(tài)變量置空即可
@Override
protected void onDestroy() {
StaticVarible = null;
super.onDestroy();
}
匿名內部類或者非靜態(tài)內部類
常見的包括
Handler,AsyncTask,TimerTask等,一般在處理多線程任務的時候
非靜態(tài)的內部類和匿名內部類都會隱式地持有其外部類的引用(否則怎么訪問外部類的非靜態(tài)成員呢?),靜態(tài)的內部類不會持有外部類的引用
Java中的類可以是static嗎?答案是可以。在java中我們可以有靜態(tài)實例變量、靜態(tài)方法、靜態(tài)塊。類也可以是靜態(tài)的。
java允許我們在一個類里面定義靜態(tài)類。比如內部類(nested class)。把nested class封閉起來的類叫外部類。在java中,我們不能用static修飾頂級類(top level class)。只有內部類可以為static。
靜態(tài)內部類和非靜態(tài)內部類之間到底有什么不同呢?下面是兩者間主要的不同。
(1)內部靜態(tài)類不需要有指向外部類的引用。但非靜態(tài)內部類需要持有對外部類的引用。
(2)非靜態(tài)內部類能夠訪問外部類的靜態(tài)和非靜態(tài)成員。靜態(tài)類不能訪問外部類的非靜態(tài)成員。他只能訪問外部類的靜態(tài)成員。
(3)一個非靜態(tài)內部類不能脫離外部類實體被創(chuàng)建,一個非靜態(tài)內部類可以訪問外部類的數(shù)據(jù)和方法,因為他就在外部類里面。
那么修改方法 1.靜態(tài)內部類 2.銷毀前及時處理非靜態(tài)內部類
package com.example.testmemoryleak;
import android.content.Context;
import android.os.Handler;
import android.os.Message;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import com.squareup.leakcanary.LeakCanary;
public class MainActivity extends AppCompatActivity {
private Button btn;
private Button btn1;
private static Context StaticVarible;
private Handler mHandler;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
btn = (Button)findViewById(R.id.button);
btn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
MainActivity.this.finish();
}
});
btn1 = (Button)findViewById(R.id.button1);
btn1.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
mHandler = new Handler(){
@Override
public void handleMessage(Message msg){
super.handleMessage(msg);
}
};
Message message = Message.obtain();
message.what = 1;
mHandler.sendMessageDelayed(message,5*60*1000);
/*
mHandler = new NoLeakHandler();
Message message = Message.obtain();
message.what = 1;
mHandler.sendMessageDelayed(message,5*60*1000);
Log.i("weijuncheng", String.valueOf(mHandler.hasMessages(1)));
*/
}
});
StaticVarible = this;
LeakCanary.install(getApplication());
}
@Override
protected void onDestroy() {
//mHandler.removeCallbacksAndMessages(null);
StaticVarible = null;
super.onDestroy();
}
private static class NoLeakHandler extends Handler{
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
}
}
}

如上,Activity銷毀時非靜態(tài)內部類mHandler中還有未處理的消息,造成無法釋放其引用的Activity對象
修改方案:
1. 使用靜態(tài)內部類(必要時結合WeakReference,弱引用方式)
private static class NoLeakHandler extends Handler{
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
}
}
mHandler = new NoLeakHandler();
Message message = Message.obtain();
message.what = 1;
mHandler.sendMessageDelayed(message,5*60*1000);
Log.i("weijuncheng", String.valueOf(mHandler.hasMessages(1)));
靜態(tài)內部類延伸
靜態(tài)內部類也可以用來實現(xiàn)單例,保證線程安全,效率高;其主要原理為:Java中靜態(tài)內部類可以訪問其外部類的靜態(tài)成員屬性,同時,1.靜態(tài)內部類只有當被調用的時候才開始首次被加載(懶加載),2.利用了classloader的機制來保證初始化instance時只有一個線程(多個線程就不是靜態(tài)類了),所以也是線程安全的,同時沒有性能損耗(加synchronized同步鎖)
與餓漢方式不同的地方在,餓漢式方式是只要Singleton類被裝載就會實例化,沒有Lazy-Loading的作用,而靜態(tài)內部類方式在Singleton類被裝載時并不會立即實例化,而是在需要實例化時,調用getInstance方法,才會裝載SingletonInstance類,從而完成Singleton的實例化(相當于一個在static語句中初始化,相當于在<clinit>中初始化;另一個相當于在靜態(tài)內部類的<clinit>中進行單例的初始化)
https://www.cnblogs.com/zhaoyan001/p/6365064.html
代碼示例如下
public class Singleton {
private Singleton() {}
private static class SingletonInstance {
private static final Singleton INSTANCE = new Singleton();
}
public static Singleton getInstance() {
return SingletonInstance.INSTANCE; //lazy-loading
}
}
2. 銷毀前及時處理非靜態(tài)內部類
@Override
protected void onDestroy() {
mHandler.removeCallbacksAndMessages(null);
super.onDestroy();
}
監(jiān)聽器
當使用Activity的context注冊監(jiān)聽,不再需要監(jiān)聽時沒有取消注冊
資源對象
資源對象未關閉:BraodcastReceiver,ContentObserver,F(xiàn)ile,Cursor,Stream,Bitmap等資源,使用后未關閉會導致內存泄漏。因為資源性對象往往都用了一些緩沖,緩沖不僅存在于 java虛擬機內,還存在于java虛擬機外。如果僅僅是把它的引用置null,而不關閉它們,也會造成內存泄漏
容器
容器中的對象沒有清理:集合一般占用內存較大,不及時關閉會導致內存緊張(不會導致內存泄漏,而會導致可用內存大大減少)
WebView
檢測、分析內存泄漏的工具
MemoryMonitor:隨時間變化,內存占用的變化情況
MAT:輸入HRPOF文件,輸出分析結果
- Histogram:查看不同類型對象及其大小
- DominateTree:對象占用內存及其引用關系
如何配置LeakCanary開源庫
apply plugin: 'com.android.application'
android {
compileSdkVersion 28
defaultConfig {
applicationId "com.example.testmemoryleak"
minSdkVersion 15
targetSdkVersion 28
versionCode 1
versionName "1.0"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}
buildTypes {
debug{
}
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
}
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation 'com.android.support:appcompat-v7:28.0.0'
implementation 'com.android.support.constraint:constraint-layout:1.1.3'
testImplementation 'junit:junit:4.12'
androidTestImplementation 'com.android.support.test:runner:1.0.2'
androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
debugImplementation 'com.squareup.leakcanary:leakcanary-android:1.6.3'
releaseImplementation 'com.squareup.leakcanary:leakcanary-android-no-op:1.6.3'
// Optional, if you use support library fragments:
debugImplementation 'com.squareup.leakcanary:leakcanary-support-fragment:1.6.3'
}
配置LeakCanary踩過的坑
1. Unable to resolve dependency for ':app@dexOptions/compileClasspath': Could not resolve project : library_Name.
With Android Studio 2.3(AS) the project works fine and i can able to run the App. After updating the AS to Android Studio 3.0. i too got the error as below for libraries and build types.
Unable to resolve dependency for ':app@dexOptions/compileClasspath': Could not resolve project : library_Name. Unable to resolve dependency for ':app@release/compileClasspath': Could not resolve project : library_Name.
To Solve the issue, simply.
What ever the
buildTypes{ debug{ ... } release{ ... } }
2.Could not resolve all dependencies for configuration ':app:debugRuntimeClasspath'.
org.gradle.api.UncheckedIOException: Failed to capture fingerprint of input files for task ':app:preDebugBuild' property 'compileManifests' during up-to-date check
https://my.oschina.net/u/616133/blog/2221453 (這樣操作就行,為什么?允許內置的maven倉庫?)
File --> Setting --->Build,Execution,Deployment --> Gradle -->Android Studio 勾選 “Enable embedded Maven repository”
如何避免內存泄漏
不要在匿名內部類中進行異步操作
將非靜態(tài)內部類轉為靜態(tài)內部類 + WeakReference(弱引用)的方式
在 Activity 回調 onDestroy 時或者 onStop 時
- 移除消息隊列 MessageQueue 中的消息
- 靜態(tài)變量置null
- 停止異步任務
- 取消注冊
使用Context時,盡量使用Application 的 Context
盡量避免使用static 成員變量。另外可以考慮lazy初始化
為webView開啟另外一個進程,通過AIDL與主線程進行通信,WebView所在的進程可以根據(jù)業(yè)務的需要選擇合適的時機進行銷毀,從而達到內存的完整釋放
及時關閉資源。Bitmap 使用后調用recycle()方法
總結
大體說明了Android內存泄漏的常見情況和修改方法,平時我們進行代碼書寫時要注意這些條件,盡量避免內存泄露的發(fā)生;如果發(fā)生了要學會運用工具分析解決