Skip to content

Commit

Permalink
Allow recursively pausing/resuming requests in a given context heirar…
Browse files Browse the repository at this point in the history
…chy.
  • Loading branch information
paulsowden authored and sjudd committed Mar 7, 2015
1 parent d520efb commit 5210ba8
Show file tree
Hide file tree
Showing 9 changed files with 307 additions and 27 deletions.
Expand Up @@ -46,6 +46,7 @@
import com.bumptech.glide.load.resource.gifbitmap.GifBitmapWrapper;
import com.bumptech.glide.load.resource.transcode.ResourceTranscoder;
import com.bumptech.glide.manager.Lifecycle;
import com.bumptech.glide.manager.RequestManagerTreeNode;
import com.bumptech.glide.request.Request;
import com.bumptech.glide.request.RequestListener;
import com.bumptech.glide.request.animation.GlideAnimation;
Expand Down Expand Up @@ -137,7 +138,8 @@ public Future<?> answer(InvocationOnMock invocation) throws Throwable {

Glide.get(getContext()).register(GlideUrl.class, InputStream.class, mockUrlLoaderFactory);
Lifecycle lifecycle = mock(Lifecycle.class);
requestManager = new RequestManager(getContext(), lifecycle);
RequestManagerTreeNode treeNode = mock(RequestManagerTreeNode.class);
requestManager = new RequestManager(getContext(), lifecycle, treeNode);
requestManager.resumeRequests();
}

Expand Down
Expand Up @@ -24,6 +24,7 @@
import com.bumptech.glide.manager.ConnectivityMonitor.ConnectivityListener;
import com.bumptech.glide.manager.ConnectivityMonitorFactory;
import com.bumptech.glide.manager.Lifecycle;
import com.bumptech.glide.manager.RequestManagerTreeNode;
import com.bumptech.glide.manager.RequestTracker;
import com.bumptech.glide.tests.BackgroundUtil;
import com.bumptech.glide.tests.GlideShadowLooper;
Expand All @@ -50,6 +51,7 @@ public class RequestManagerTest {
private ConnectivityListener connectivityListener;
private RequestManager.DefaultOptions options;
private Lifecycle lifecycle = mock(Lifecycle.class);
private RequestManagerTreeNode treeNode = mock(RequestManagerTreeNode.class);

@Before
public void setUp() {
Expand All @@ -64,7 +66,8 @@ public ConnectivityMonitor answer(InvocationOnMock invocation) throws Throwable
}
});
requestTracker = mock(RequestTracker.class);
manager = new RequestManager(Robolectric.application, lifecycle, requestTracker, factory);
manager =
new RequestManager(Robolectric.application, lifecycle, treeNode, requestTracker, factory);
options = mock(RequestManager.DefaultOptions.class);
manager.setDefaultOptions(options);
}
Expand Down
Expand Up @@ -65,7 +65,7 @@ public void testCreatesNewFragmentIfNoneExists() {
harness.doGet();

Robolectric.shadowOf(Looper.getMainLooper()).runToEndOfTasks();
assertTrue(harness.hasFragmentWithTag(RequestManagerRetriever.TAG));
assertTrue(harness.hasFragmentWithTag(RequestManagerRetriever.FRAGMENT_TAG));
}
}

Expand All @@ -81,7 +81,7 @@ public void testReturnsExistingRequestManagerIfExists() {
for (RetrieverHarness harness : harnesses) {
RequestManager requestManager = mock(RequestManager.class);

harness.addFragmentWithTag(RequestManagerRetriever.TAG, requestManager);
harness.addFragmentWithTag(RequestManagerRetriever.FRAGMENT_TAG, requestManager);

assertEquals(requestManager, harness.doGet());
}
Expand All @@ -90,7 +90,7 @@ public void testReturnsExistingRequestManagerIfExists() {
@Test
public void testReturnsNewRequestManagerIfFragmentExistsButHasNoRequestManager() {
for (RetrieverHarness harness : harnesses) {
harness.addFragmentWithTag(RequestManagerRetriever.TAG, null);
harness.addFragmentWithTag(RequestManagerRetriever.FRAGMENT_TAG, null);

assertNotNull(harness.doGet());
}
Expand All @@ -99,7 +99,7 @@ public void testReturnsNewRequestManagerIfFragmentExistsButHasNoRequestManager()
@Test
public void testSavesNewRequestManagerToFragmentIfCreatesRequestManagerForExistingFragment() {
for (RetrieverHarness harness : harnesses) {
harness.addFragmentWithTag(RequestManagerRetriever.TAG, null);
harness.addFragmentWithTag(RequestManagerRetriever.FRAGMENT_TAG, null);
RequestManager first = harness.doGet();
RequestManager second = harness.doGet();

Expand All @@ -109,7 +109,7 @@ public void testSavesNewRequestManagerToFragmentIfCreatesRequestManagerForExisti

@Test
public void testHasValidTag() {
assertEquals(RequestManagerRetriever.class.getPackage().getName(), RequestManagerRetriever.TAG);
assertEquals(RequestManagerRetriever.class.getPackage().getName(), RequestManagerRetriever.FRAGMENT_TAG);
}

@Test
Expand Down Expand Up @@ -396,7 +396,9 @@ public RequestManager doGet() {

@Override
public boolean hasFragmentWithTag(String tag) {
return controller.get().getFragmentManager().findFragmentByTag(RequestManagerRetriever.TAG) != null;
return null != controller.get()
.getFragmentManager()
.findFragmentByTag(RequestManagerRetriever.FRAGMENT_TAG);
}

@Override
Expand All @@ -405,7 +407,7 @@ public void addFragmentWithTag(String tag, RequestManager requestManager) {
fragment.setRequestManager(requestManager);
controller.get().getFragmentManager()
.beginTransaction()
.add(fragment, RequestManagerRetriever.TAG)
.add(fragment, RequestManagerRetriever.FRAGMENT_TAG)
.commitAllowingStateLoss();
controller.get().getFragmentManager().executePendingTransactions();
}
Expand Down Expand Up @@ -440,7 +442,7 @@ public RequestManager doGet() {

@Override
public boolean hasFragmentWithTag(String tag) {
return controller.get().getSupportFragmentManager().findFragmentByTag(RequestManagerRetriever.TAG)
return controller.get().getSupportFragmentManager().findFragmentByTag(RequestManagerRetriever.FRAGMENT_TAG)
!= null;
}

Expand All @@ -450,7 +452,7 @@ public void addFragmentWithTag(String tag, RequestManager manager) {
fragment.setRequestManager(manager);
controller.get().getSupportFragmentManager()
.beginTransaction()
.add(fragment, RequestManagerRetriever.TAG)
.add(fragment, RequestManagerRetriever.FRAGMENT_TAG)
.commitAllowingStateLoss();
controller.get().getSupportFragmentManager().executePendingTransactions();
}
Expand Down
45 changes: 41 additions & 4 deletions library/src/main/java/com/bumptech/glide/RequestManager.java
Expand Up @@ -17,6 +17,7 @@
import com.bumptech.glide.manager.ConnectivityMonitorFactory;
import com.bumptech.glide.manager.Lifecycle;
import com.bumptech.glide.manager.LifecycleListener;
import com.bumptech.glide.manager.RequestManagerTreeNode;
import com.bumptech.glide.manager.RequestTracker;
import com.bumptech.glide.signature.ApplicationVersionSignature;
import com.bumptech.glide.signature.MediaStoreSignature;
Expand All @@ -42,19 +43,21 @@
public class RequestManager implements LifecycleListener {
private final Context context;
private final Lifecycle lifecycle;
private final RequestManagerTreeNode treeNode;
private final RequestTracker requestTracker;
private final Glide glide;
private final OptionsApplier optionsApplier;
private DefaultOptions options;

public RequestManager(Context context, Lifecycle lifecycle) {
this(context, lifecycle, new RequestTracker(), new ConnectivityMonitorFactory());
public RequestManager(Context context, Lifecycle lifecycle, RequestManagerTreeNode treeNode) {
this(context, lifecycle, treeNode, new RequestTracker(), new ConnectivityMonitorFactory());
}

RequestManager(Context context, final Lifecycle lifecycle, RequestTracker requestTracker,
ConnectivityMonitorFactory factory) {
RequestManager(Context context, final Lifecycle lifecycle, RequestManagerTreeNode treeNode,
RequestTracker requestTracker, ConnectivityMonitorFactory factory) {
this.context = context.getApplicationContext();
this.lifecycle = lifecycle;
this.treeNode = treeNode;
this.requestTracker = requestTracker;
this.glide = Glide.get(context);
this.optionsApplier = new OptionsApplier();
Expand Down Expand Up @@ -145,6 +148,27 @@ public void pauseRequests() {
requestTracker.pauseRequests();
}

/**
* Performs {@link #pauseRequests()} recursively for all managers that are contextually descendant
* to this manager based on the Activity/Fragment hierarchy:
*
* <ul>
* <li>When pausing on an Activity all attached fragments will also get paused.
* <li>When pausing on an attached Fragment all descendant fragments will also get paused.
* <li>When pausing on a detached Fragment or the application context only the current RequestManager is paused.
* </ul>
*
* <p>Note, on pre-Jelly Bean MR1 calling pause on a Fragment will not cause child fragments to pause, in this
* case either call pause on the Activity or use a support Fragment.
*/
public void pauseRequestsRecursive() {
Util.assertMainThread();
pauseRequests();
for (RequestManager requestManager : treeNode.getDescendants()) {
requestManager.pauseRequests();
}
}

/**
* Restarts any loads that have not yet completed.
*
Expand All @@ -156,6 +180,19 @@ public void resumeRequests() {
requestTracker.resumeRequests();
}

/**
* Performs {@link #resumeRequests()} recursively for all managers that are contextually descendant
* to this manager based on the Activity/Fragment hierarchy. The hierarchical semantics are identical as for
* {@link #pauseRequestsRecursive()}.
*/
public void resumeRequestsRecursive() {
Util.assertMainThread();
resumeRequests();
for (RequestManager requestManager : treeNode.getDescendants()) {
requestManager.resumeRequests();
}
}

/**
* Lifecycle callback that registers for connectivity events (if the android.permission.ACCESS_NETWORK_STATE
* permission is present) and restarts failed or paused requests.
Expand Down
@@ -0,0 +1,16 @@
package com.bumptech.glide.manager;

import com.bumptech.glide.RequestManager;

import java.util.Collections;
import java.util.Set;

/**
* A {@link RequestManagerTreeNode} that returns no relatives.
*/
final class EmptyRequestManagerTreeNode implements RequestManagerTreeNode {
@Override
public Set<RequestManager> getDescendants() {
return Collections.emptySet();
}
}
Expand Up @@ -2,11 +2,16 @@

import android.annotation.SuppressLint;
import android.annotation.TargetApi;
import android.app.Activity;
import android.app.Fragment;
import android.os.Build;

import com.bumptech.glide.RequestManager;

import java.util.Collections;
import java.util.HashSet;
import java.util.Set;

/**
* A view-less {@link android.app.Fragment} used to safely store an {@link com.bumptech.glide.RequestManager} that
* can be used to start, stop and manage Glide requests started for targets the fragment or activity this fragment is a
Expand All @@ -19,7 +24,11 @@
@TargetApi(Build.VERSION_CODES.HONEYCOMB)
public class RequestManagerFragment extends Fragment {
private final ActivityFragmentLifecycle lifecycle;
private final RequestManagerTreeNode requestManagerTreeNode = new FragmentRequestManagerTreeNode();
private RequestManager requestManager;
private final HashSet<RequestManagerFragment> childRequestManagerFragments
= new HashSet<RequestManagerFragment>();
private RequestManagerFragment rootRequestManagerFragment;

public RequestManagerFragment() {
this(new ActivityFragmentLifecycle());
Expand Down Expand Up @@ -51,6 +60,76 @@ public RequestManager getRequestManager() {
return requestManager;
}

public RequestManagerTreeNode getRequestManagerTreeNode() {
return requestManagerTreeNode;
}

private void addChildRequestManagerFragment(RequestManagerFragment child) {
childRequestManagerFragments.add(child);
}

private void removeChildRequestManagerFragment(RequestManagerFragment child) {
childRequestManagerFragments.remove(child);
}

/**
* Returns the set of fragments that this RequestManagerFragment's parent is a parent to. (i.e. our parent is
* the fragment that we are annotating).
*/
@TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1)
public Set<RequestManagerFragment> getDescendantRequestManagerFragments() {
if (rootRequestManagerFragment == this) {
return Collections.unmodifiableSet(childRequestManagerFragments);
} else if (rootRequestManagerFragment == null || Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR1) {
// Pre JB MR1 doesn't allow us to get the parent fragment so we can't introspect hierarchy, so just
// return an empty set.
return Collections.emptySet();
} else {
HashSet<RequestManagerFragment> descendants = new HashSet<RequestManagerFragment>();
for (RequestManagerFragment fragment
: rootRequestManagerFragment.getDescendantRequestManagerFragments()) {
if (isDescendant(fragment.getParentFragment())) {
descendants.add(fragment);
}
}
return Collections.unmodifiableSet(descendants);
}
}

/**
* Returns true if the fragment is a descendant of our parent.
*/
@TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1)
private boolean isDescendant(Fragment fragment) {
Fragment root = this.getParentFragment();
while (fragment.getParentFragment() != null) {
if (fragment.getParentFragment() == root) {
return true;
}
fragment = fragment.getParentFragment();
}
return false;
}

@Override
public void onAttach(Activity activity) {
super.onAttach(activity);
rootRequestManagerFragment = RequestManagerRetriever.get()
.getRequestManagerFragment(getActivity().getFragmentManager());
if (rootRequestManagerFragment != this) {
rootRequestManagerFragment.addChildRequestManagerFragment(this);
}
}

@Override
public void onDetach() {
super.onDetach();
if (rootRequestManagerFragment != null) {
rootRequestManagerFragment.removeChildRequestManagerFragment(this);
rootRequestManagerFragment = null;
}
}

@Override
public void onStart() {
super.onStart();
Expand Down Expand Up @@ -86,4 +165,19 @@ public void onLowMemory() {
requestManager.onLowMemory();
}
}

private class FragmentRequestManagerTreeNode implements RequestManagerTreeNode {
@Override
public Set<RequestManager> getDescendants() {
Set<RequestManagerFragment> descendantFragments = getDescendantRequestManagerFragments();
HashSet<RequestManager> descendants =
new HashSet<RequestManager>(descendantFragments.size());
for (RequestManagerFragment fragment : descendantFragments) {
if (fragment.getRequestManager() != null) {
descendants.add(fragment.getRequestManager());
}
}
return descendants;
}
}
}

0 comments on commit 5210ba8

Please sign in to comment.