Handler 引發的內存泄露分析以及解決方法

Handler 引發的內存泄露分析以及解決方法

Handler是Android系統提供的一種在子線程更新UI的機制,可是使用不當會致使memory leak。嚴重的話可能致使OOMjava

Java語言的垃圾回收機制採用了可達性分析來判斷一個對象是否還有存在的必要性,如無必要就回收該對象引用的內存區域,async

Handler handler ;
@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    handler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
        }
    };

}

而後在其餘地方來發送一個延遲消息ide

handler.postDelayed(new Runnable() {
    @Override
    public void run() {
        
    }
}, 500);

咱們通常使用Handler就是這樣,可是這樣會當Activity銷燬後會致使memory leak.oop

緣由就是activity銷燬了,可是覺得咱們的Handler對象是一個內部類,由於內部類會持有外部類的一個引用。因此當activity銷燬了,可是由於Handler還持有改Activity的引用,致使GC啓動後,可達性分析發現該Activity對象還有其餘引用。因此沒法銷燬改Activity,post

可是handler僅僅是Activity的一個內存對象。及時他引用了Activity,他們之間也只是循環引用而已。而循環引用則不影響GC回收內存。ui

其實真正的緣由是Handler調用postDelayed發送一個延遲消息時:this

public final boolean postDelayed(Runnable r, long delayMillis)
{
    return sendMessageDelayed(getPostMessage(r), delayMillis);
}

而sendMessageDelayed的實現是線程

public final boolean sendMessageDelayed(Message msg, long delayMillis)
{
    if (delayMillis < 0) {
        delayMillis = 0;
    }
    return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
}

再往下看code

public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
    MessageQueue queue = mQueue;
    if (queue == null) {
        RuntimeException e = new RuntimeException(
                this + " sendMessageAtTime() called with no mQueue");
        Log.w("Looper", e.getMessage(), e);
        return false;
    }
    return enqueueMessage(queue, msg, uptimeMillis);
}

最終是將該消息加入到消息隊列中。對象

private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
    msg.target = this;
    if (mAsynchronous) {
        msg.setAsynchronous(true);
    }
    return queue.enqueueMessage(msg, uptimeMillis);
}

能夠看到,在enqueueMessage的實現中。將msg.target = this;

就是講改Handler對象賦值給了message的target對象。因此message對象就引用了Handler對象''

進而messageQueue對象就引用了Handler對象。這次逐漸明朗。就是messagequeue———message———

Handler——Activity。

因此咱們能夠在任一環節作文章便可避免Handler持有Activity對象致使的內存泄露問題。

咱們能夠在Activity銷燬時將任務隊列清空,或者 在Activity 銷燬時將Handler對象銷燬。

總之,就是在任一環節將該引用鏈條切換就行了,這樣GC就能夠銷燬Activity對象了。

此時仍是沒有觸及到問題的核心,就是爲何messageQueue爲何會持有message對象進而持有Handler對象,致使Activity銷燬時還有其餘引用。爲何Activity銷燬時MessageQueue不銷燬呢,這纔是問題的核心,若是messageQueue銷燬了啥問題也沒有了。固然咱們也能夠在Activity銷燬時手動銷燬messageQueue對象。這樣也能夠避免內存泄露。

從這咱們能夠看出messagequeue的生命週期比Activity長了。因此才致使這些問題。

其實熟悉Handler機制的話就會明白背後的緣由了

final Looper mLooper;
 final MessageQueue mQueue;

public Handler() {
    this(null, false);
}

public Handler(Callback callback, boolean async) {
        if (FIND_POTENTIAL_LEAKS) {
            final Class<? extends Handler> klass = getClass();
            if ((klass.isAnonymousClass() || klass.isMemberClass() || klass.isLocalClass()) &&
                    (klass.getModifiers() & Modifier.STATIC) == 0) {
                Log.w(TAG, "The following Handler class should be static or leaks might occur: " +
                    klass.getCanonicalName());
            }
        }

        mLooper = Looper.myLooper();
        if (mLooper == null) {
            throw new RuntimeException(
                "Can't create handler inside thread " + Thread.currentThread()
                        + " that has not called Looper.prepare()");
        }
        mQueue = mLooper.mQueue;
        mCallback = callback;
        mAsynchronous = async;
    }

從構造方法咱們能夠看出,無參的構造方法最終調用了兩參的構造方法。

mLooper = Looper.myLooper();
        if (mLooper == null) {
            throw new RuntimeException(
                "Can't create handler inside thread " + Thread.currentThread()
                        + " that has not called Looper.prepare()");
        }
        mQueue = mLooper.mQueue;

這幾行代碼纔是重中之重。

首先調用Looper.myLooper()方法。若是looper爲null,說明沒有調用looper.prepare()方法。從拋出的運行時異常能夠看出來。(ps:因此在子線程使用handler時,第一就是要調用Looper.prepare方法)

looper不爲空話話,將looper複製個Handler的looper對象,而後將looper的queue對象賦值給handler的queue對象。

能夠說Handler的looper字段和queue字段都是來着looper對象的。

能夠看出咱們在Handler裏發送的消息最終發送到了handler的queue對象所執行的內存區域,而這片內存區域也是Looper對象的queue對象所指向的。因此說該queue對象裏全部的message對象都收到Looper對象的queue對象的管理。

真正的大boss來了,都是Looper搞鬼。

由於咱們是在主線程中初始化的Handler。因此Handler引用的looper對象是在主線程中建立的。

在代碼ActivityThread.main()中:

public static void main(String[] args) {
        ....

        //建立Looper和MessageQueue對象,用於處理主線程的消息
        Looper.prepareMainLooper();

        //建立ActivityThread對象
        ActivityThread thread = new ActivityThread(); 

        //創建Binder通道 (建立新線程)
        thread.attach(false);

        Looper.loop(); //消息循環運行
        throw new RuntimeException("Main thread loop unexpectedly exited");
    }
Looper.prepareMainLooper();
 
  public static void prepareMainLooper() {
        prepare(false);
        synchronized (Looper.class) {
            if (sMainLooper != null) {
                throw new IllegalStateException("The main Looper has already been prepared.");
            }
            sMainLooper = myLooper();
        }
    }

在prepareMainLooper方法中首先調用了prepare方法,這就是爲何咱們在主線程使用Handler時不須要本身手動調動looper的prepare方法的緣由。

private static void prepare(boolean quitAllowed) {
    if (sThreadLocal.get() != null) {
        throw new RuntimeException("Only one Looper may be created per thread");
    }
    sThreadLocal.set(new Looper(quitAllowed));
}

在prepare方法中首先從sThreadLocal對象中取出looper對象。若是不爲null.說明已經初始化過了,直接拋出異常。

沒有初始化的話直接初始化而後放到sThreadLocal中。sThreadLocal是一個ThreadLocal類型。持有線程的私有數據。

此時,真相大白了。主線程的ThreadLocal——>looper——>messagequue——>message——>handler——>Acitivity

由於APP在活動中,因此主線程一直存在。looper一直存在,messageQueue一直存在。因此當咱們發送了延遲消息時,而此時Activity銷燬的話。天然會引發內存泄露的。

解決方法也很明瞭了。既然咱們不能再looper層面作文章,就只能在handler和message層面作文章了。在Activity銷燬時 將Handler手動置爲null,或者將messagequeue 清空,或者將Handler設置爲靜態內部類。而後內部經過若引用持有Activity對象。總之就是要讓Handler和message改放手時就放手

相關文章
相關標籤/搜索