成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專欄INFORMATION COLUMN

Handler 引起的內(nèi)存泄露分析以及解決方法

fish / 3107人閱讀

摘要:引起的內(nèi)存泄露分析以及解決方法是系統(tǒng)提供的一種在子線程更新的機制,但是使用不當(dāng)會導(dǎo)致。所以引用的對象是在主線程中創(chuàng)建的。主線程的因為在活動中,所以主線程一直存在。自然會引起內(nèi)存泄露的。

Handler 引起的內(nèi)存泄露分析以及解決方法

Handler是Android系統(tǒng)提供的一種在子線程更新UI的機制,但是使用不當(dāng)會導(dǎo)致memory leak。嚴(yán)重的話可能導(dǎo)致OOM

Java語言的垃圾回收機制采用了可達(dá)性分析來判斷一個對象是否還有存在的必要性,如無必要就回收該對象引用的內(nèi)存區(qū)域,

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

}

然后在其他地方來發(fā)送一個延遲消息

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

我們一般使用Handler就是這樣,但是這樣會當(dāng)Activity銷毀后會導(dǎo)致memory leak.

原因就是activity銷毀了,但是以為我們的Handler對象是一個內(nèi)部類,因為內(nèi)部類會持有外部類的一個引用。所以當(dāng)activity銷毀了,但是因為Handler還持有改Activity的引用,導(dǎo)致GC啟動后,可達(dá)性分析發(fā)現(xiàn)該Activity對象還有其他引用。所以無法銷毀改Activity,

但是handler僅僅是Activity的一個內(nèi)存對象。及時他引用了Activity,他們之間也只是循環(huán)引用而已。而循環(huán)引用則不影響GC回收內(nèi)存。

其實真正的原因是Handler調(diào)用postDelayed發(fā)送一個延遲消息時:

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

而sendMessageDelayed的實現(xiàn)是

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

再往下看

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的實現(xiàn)中。將msg.target = this;

就是講改Handler對象賦值給了message的target對象。所以message對象就引用了Handler對象""

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

Handler——Activity。

所以我們可以在任一環(huán)節(jié)做文章即可避免Handler持有Activity對象導(dǎo)致的內(nèi)存泄露問題。

我們可以在Activity銷毀時將任務(wù)隊列清空,或者 在Activity 銷毀時將Handler對象銷毀。

總之,就是在任一環(huán)節(jié)將該引用鏈條切換就好了,這樣GC就可以銷毀Activity對象了。

此時還是沒有觸及到問題的核心,就是為什么messageQueue為什么會持有message對象進而持有Handler對象,導(dǎo)致Activity銷毀時還有其他引用。為什么Activity銷毀時MessageQueue不銷毀呢,這才是問題的核心,如果messageQueue銷毀了啥問題也沒有了。當(dāng)然我們也可以在Activity銷毀時手動銷毀messageQueue對象。這樣也可以避免內(nèi)存泄露。

從這我們可以看出messagequeue的生命周期比Activity長了。所以才導(dǎo)致這些問題。

其實熟悉Handler機制的話就會明白背后的原因了

 final Looper mLooper;
 final MessageQueue mQueue;

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

public Handler(Callback callback, boolean async) {
        if (FIND_POTENTIAL_LEAKS) {
            final Class 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;
    }

從構(gòu)造方法我們可以看出,無參的構(gòu)造方法最終調(diào)用了兩參的構(gòu)造方法。

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;

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

首先調(diào)用Looper.myLooper()方法。如果looper為null,說明沒有調(diào)用looper.prepare()方法。從拋出的運行時異常可以看出來。(ps:所以在子線程使用handler時,第一就是要調(diào)用Looper.prepare方法)

looper不為空話話,將looper復(fù)制個Handler的looper對象,然后將looper的queue對象賦值給handler的queue對象。

可以說Handler的looper字段和queue字段都是來著looper對象的。

可以看出我們在Handler里發(fā)送的消息最終發(fā)送到了handler的queue對象所執(zhí)行的內(nèi)存區(qū)域,而這片內(nèi)存區(qū)域也是Looper對象的queue對象所指向的。所以說該queue對象里所有的message對象都收到Looper對象的queue對象的管理。

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

因為我們是在主線程中初始化的Handler。所以Handler引用的looper對象是在主線程中創(chuàng)建的。

在代碼ActivityThread.main()中:

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

        //創(chuàng)建Looper和MessageQueue對象,用于處理主線程的消息
        Looper.prepareMainLooper();

        //創(chuàng)建ActivityThread對象
        ActivityThread thread = new ActivityThread(); 

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

        Looper.loop(); //消息循環(huán)運行
        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方法中首先調(diào)用了prepare方法,這就是為什么我們在主線程使用Handler時不需要自己手動調(diào)動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.說明已經(jīng)初始化過了,直接拋出異常。

沒有初始化的話直接初始化然后放到sThreadLocal中。sThreadLocal是一個ThreadLocal類型。持有線程的私有數(shù)據(jù)。

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

因為APP在活動中,所以主線程一直存在。looper一直存在,messageQueue一直存在。所以當(dāng)我們發(fā)送了延遲消息時,而此時Activity銷毀的話。自然會引起內(nèi)存泄露的。

解決方法也很明了了。既然我們不能再looper層面做文章,就只能在handler和message層面做文章了。在Activity銷毀時 將Handler手動置為null,或者將messagequeue 清空,或者將Handler設(shè)置為靜態(tài)內(nèi)部類。然后內(nèi)部通過若引用持有Activity對象??傊褪且孒andler和message改放手時就放手

文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請注明本文地址:http://systransis.cn/yun/77835.html

相關(guān)文章

  • 內(nèi)存泄漏優(yōu)化

    摘要:內(nèi)存泄漏造成什么影響它是造成應(yīng)用程序的主要原因之一。造成的內(nèi)存泄漏早時期的時候處理耗時操作多數(shù)都是采用的方式,后來逐步被取代,直到現(xiàn)在采用的方式來處理異步。 目錄介紹: 01.什么是內(nèi)存泄漏 02.內(nèi)存泄漏造成什么影響 03.內(nèi)存泄漏檢測的工具有哪些 04.關(guān)于Leakcanary使用介紹 05.錯誤使用單例造成的內(nèi)存泄漏 06.Handler使用不當(dāng)造成內(nèi)存泄漏 07.Thread...

    fanux 評論0 收藏0
  • Android內(nèi)存泄漏優(yōu)化

    摘要:內(nèi)存泄漏造成什么影響它是造成應(yīng)用程序的主要原因之一。因此總結(jié)來看,線程產(chǎn)生內(nèi)存泄露的主要原因有兩點線程生命周期的不可控。造成的內(nèi)存泄漏早時期的時候處理耗時操作多數(shù)都是采用的方式,后來逐步被取代,直到現(xiàn)在采用的方式來處理異步。 目錄介紹: 01.什么是內(nèi)存泄漏 02.內(nèi)存泄漏造成什么影響 03.內(nèi)存泄漏檢測的工具有哪些 04.關(guān)于Leakcanary使用介紹 05.錯誤使用單例造成的內(nèi)...

    sourcenode 評論0 收藏0
  • 徹底搞懂Java內(nèi)存泄露

    摘要:所以如果趕在之前切斷是可以避免內(nèi)存泄露的。經(jīng)過測試情況始終沒有內(nèi)存泄露。如果當(dāng)退出時候,還有消息未處理或正在處理,由于引用又引用,此時將引發(fā)內(nèi)存泄露??偨Y(jié)如果某些單例需要使用到對象,推薦使用的,不要使用的,否則容易導(dǎo)致內(nèi)存泄露。 之前一直在簡書寫作,第一次發(fā)布到SF上來,也是第一次使用SF,后面會盡量同步到SF,更多文章請關(guān)注:簡書?編程之樂轉(zhuǎn)載請注明出處:謝謝! Java內(nèi)存回收方式...

    seasonley 評論0 收藏0

發(fā)表評論

0條評論

fish

|高級講師

TA的文章

閱讀更多
最新活動
閱讀需要支付1元查看
<