在Android開發中,使用Handler的地方不少,大體一般寫法以下:java
private Handler handler = new Handler() { public void handleMessage(android.os.Message msg) { if (msg.what == 1) { mAdapter.notifyDataSetChanged(); } } };
這段代碼看似沒什麼問題,可是裏面卻有一個警告,警告信息以下:android
This Handler class should be static or leaks might occur
大體意思:建議使用靜態聲明否者可能出現內存泄露
Handler會內存泄露?下面來講明:git
當使用內部類(包括匿名類)來建立Handler的時候,Handler對象會隱式地持有一個外部類對象(一般是一個Activity)的引用(否則你怎麼可能經過Handler來操做Activity中的View?)。而Handler一般會伴隨着一個耗時的後臺線程(例如從網絡拉取圖片)一塊兒出現,這個後臺線程在任務執行完畢(例如圖片下載完畢)以後,經過消息機制通知Handler,而後Handler把圖片更新到界面。然而,若是用戶在網絡請求過程當中關閉了Activity,正常狀況下,Activity再也不被使用,它就有可能在GC檢查時被回收掉,但因爲這時線程還沒有執行完,而該線程持有Handler的引用(否則它怎麼發消息給Handler?),這個Handler又持有Activity的引用,就致使該Activity沒法被回收(即內存泄露),直到網絡請求結束(例如圖片下載完畢)。另外,若是你執行了Handler的postDelayed()方法,該方法會將你的Handler裝入一個Message,並把這條Message推到MessageQueue中,那麼在你設定的delay到達以前,會有一條MessageQueue
-> Message -> Handler -> Activity的鏈,致使你的Activity被持有引用而沒法被回收。github
Java使用有向圖機制,經過GC自動檢查內存中的對象(何時檢查由虛擬機決定),若是GC發現一個或一組對象爲不可到達狀態,則將該對象從內存中回收。也就是說,一個對象不被任何引用所指向,則該對象會在被GC發現的時候被回收;另外,若是一組對象中只包含互相的引用,而沒有來自它們外部的引用(例若有兩個對象A和B互相持有引用,但沒有任何外部對象持有指向A或B的引用),這仍然屬於不可到達,一樣會被GC回收。網絡
內存泄露的危害就是會使虛擬機佔用內存太高,致使OOM(內存溢出),程序出錯。app
對於Android應用來講,就是你的用戶打開一個Activity,使用完以後關閉它,內存泄露;又打開,又關閉,又泄露;幾回以後,程序佔用內存超過系統限制,FC。ide
經過程序邏輯來進行保護oop
1,在關閉Activity的時候停掉你的後臺線程。線程停掉了,就至關於切斷了Handler和外部鏈接的線,Activity天然會在合適的時候被回收。
2,若是你的Handler是被delay的Message持有了引用,那麼使用相應的Handler的removeCallbacks()方法,把消息對象從消息隊列移除就好了。post
將Handler聲明爲靜態類this
靜態類不持有外部類的對象,因此你的Activity能夠隨意被回收。因爲Handler再也不持有外部類對象的引用,致使程序不容許你在Handler中操做Activity中的對象了。因此你須要在Handler中增長一個對Activity的弱引用(WeakReference 文章尾部會有說明)。
PS:在Java 中,非靜態的內部類和匿名內部類都會隱式地持有其外部類的引用,靜態的內部類不會持有外部類的引用。
static class MyHandler extends Handler { WeakReference<Activity> mWeakReference; public MyHandler(Activity activity) { mWeakReference = new WeakReference<Activity>(activity); } @Override public void handleMessage(Message msg) { final Activity activity = mWeakReference.get(); if (activity != null) { if (msg.what == 1) { mAdapter.notifyDataSetChanged(); } } } }
什麼是WeakReference?
WeakReference弱引用,與強引用(即咱們常說的引用)相對,它的特色是,GC在回收時會忽略掉弱引用,即就算有弱引用指向某對象,但只要該對象沒有被強引用指向(實際上多數時候還要求沒有軟引用,但此處軟引用的概念能夠忽略),該對象就會在被GC檢查到時回收掉。對於上面的代碼,用戶在關閉Activity以後,就算後臺線程還沒結束,但因爲僅有一條來自Handler的弱引用指向Activity,因此GC仍然會在檢查的時候把Activity回收掉。這樣,內存泄露的問題就不會出現了。
在此推薦一個寫好的Handler WeakReference,可直接使用。
WeakHandler.java
import java.lang.ref.WeakReference; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; import android.os.Handler; import android.os.Looper; import android.os.Message; import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.support.annotation.VisibleForTesting; public class WeakHandler { private final Handler.Callback mCallback; // hard reference to Callback. We need to keep callback in memory private final ExecHandler mExec; private Lock mLock = new ReentrantLock(); @VisibleForTesting final ChainedRef mRunnables = new ChainedRef(mLock, null); public WeakHandler() { mCallback = null; mExec = new ExecHandler(); } public WeakHandler(@Nullable Handler.Callback callback) { mCallback = callback; // Hard referencing body mExec = new ExecHandler(new WeakReference<>(callback)); // Weak referencing inside ExecHandler } public WeakHandler(@NonNull Looper looper) { mCallback = null; mExec = new ExecHandler(looper); } public WeakHandler(@NonNull Looper looper, @NonNull Handler.Callback callback) { mCallback = callback; mExec = new ExecHandler(looper, new WeakReference<>(callback)); } public final boolean post(@NonNull Runnable r) { return mExec.post(wrapRunnable(r)); } public final boolean postAtTime(@NonNull Runnable r, long uptimeMillis) { return mExec.postAtTime(wrapRunnable(r), uptimeMillis); } public final boolean postAtTime(Runnable r, Object token, long uptimeMillis) { return mExec.postAtTime(wrapRunnable(r), token, uptimeMillis); } public final boolean postDelayed(Runnable r, long delayMillis) { return mExec.postDelayed(wrapRunnable(r), delayMillis); } public final boolean postAtFrontOfQueue(Runnable r) { return mExec.postAtFrontOfQueue(wrapRunnable(r)); } public final void removeCallbacks(Runnable r) { final WeakRunnable runnable = mRunnables.remove(r); if (runnable != null) { mExec.removeCallbacks(runnable); } } public final void removeCallbacks(Runnable r, Object token) { final WeakRunnable runnable = mRunnables.remove(r); if (runnable != null) { mExec.removeCallbacks(runnable, token); } } public final boolean sendMessage(Message msg) { return mExec.sendMessage(msg); } public final boolean sendEmptyMessage(int what) { return mExec.sendEmptyMessage(what); } public final boolean sendEmptyMessageDelayed(int what, long delayMillis) { return mExec.sendEmptyMessageDelayed(what, delayMillis); } public final boolean sendEmptyMessageAtTime(int what, long uptimeMillis) { return mExec.sendEmptyMessageAtTime(what, uptimeMillis); } public final boolean sendMessageDelayed(Message msg, long delayMillis) { return mExec.sendMessageDelayed(msg, delayMillis); } public boolean sendMessageAtTime(Message msg, long uptimeMillis) { return mExec.sendMessageAtTime(msg, uptimeMillis); } public final boolean sendMessageAtFrontOfQueue(Message msg) { return mExec.sendMessageAtFrontOfQueue(msg); } public final void removeMessages(int what) { mExec.removeMessages(what); } public final void removeMessages(int what, Object object) { mExec.removeMessages(what, object); } public final void removeCallbacksAndMessages(Object token) { mExec.removeCallbacksAndMessages(token); } public final boolean hasMessages(int what) { return mExec.hasMessages(what); } public final boolean hasMessages(int what, Object object) { return mExec.hasMessages(what, object); } public final Looper getLooper() { return mExec.getLooper(); } private WeakRunnable wrapRunnable(@NonNull Runnable r) { //noinspection ConstantConditions if (r == null) { throw new NullPointerException("Runnable can't be null"); } final ChainedRef hardRef = new ChainedRef(mLock, r); mRunnables.insertAfter(hardRef); return hardRef.wrapper; } private static class ExecHandler extends Handler { private final WeakReference<Callback> mCallback; ExecHandler() { mCallback = null; } ExecHandler(WeakReference<Callback> callback) { mCallback = callback; } ExecHandler(Looper looper) { super(looper); mCallback = null; } ExecHandler(Looper looper, WeakReference<Callback> callback) { super(looper); mCallback = callback; } @Override public void handleMessage(@NonNull Message msg) { if (mCallback == null) { return; } final Callback callback = mCallback.get(); if (callback == null) { // Already disposed return; } callback.handleMessage(msg); } } static class WeakRunnable implements Runnable { private final WeakReference<Runnable> mDelegate; private final WeakReference<ChainedRef> mReference; WeakRunnable(WeakReference<Runnable> delegate, WeakReference<ChainedRef> reference) { mDelegate = delegate; mReference = reference; } @Override public void run() { final Runnable delegate = mDelegate.get(); final ChainedRef reference = mReference.get(); if (reference != null) { reference.remove(); } if (delegate != null) { delegate.run(); } } } static class ChainedRef { @Nullable ChainedRef next; @Nullable ChainedRef prev; @NonNull final Runnable runnable; @NonNull final WeakRunnable wrapper; @NonNull Lock lock; public ChainedRef(@NonNull Lock lock, @NonNull Runnable r) { this.runnable = r; this.lock = lock; this.wrapper = new WeakRunnable(new WeakReference<>(r), new WeakReference<>(this)); } public WeakRunnable remove() { lock.lock(); try { if (prev != null) { prev.next = next; } if (next != null) { next.prev = prev; } prev = null; next = null; } finally { lock.unlock(); } return wrapper; } public void insertAfter(@NonNull ChainedRef candidate) { lock.lock(); try { if (this.next != null) { this.next.prev = candidate; } candidate.next = this.next; this.next = candidate; candidate.prev = this; } finally { lock.unlock(); } } @Nullable public WeakRunnable remove(Runnable obj) { lock.lock(); try { ChainedRef curr = this.next; // Skipping head while (curr != null) { if (curr.runnable == obj) { // We do comparison exactly how Handler does inside return curr.remove(); } curr = curr.next; } } finally { lock.unlock(); } return null; } } }
by anonymous