摘要:引起的內存泄露分析以及解決方法是系統提供的一種在子線程更新的機制,但是使用不當會導致。所以引用的對象是在主線程中創建的。主線程的因為在活動中,所以主線程一直存在。自然會引起內存泄露的。
Handler 引起的內存泄露分析以及解決方法
Handler是Android系統提供的一種在子線程更新UI的機制,但是使用不當會導致memory leak。嚴重的話可能導致OOM
Java語言的垃圾回收機制采用了可達性分析來判斷一個對象是否還有存在的必要性,如無必要就回收該對象引用的內存區域,
Handler handler ; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); handler = new Handler() { @Override public void handleMessage(Message msg) { super.handleMessage(msg); } }; }
然后在其他地方來發送一個延遲消息
handler.postDelayed(new Runnable() { @Override public void run() { } }, 500);
我們一般使用Handler就是這樣,但是這樣會當Activity銷毀后會導致memory leak.
原因就是activity銷毀了,但是以為我們的Handler對象是一個內部類,因為內部類會持有外部類的一個引用。所以當activity銷毀了,但是因為Handler還持有改Activity的引用,導致GC啟動后,可達性分析發現該Activity對象還有其他引用。所以無法銷毀改Activity,
但是handler僅僅是Activity的一個內存對象。及時他引用了Activity,他們之間也只是循環引用而已。而循環引用則不影響GC回收內存。
其實真正的原因是Handler調用postDelayed發送一個延遲消息時:
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); }
再往下看
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改放手時就放手
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/77835.html
摘要:內存泄漏造成什么影響它是造成應用程序的主要原因之一。因此總結來看,線程產生內存泄露的主要原因有兩點線程生命周期的不可控。造成的內存泄漏早時期的時候處理耗時操作多數都是采用的方式,后來逐步被取代,直到現在采用的方式來處理異步。 目錄介紹: 01.什么是內存泄漏 02.內存泄漏造成什么影響 03.內存泄漏檢測的工具有哪些 04.關于Leakcanary使用介紹 05.錯誤使用單例造成的內...
摘要:所以如果趕在之前切斷是可以避免內存泄露的。經過測試情況始終沒有內存泄露。如果當退出時候,還有消息未處理或正在處理,由于引用又引用,此時將引發內存泄露。總結如果某些單例需要使用到對象,推薦使用的,不要使用的,否則容易導致內存泄露。 之前一直在簡書寫作,第一次發布到SF上來,也是第一次使用SF,后面會盡量同步到SF,更多文章請關注:簡書?編程之樂轉載請注明出處:謝謝! Java內存回收方式...
閱讀 2024·2021-09-30 09:47
閱讀 703·2021-09-22 15:43
閱讀 1981·2019-08-30 15:52
閱讀 2431·2019-08-30 15:52
閱讀 2540·2019-08-30 15:44
閱讀 903·2019-08-30 11:10
閱讀 3372·2019-08-29 16:21
閱讀 3296·2019-08-29 12:19