Weak Handler 與 記憶體洩露
阿新 • • 發佈:2019-01-02
一個通用的場景是 使用 匿名內部類 例項 作為某個 行為/動作的 回撥,如果該行為/動作 是非同步的,則其返回時間往往無法確定,有造成記憶體洩露風險.
使用靜態內部類,或者妥善處理生命週期,都不會造成記憶體洩露,反過來,當沒有記憶體洩露風險時,一般直接匿名內部類即可.
這其實是一個特別矛盾的說法.
因為這要求程式設計師能 能瞭解到 回撥行為是在何時發生的.
而相反,我們設計介面回撥的時候總是盡力遮蔽內部實現細節.
而面對不確定的行為,當然也可以使用靜態內部類,或者removeCallback去取消回撥(特指Handler),然而這樣程式碼變得繁瑣,或者新增不必要的程式碼.
對於Handler,一個簡單的第三方解決方案是使用 android-weak-handler ,該庫嘗試使用WeakReference解決這個問題,然而卻引入新的問題.
核心程式碼如下:
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();
@SuppressWarnings("ConstantConditions")
@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<Handler.Callback> mCallback;
ExecHandler() {
mCallback = null;
}
ExecHandler(WeakReference<Handler.Callback> callback) {
mCallback = callback;
}
ExecHandler(Looper looper) {
super(looper);
mCallback = null;
}
ExecHandler(Looper looper, WeakReference<Handler.Callback> callback) {
super(looper);
mCallback = callback;
}
@Override
public void handleMessage(@NonNull Message msg) {
if (mCallback == null) {
return;
}
final Handler.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;
}
}
}
這個類的實現有個問題,為了避免持