/*
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
package com.facebook.drawee.components;
import java.util.HashSet;
import java.util.Set;
import android.os.Handler;
import android.os.Looper;
/**
* Component that defers {@code release} until after the main Looper has completed its current
* message. Although we would like for defer {@code release} to happen immediately after the current
* message is done, this is not guaranteed as there might be other messages after the current one,
* but before the deferred one, pending in the Looper's queue.
* <p>
* onDetach / onAttach events are used for releasing / acquiring resources. However, sometimes we
* get an onDetach event followed by an onAttach event within the same loop. In order to avoid
* overaggressive resource releasing / acquiring, we defer releasing. If onAttach happens within
* the same loop, we will simply cancel corresponding deferred release, avoiding an unnecessary
* resource release / acquire cycle. If onAttach doesn't happen before the deferred message gets
* executed, the resources will be released.
* <p>
* This class is not thread-safe and should only be used from the main thread (UI thread).
*/
public class DeferredReleaser {
private static DeferredReleaser sInstance = null;
public static synchronized DeferredReleaser getInstance() {
if (sInstance == null) {
sInstance = new DeferredReleaser();
}
return sInstance;
}
public interface Releasable {
public void release();
}
private final Set<Releasable> mPendingReleasables;
private final Handler mUiHandler;
public DeferredReleaser() {
mPendingReleasables = new HashSet<Releasable>();
mUiHandler = new Handler(Looper.getMainLooper());
}
/*
* Walks through the set of pending releasables, and calls release on them.
* Resets the pending list to an empty list when done.
*/
private final Runnable releaseRunnable = new Runnable() {
@Override
public void run() {
for (Releasable releasable : mPendingReleasables) {
releasable.release();
}
mPendingReleasables.clear();
}
};
/**
* Schedules deferred release.
* <p>
* The object will be released after the current Looper's loop,
* unless {@code cancelDeferredRelease} is called before then.
* @param releasable Object to release.
*/
public void scheduleDeferredRelease(Releasable releasable) {
if (!mPendingReleasables.add(releasable)) {
return;
}
// Posting to the UI queue is an O(n) operation, so we only do it once.
// The one runnable does all the releases.
if (mPendingReleasables.size() == 1) {
mUiHandler.post(releaseRunnable);
}
}
/**
* Cancels a pending release for this object.
* @param releasable Object to cancel release of.
*/
public void cancelDeferredRelease(Releasable releasable) {
mPendingReleasables.remove(releasable);
}
}