Android AndroidStudio MAT LeakCanary 內存分析之 初識內存泄漏

Android AndroidStudio MAT LeakCanary 內存分析之 初識內存泄漏
http://blog.csdn.net/qq_28195645/article/details/51733342

Android AndroidStudio MAT LeakCanary 內存分析之 AndroidStudio 內存泄漏分析 Memory Monitor
http://blog.csdn.net/qq_28195645/article/details/51734506

Android AndroidStudio MAT LeakCanary 內存分析之 LeakCanary
http://blog.csdn.net/qq_28195645/article/details/51734987

Android AndroidStudio MAT LeakCanary 內存分析之 DDMS+MAT
http://blog.csdn.net/qq_28195645/article/details/51735522

說起Android進階、那麼必須談一談內存泄漏。那麼現在檢測app內存泄漏的辦法有很多 比如 :
1、DDMS + MAT http://www.eclipse.org/mat/downloads.php
2、leakcanary https://github.com/square/leakcanary
3、AndroidStudio 1.5+ Memory Monitor https://developer.android.com/studio/profile/am-memory.html
題外:Android性能分析工具——TraceView
實際開發中、常用的就是leakcanary,本系列我們會將這三種辦法都有涉及

首先什麼是內存泄漏、常引起內存泄漏的code

這裏寫圖片描述

如上圖、如果你要刪除對象B,這也將釋放它所支配的對象(C,D,E,F)所使用的內存.事實上,如果對象C,D,E和F被標記爲刪除,但對象B還是指他們,這就是他們沒有釋放的原因。

Java內存泄漏指的是進程中某些對象(垃圾對象)已經沒有使用價值了,但是它們卻可以直接或間接地引用到gc roots導致無法被GC回收。無用的對象佔據着內存空間,使得實際可使用內存變小,形象地說法就是內存泄漏了。而內存泄漏出現的原因就是存在了無效的引用,導致本來需要被GC的對象沒有被回收掉。這樣會導致一直佔用着內存。下面分析一些可能導致內存泄漏的情景。如果想了解更多就百度吧

1、非靜態內部類的靜態實例容易造成內存泄漏

    private static Leak mLeak;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_second);
        mLeak = new Leak();
    }

    class Leak {
    }

上面的代碼中的mLeak實例類型爲靜態實例,在第一個MainActivity act1實例創建時,mLeak會獲得並一直持有act1的引用。(原因:mLeak是存儲在靜態區的靜態變量,而Leak是內部類,其持有外部類Activity的引用。)當MainAcitivity銷燬後重建,因爲mLeak持有act1的引用,所以act1是無法被GC回收的,進程中會存在2個MainActivity實例(act1和重建後的MainActivity實例),這個act1對象就是一個無用的但一直佔用內存的對象,即無法回收的垃圾對象。所以,對於lauchMode不是singleInstance的Activity, 應該避免在activity裏面實例化其非靜態內部類的靜態實例。

2、activity使用靜態成員

private static Drawable sBackground;    
@Override    
protected void onCreate(Bundle state) {    
    super.onCreate(state);    

    TextView label = new TextView(this);    
    label.setText("Leaks are bad");    

    if (sBackground == null) {    
        sBackground = getDrawable(R.drawable.large_bitmap);    
    }    
    label.setBackgroundDrawable(sBackground);    

    setContentView(label);    
}   

由於用靜態成員sBackground 緩存了drawable對象,所以activity加載速度會加快,但是這樣做是錯誤的。因爲在Android 2.3系統上,它會導致activity銷燬後無法被系統回收。

label .setBackgroundDrawable函數調用會將label賦值給sBackground的成員變量mCallback。

上面代碼意味着:sBackground(GC Root)會持有TextView對象,而TextView持有Activity對象。所以導致Activity對象無法被系統回收。

下面看看android4.0爲了避免上述問題所做的改進。

先看看android 2.3的Drawable.Java對setCallback的實現:

public final void setCallback(Callback cb){

    mCallback = cb;

}

再看看android 4.0的Drawable.Java對setCallback的實現:

public final void setCallback(Callback cb){

    mCallback = newWeakReference<Callback> (cb);

}

在android 2.3中要避免內存泄漏也是可以做到的, 在activity的onDestroy時調用

sBackgroundDrawable.setCallback(null)。

以上2個例子的內存泄漏都是因爲Activity的引用的生命週期超越了activity對象的生命週期。也就是常說的Context泄漏,因爲activity就是context。

想要避免context相關的內存泄漏,需要注意以下幾點:

·不要對activity的context長期引用(一個activity的引用的生存週期應該和activity的生命週期相同)

·如果可以的話,儘量使用關於application的context來替代和activity相關的context

·如果一個acitivity的非靜態內部類的生命週期不受控制,那麼避免使用它;正確的方法是使用一個靜態的內部類,並且對它的外部類有一WeakReference,就像在ViewRootImpl中內部類W所做的那樣。

3、使用handler時的內存問題

public class HandlerActivity extends Activity {  

    private final Handler mHandler = new Handler() {  
        @Override  
        public void handleMessage(Message msg) {  
            // ...  
        }  
    };  
    @Override  
    public void onCreate(Bundle savedInstanceState) {  
        super.onCreate(savedInstanceState);  
        setContentView(R.layout.activity_main);  
        mHandler.sendMessageDelayed(Message.obtain(), 60000);  
        //just finish this activity  
        finish();  
    }  
}  

以上的寫法會出現這樣的error提示:This Handler class should be static or leaks might occur

1、Handler 的生命週期與Activity 不一致

  • 當Android應用啓動的時候,會先創建一個UI主線程的Looper對象,Looper實現了一個簡單的消息隊列,一個一個的處理裏面的Message對象。主線程Looper對象在整個應用生命週期中存在。
  • 當在主線程中初始化Handler時,該Handler和Looper的消息隊列關聯(沒有關聯會報錯的)。發送到消息隊列的Message會引用發送該消息的Handler對象,這樣系統可以調用 Handler#handleMessage(Message) 來分發處理該消息。

2、handler 引用 Activity 阻止了GC對Acivity的回收

在Java中,非靜態(匿名)內部類會默認隱性引用外部類對象。而靜態內部類不會引用外部類對象。
如果外部類是Activity,則會引起Activity泄露 。

當Activity finish後,延時消息會繼續存在主線程消息隊列中1分鐘,然後處理消息。而該消息引用了Activity的Handler對象,然後這個Handler又引用了這個Activity。這些引用對象會保持到該消息被處理完,這樣就導致該Activity對象無法被回收,從而導致了上面說的 Activity泄露。

避免handler導致的內存泄漏

  • 使用顯形的引用,1.靜態內部類。 2. 外部類
  • 使用弱引用 2. WeakReference
  • android-weak-handler https://github.com/badoo/android-weak-handler
  • Handler可以使用removeCallbacksAndMessages(null),它將移除這個Handler所擁有的Runnable與Message。

修改代碼如下

public class HandlerActivity2 extends Activity {  
    private static final int MESSAGE_1 = 1;  
    private static final int MESSAGE_2 = 2;  
    private static final int MESSAGE_3 = 3;  
    private final Handler mHandler = new MyHandler(this);  
    @Override  
    public void onCreate(Bundle savedInstanceState) {  
        super.onCreate(savedInstanceState);  
        setContentView(R.layout.activity_main);  
        mHandler.sendMessageDelayed(Message.obtain(), 60000);  

        // just finish this activity  
        finish();  
    }  

    public void todo() {  
    };  

    private static class MyHandler extends Handler {  
        private final WeakReference<HandlerActivity2> mActivity;  

        public MyHandler(HandlerActivity2 activity) {  
            mActivity = new WeakReference<HandlerActivity2>(activity);  
        }  

        @Override  
        public void handleMessage(Message msg) {  
            System.out.println(msg);  
            if (mActivity.get() == null) {  
                return;  
            }  
            mActivity.get().todo();  
        }  
    }  
    /**
    *  當Activity finish後 handler對象還是在Message中排隊。 還是會處理消息,這些處理有必要?
    *正常Activitiy finish後,已經沒有必要對消息處理,那需要怎麼做呢?
    *解決方案也很簡單,在Activity onStop或者onDestroy的時候,取消掉該Handler對象的Message和   Runnable。
    *通過查看Handler的API,它有幾個方法:removeCallbacks(Runnable r)和removeMessages(int what)等。
    */

    @Override  
    public void onDestroy() {  
        //  If null, all callbacks and messages will be removed.  
        mHandler.removeCallbacksAndMessages(null);  
    }  
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章