摘要:引起的內(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 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; }
從構(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
摘要:內(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...
摘要:內(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)...
摘要:所以如果趕在之前切斷是可以避免內(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)存回收方式...
閱讀 2039·2021-09-30 09:47
閱讀 714·2021-09-22 15:43
閱讀 1996·2019-08-30 15:52
閱讀 2445·2019-08-30 15:52
閱讀 2556·2019-08-30 15:44
閱讀 919·2019-08-30 11:10
閱讀 3380·2019-08-29 16:21
閱讀 3304·2019-08-29 12:19