Android開發之漫漫長途 番外篇——內存泄漏分析與解決
該文章是一個系列文章,是本人在Android開發的漫漫長途上的一點感想和記錄,我會盡量按照先易后難的順序進行編寫該系列。該系列引用了《Android開發藝術探索》以及《深入理解Android 卷Ⅰ,Ⅱ,Ⅲ》中的相關知識,另外也借鑒了其他的優質博客,在此向各位大神表示感謝,膜拜!!!另外,本系列文章知識可能需要有一定Android開發基礎和項目經驗的同學才能更好理解,也就是說該系列文章面向的是Android中高級開發工程師。
前言
上一篇我們主要上了一個實例來把讀者帶進自定義ViewGroup的大門,只是帶進大門,自定義View的內容還有很多,我之后碰到一些好的自定義View的話一定還來這里分享。本篇內容我們來分析App運行過程中出現的內存泄漏及如何解決。
內存泄漏概念及其影響
內存泄漏通俗的講是一個本該被回收的對象卻因為某些原因導致其不能回收。我們都知道對象是有生命周期的,從生到死,當對象的任務完成之后,由Android系統進行垃圾回收。我們知道Android系統為某個App分配的內存是有限的(這個可能根據機型的不同而不同),當一個應用中產生的內存泄漏比較多時,就難免會導致應用所需要的內存超過這個系統分配的內存限額,最終導致OOM(OutOfMemory)使程序崩潰。
內存泄漏檢查工具介紹
早在使用Eclipse的時候我們就知道了MAT性能分析工具,使用MAT當然能檢查內存泄漏,不過使用稍微有些麻煩,我這里介紹另一個工具,同時呢,我們也拋棄了Eclipse,擁抱Android Studio。這個工具名叫LeakCanary。為什么要使用這個工具呢,當然因為其簡單,傻瓜式操作。這個工具是在Github開源的,是Square公司出品的,不是有一句話嘛,Square出品必屬精品,https://github.com/square/leakcanary我們可以方便的引用它
In your build.gradle:
dependencies {
debugCompile 'com.squareup.leakcanary:leakcanary-android:1.5.4'
releaseCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.5.4'
}
In your Application class:
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...
}
}
就是如此簡單,那么下面我們就來用一下把 結合下面的內存泄漏場景應用。
常見的內存泄漏
在我們平時的開發中可能已經造成了內存泄漏而不自知,下面就羅列其中幾種,看看你的程序里是不是有這樣的代碼。
靜態變量造成的內存泄漏
public class MainActivity extends Activity{
private static final String TAG = "MainActivity";
private static Context sContext;
private static View sView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//這里直接把當前Activity賦值給了靜態變量sContext
sContext = this;
//這種寫法和上面的類似
sView = new View(this);
}
}
上面這種方法估計小學生都知道會造成內存泄漏,原因是當MainActivity對象完成任務需要回收時,卻有一個靜態變量引用它(靜態變量的生命周期與Application相同),造成內存泄漏。我們使用LeakCanary分析就是如下圖
當我們的App發生內存泄漏時會在通知欄顯示通知,點擊該通知可得到內存泄漏的詳細信息,或者點擊上圖中的Leaks圖標獲得App運行過程中所有的內存泄漏,上面例子中得到的內存泄漏信息如下圖所示
單例模式造成的內存泄漏
上面的內存泄漏太明顯,估計大家都不會這樣寫,但是單例模式就不一樣了,我們往往會忽略掉錯誤使用單例模式而造成的泄漏。比如說我們常在開發中用到的dp轉px,px轉dp等往往會封裝成一個單例類。如下
public class DisplayUtils {
private static volatile DisplayUtils instance = null;
private Context mContext;
private DisplayUtils(Context context){
this.mContext = context;
}
public static DisplayUtils getInstance(Context context){
if (instance != null){
synchronized (DisplayUtils.class){
if (instance !=null){
instance = new DisplayUtils(context);
}
}
}
return instance;
}
public int dip2px(float dpValue) {
final float scale = mContext.getResources().getDisplayMetrics().density;
return (int) (dpValue * scale + 0.5f);
}
}
然后我們去調用它
public class SingleActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_single);
//這里我們把當前SingleActivity傳入
DisplayUtils.getInstance(this).dip2px(5);
}
}
就這樣內存泄漏產生了,我們可以看圖。
這個圖和上面的內存泄漏的圖很相像。但是我們常常忽略了這種內存泄漏,是因為我們沒有直接使用靜態變量指向傳遞進來的參數,解決辦法要保證Context和AppLication的生命周期一樣,修改后代碼如下:
public class DisplayUtils {
private static volatile DisplayUtils instance = null;
private Context mContext;
private DisplayUtils(Context context){
//這里變化了,把當前Context指向個應用程序的Context
this.mContext = context.getApplicationContext();
}
public static DisplayUtils getInstance(Context context){
if (instance != null){
synchronized (DisplayUtils.class){
if (instance !=null){
instance = new DisplayUtils(context);
}
}
}
return instance;
}
public int dip2px(float dpValue) {
final float scale = mContext.getResources().getDisplayMetrics().density;
return (int) (dpValue * scale + 0.5f);
}
}
非靜態內部類創建靜態實例造成的內存泄漏
我們在程序中基本上不能避免使用ListView或者RecyclerView,談到這些列表展示的類,那么我們的Adapter基本上也是不可缺少,我們在優化ListView的Adapter的時候會使用ViewHolder(RecyclerView本身已經做了優化),我們在使用ViewHolder的使用建議使用靜態內部類。那么為什么會由此建議呢?這就是我們下面要談到的。非靜態內部類創建靜態實例可能造成的內存泄漏
public class NonStaticActivity extends AppCompatActivity {
private static Config sConfig;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_non_static);
//Config類并不是靜態類,
sConfig = new Config();
}
class Config {
}
}
造成內存泄漏的原因是內部類會隱式持有外部類的引用,這里的外部類是NonStaticActivity,然而內部類sConfig又是static靜態變量其生命周期與Application生命周期一樣,所以在NonStaticActivity關閉的時候,內部類靜態實例依然持有對NonStaticActivity的引用,導致NonStaticActivity無法被回收釋放,引發內存泄漏。
解決辦法就是把內部類生命為靜態內部類,與外部類解耦。,這也是在使用ViewHolder的使用建議使用靜態內部類的原因。
WebView造成的內存泄漏
對于使用Android的WebView造成的內存泄漏。我在此建議使用https://github.com/delight-im/Android-AdvancedWebView,使用這個優化后的WebView,按照提示進行操作。
Handler造成的內存泄漏
我在我的項目中使用了handler,此時mHandler會隱式地持有一個外部類對象引用這里就是MainActivity,當執行postDelayed方法時,該方法會將你的Handler裝入一個Message,并把這條Message推到MessageQueue中,MessageQueue是在一個Looper線程中不斷輪詢處理消息,那么當這個Activity退出時消息隊列中還有未處理的消息或者正在處理消息,而消息隊列中的Message持有mHandler實例的引用,mHandler又持有Activity的引用,所以導致該Activity的內存資源無法及時回收,引發內存泄漏。
public class HandlerActivity extends AppCompatActivity {
private Handler mHandler = new Handler();
private TextView mTextView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_handler);
mTextView = (TextView) findViewById(R.id.text);//模擬內存泄露
mHandler.postDelayed(new Runnable() {
@Override
public void run() {
mTextView.setText("test");
}
}, 5 * 60 * 1000);
}
}
用LeakCanary可以看到類似下圖
解決辦法是 在HandlerActivity onDestroy里面移除消息隊列中所有消息和所有的Runnable。
@Override
protected void onDestroy() {
super.onDestroy();
mHandler.removeCallbacksAndMessages(null);
mHandler = null;
}
其他原因造成的內存泄漏
造成內存泄漏的原因有很多,我們這里只是列舉了其中比較典型的幾種,當然還有好多原因會造成內存泄漏,比如資源開啟但是未關閉、多線程等等等等。但是我們有LeakCanary這個利器哈。
本篇總結
本篇只是稍微介紹了下LeakCanary以及幾種常見的內存泄漏,內存泄漏以及內存性能優化是個持久的過程。我這里只是向你們介紹其中一種方法。編程無止境,性能優化也是。
來自: