Skip to content

Commit

Permalink
Add a clearOnDetach method to ViewTarget.
Browse files Browse the repository at this point in the history
Also returns appropriate ViewTarget implementations from into(ImageView) 
so that the method can easily be chained.

Related to #2520.
  • Loading branch information
sjudd committed Nov 19, 2017
1 parent eff228f commit d0fd967
Show file tree
Hide file tree
Showing 6 changed files with 314 additions and 14 deletions.
5 changes: 3 additions & 2 deletions library/src/main/java/com/bumptech/glide/GlideContext.java
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
import com.bumptech.glide.load.engine.Engine;
import com.bumptech.glide.request.RequestOptions;
import com.bumptech.glide.request.target.ImageViewTargetFactory;
import com.bumptech.glide.request.target.Target;
import com.bumptech.glide.request.target.ViewTarget;
import java.util.Map;
import java.util.Map.Entry;

Expand Down Expand Up @@ -69,7 +69,8 @@ public <T> TransitionOptions<?, T> getDefaultTransitionOptions(Class<T> transcod
return (TransitionOptions<?, T>) result;
}

public <X> Target<X> buildImageViewTarget(ImageView imageView, Class<X> transcodeClass) {
public <X> ViewTarget<ImageView, X> buildImageViewTarget(
ImageView imageView, Class<X> transcodeClass) {
return imageViewTargetFactory.buildTarget(imageView, transcodeClass);
}

Expand Down
3 changes: 2 additions & 1 deletion library/src/main/java/com/bumptech/glide/RequestBuilder.java
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
import com.bumptech.glide.request.ThumbnailRequestCoordinator;
import com.bumptech.glide.request.target.PreloadTarget;
import com.bumptech.glide.request.target.Target;
import com.bumptech.glide.request.target.ViewTarget;
import com.bumptech.glide.signature.ApplicationVersionSignature;
import com.bumptech.glide.signature.ObjectKey;
import com.bumptech.glide.util.Preconditions;
Expand Down Expand Up @@ -590,7 +591,7 @@ private <Y extends Target<TranscodeType>> Y into(
* @return The
* {@link com.bumptech.glide.request.target.Target} used to wrap the given {@link ImageView}.
*/
public Target<TranscodeType> into(ImageView view) {
public ViewTarget<ImageView, TranscodeType> into(ImageView view) {
Util.assertMainThread();
Preconditions.checkNotNull(view);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,11 @@
public class ImageViewTargetFactory {

@SuppressWarnings("unchecked")
public <Z> Target<Z> buildTarget(ImageView view, Class<Z> clazz) {
public <Z> ViewTarget<ImageView, Z> buildTarget(ImageView view, Class<Z> clazz) {
if (Bitmap.class.equals(clazz)) {
return (Target<Z>) new BitmapImageViewTarget(view);
return (ViewTarget<ImageView, Z>) new BitmapImageViewTarget(view);
} else if (Drawable.class.isAssignableFrom(clazz)) {
return (Target<Z>) new DrawableImageViewTarget(view);
return (ViewTarget<ImageView, Z>) new DrawableImageViewTarget(view);
} else {
throw new IllegalArgumentException(
"Unhandled class: " + clazz + ", try .as*(Class).transcode(ResourceTranscoder)");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,13 @@
import android.content.Context;
import android.graphics.Point;
import android.graphics.drawable.Drawable;
import android.support.annotation.CallSuper;
import android.support.annotation.Nullable;
import android.support.annotation.VisibleForTesting;
import android.util.Log;
import android.view.Display;
import android.view.View;
import android.view.View.OnAttachStateChangeListener;
import android.view.ViewGroup.LayoutParams;
import android.view.ViewTreeObserver;
import android.view.WindowManager;
Expand Down Expand Up @@ -45,6 +47,11 @@ public abstract class ViewTarget<T extends View, Z> extends BaseTarget<Z> {

protected final T view;
private final SizeDeterminer sizeDeterminer;
@Nullable
private OnAttachStateChangeListener attachStateListener;
private boolean isClearedByUs;
private boolean isAttachStateListenerAdded;


/**
* Constructor that defaults {@code waitForLayout} to {@code false}.
Expand All @@ -70,6 +77,78 @@ public ViewTarget(T view, boolean waitForLayout) {
sizeDeterminer = new SizeDeterminer(view, waitForLayout);
}

/**
* Clears the {@link View}'s {@link Request} when the {@link View} is detached from its
* {@link android.view.Window} and restarts the {@link Request} when the {@link View} is
* re-attached from its {@link android.view.Window}.
*
* <p>This is an experimental API that may be removed in a future version.
*
* <p>Using this method can save memory by allowing Glide to more eagerly clear resources when
* transitioning screens or swapping adapters in scrolling views. However it also substantially
* increases the odds that images will not be in memory if users subsequently return to a screen
* where images were previously loaded. Whether or not this happens will depend on the number
* of images loaded in the new screen and the size of the memory cache. Increasing the size of
* the memory cache can improve this behavior but it largely negates the memory benefits of using
* this method.
*
* <p>Use this method with caution and measure your memory usage to ensure that it's actually
* improving your memory usage in the cases you care about.
*/
// Public API.
@SuppressWarnings({"UnusedReturnValue", "WeakerAccess"})
public final ViewTarget<T, Z> clearOnDetach() {
if (attachStateListener != null) {
return this;
}
attachStateListener = new OnAttachStateChangeListener() {
@Override
public void onViewAttachedToWindow(View v) {
Request request = getRequest();
if (request != null && request.isPaused()) {
request.begin();
}
}

@Override
public void onViewDetachedFromWindow(View v) {
Request request = getRequest();
if (request != null && !request.isCancelled() && !request.isPaused()) {
isClearedByUs = true;
request.pause();
isClearedByUs = false;
}
}
};
maybeAddAttachStateListener();
return this;
}

@CallSuper
@Override
public void onLoadStarted(@Nullable Drawable placeholder) {
super.onLoadStarted(placeholder);
maybeAddAttachStateListener();
}

private void maybeAddAttachStateListener() {
if (attachStateListener == null || isAttachStateListenerAdded) {
return;
}

view.addOnAttachStateChangeListener(attachStateListener);
isAttachStateListenerAdded = true;
}

private void maybeRemoveAttachStateListener() {
if (attachStateListener == null || !isAttachStateListenerAdded) {
return;
}

view.removeOnAttachStateChangeListener(attachStateListener);
isAttachStateListenerAdded = false;
}

/**
* Returns the wrapped {@link android.view.View}.
*/
Expand All @@ -86,20 +165,27 @@ public T getView() {
*
* @param cb {@inheritDoc}
*/
@CallSuper
@Override
public void getSize(SizeReadyCallback cb) {
sizeDeterminer.getSize(cb);
}

@CallSuper
@Override
public void removeCallback(SizeReadyCallback cb) {
sizeDeterminer.removeCallback(cb);
}

@CallSuper
@Override
public void onLoadCleared(Drawable placeholder) {
super.onLoadCleared(placeholder);
sizeDeterminer.clearCallbacksAndListener();

if (!isClearedByUs) {
maybeRemoveAttachStateListener();
}
}

/**
Expand Down Expand Up @@ -278,11 +364,11 @@ void removeCallback(SizeReadyCallback cb) {
}

void clearCallbacksAndListener() {
// Keep a reference to the layout listener and remove it here
// Keep a reference to the layout attachStateListener and remove it here
// rather than having the observer remove itself because the observer
// we add the listener to will be almost immediately merged into
// we add the attachStateListener to will be almost immediately merged into
// another observer and will therefore never be alive. If we instead
// keep a reference to the listener and remove it here, we get the
// keep a reference to the attachStateListener and remove it here, we get the
// current view tree observer and should succeed.
ViewTreeObserver observer = view.getViewTreeObserver();
if (observer.isAlive()) {
Expand Down Expand Up @@ -382,7 +468,7 @@ private static final class SizeDeterminerLayoutListener
@Override
public boolean onPreDraw() {
if (Log.isLoggable(TAG, Log.VERBOSE)) {
Log.v(TAG, "OnGlobalLayoutListener called listener=" + this);
Log.v(TAG, "OnGlobalLayoutListener called attachStateListener=" + this);
}
SizeDeterminer sizeDeterminer = sizeDeterminerRef.get();
if (sizeDeterminer != null) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
import com.bumptech.glide.request.Request;
import com.bumptech.glide.request.RequestOptions;
import com.bumptech.glide.request.target.Target;
import com.bumptech.glide.request.target.ViewTarget;
import com.bumptech.glide.tests.BackgroundUtil.BackgroundTester;
import com.bumptech.glide.tests.TearDownGlide;
import org.junit.Before;
Expand Down Expand Up @@ -113,7 +114,7 @@ public void runTest() {

private RequestBuilder<Object> getNullModelRequest() {
when(glideContext.buildImageViewTarget(isA(ImageView.class), isA(Class.class)))
.thenReturn(mock(Target.class));
.thenReturn(mock(ViewTarget.class));
when(glideContext.getDefaultRequestOptions()).thenReturn(new RequestOptions());
when(requestManager.getDefaultRequestOptions())
.thenReturn(new RequestOptions());
Expand Down
Loading

0 comments on commit d0fd967

Please sign in to comment.