2015-09-04 61 views
7

我一直在努力識別內存泄漏。我想我的項目circular progress view有幾個內存泄漏。實際示例中的內存泄漏

我的猜測之一是我在內部類FadeRunnable內存泄漏。 但說實話,我不知道如何確定這是否是問題的根源。那麼,當我執行通常的方案並切換方向時,就會看到內存使用量增加,如下所示。如果我註釋掉FadeRunnable類的使用步驟是小(但還是存在的,所以我想這是不是唯一的泄漏

memory steps

有一次,我分析了堆轉儲,我看到的東西。但實際上我不知道這些價值是什麼意思。我做的事情是

  1. 改變方向多次
  2. 打開堆轉儲和分類方式「保留大小」
  3. 現在,當我點擊「CircularProgressView」我看到在右側區域8行。我猜這意味着還有的「CircularProgressView」泄露和生活在記憶裏孤兒的地方。

這是正確的嗎?如果是這樣,我怎麼能在轉儲信息找出(我想在某處8個實例下方的窗格)保存/保存此對象的位置。

heap dump

我很想有一個一步一步的解釋如何找出是否和對象泄漏一些內存。

可疑視圖的所有代碼都可以在這個類中找到。

https://github.com/momentummodules/CircularProgressView/blob/master/circularprogressview/src/main/java/momentum/circularprogressview/CircularProgressView.java

但也隨時檢查出完整的項目更深入的瞭解,如果你想要玩它。

在此先感謝!

UPDATE

從上面的代碼鏈接顯示MEM-泄漏內部類的固定代碼。下面的代碼片段顯示了原本應該永遠不會使用的代碼泄漏代碼

/** 
* Mem-leaking code, for fixed code see repository link 
* https://github.com/momentummodules/CircularProgressView/blob/master/circularprogressview/src/main/java/momentum/circularprogressview/CircularProgressView.java 
*/ 
public class CircularProgressView extends View 
{ 
    ... 
    private Thread fadeThread = null; 
    ... 

    ... 
    class FadeRunnable implements Runnable 
    { 
     @Override 
     public void run() 
     { 
      ... 
     } 
    } 
    ... 

    ... 
    private void startFade(boolean fadeIn) 
    { 
     // check existing 
     if(this.fadeThread != null) 
     { 
      // check if fade is already running 
      switch(this.fadeThread.getState()) 
      { 
       case TERMINATED: 
       case NEW: 
        this.fadeThread = null; 
        break; 
       case RUNNABLE: 
       case BLOCKED: 
       case TIMED_WAITING: 
       case WAITING: 
        return; 
      } 
     } 
     // create new 
     this.fadeThread = new Thread(new FadeRunnable(fadeIn, this.fadeTime)); 
     this.fadeThread.start(); 
    } 
} 
+1

我會建議從LeakCanary庫開始:https://github.com/square/leakcanary。集成和開箱非常容易,它會告訴你,如果你泄漏的背景。 –

+0

是的,使用它已經爲我的大項目。但實際上,我有興趣瞭解整個事情,而不是依賴第三方庫! –

回答

5

是的,您在FadeRunnable類中確實有內存泄漏。

內部類的每個實例都包含對其外部類的隱式引用,可通過OuterClass.this運算符訪問。在您的項目中,當您執行FadeRunnable,然後通過方向更改觸發重新配置時,將重新創建包含在內的整個活動和CircularProgressView,但前一個的FadeRunnable仍處於活動狀態(已分配),並且由於它持有對其外部的隱式引用CircularProgressView類,視圖仍然存在,這就是爲什麼經過多次重新配置後,您在內存中分配了8個CircularProgressView實例,並且情況變得更糟 - 每個View都會保留對其上下文的引用,並且這也無法釋放,從而導致內存不足泄漏。

Runnables,Handlers和類似的對象可以將其封裝的活動,片段,視圖等等聲明爲標準類或STATIC內部類(靜態內部類不保留對其外部類的隱式引用) ,不應該保留引用,如Context,View等,而是可以保留WeakReference<>,所以當通過配置更改重新創建Activity時,View可以被垃圾回收器銷燬和釋放。

This是關於這個問題的非常豐富的文章,我強烈建議閱讀它。

+0

非常感謝。我有這種情況。但是,您是否有一些關於一般分步內存泄漏分析的鏈接?我對使用堆轉儲的方式感興趣,發現'FadeRunnable'是原因。 –

+0

不是我現在能想到的,對不起 – maciekjanusz

2

我想你有正確的方向。這FadeRunnable肯定不是很酷。即使你有其他內存泄漏,你應該檢查了這一點。

一般來說,你應該在視圖中做的事情是完全不同的,特別是視圖已經有設施來處理時序和動畫而不需要線程。

我會建議你,我相信是一個更簡單和更清潔的方法來動畫視圖上的東西。

  • 首先刪除您的可運行和完全線程。

然後開始你做一個動畫:

ValueAnimator animation = ValueAnimator.ofFloat(0, 1); 
animation.setDuration(500); 
animation.addUpdateListener(animationUpdate); 
animation.addListener(animationUpdate); 
animation.start(); 

,然後你需要那些聽衆

// this gets called for every animation update, 
    // inside this call you update `CircularProgressView.this.fadeAlpha` 
    private final ValueAnimator.AnimatorUpdateListener animationUpdate = new ValueAnimator.AnimatorUpdateListener() { 
     @Override public void onAnimationUpdate(ValueAnimator animation) { 
     // this fraction varies between 0f and 1f 
     float fraction = animation.getAnimatedFraction(); 
     // ... do your calculation 

     ViewCompat.postInvalidateOnAnimation(CircularProgressView.this); 
     } 
    }; 

    // this is an optional one only if you really need 
    // in that you get notified when the animation starts and ends 
    private final Animator.AnimatorListener animationListener = new AnimatorListenerAdapter() { 

    @Override public void onAnimationStart(Animator animation) { 
     // anything u need goes here 
     ViewCompat.postInvalidateOnAnimation(CircularProgressView.this); 
    } 

     @Override public void onAnimationEnd(Animator animation) { 
     // anything u need goes here 
     ViewCompat.postInvalidateOnAnimation(CircularProgressView.this); 
     } 
    }; 

,這就是它。

關於實際內存泄漏分析的主題,我建議你從現在開始永遠使用泄漏金絲雀庫:https://github.com/square/leakcanary這是幫助我們(開發人員)跟蹤內存泄漏的好工具。

編輯:

你爲什麼有這個動畫內存泄漏? 這是很簡單的:

  • startFade(boolean);您創建一個新的線程和新的可運行
  • 可運行已到視圖的引用(因爲它是一個非靜態內部類)
  • 線程有引用Runnable,所以可以運行它。
  • 框架破壞的觀點,因爲它不是用戶界面的一部分了(旋轉,返回鍵)
  • 線程仍在運行,同時運行的還循環,與仍然沒有被破壞,因爲可運行引用視圖對象時,它。
  • 視圖對象有一個Context的實例,這個上下文是Activity

因此,在這個序列的結尾,你的活動不會被垃圾收集到GC,AKA:Memory Leak!

+0

感謝此方法的動畫值。但關於內存泄漏,我已經在我的其他項目中使用了泄漏金絲雀,但是想從頭開始理解整個內存泄漏故事 –

+1

@martynmlostekk在我的答案結尾處看到我的編輯。 – Budius

+0

還有一個問題。我可以使用'postInvalidate'嗎?或者'postInvalidate'和'postInvalidateOnAnimation'有什麼區別? –