Skip to content

Commit e802964

Browse files
committedAug 2, 2022
Deprecate Glide fragments in favor of androidx Lifecycle.
1 parent 813ebd6 commit e802964

File tree

11 files changed

+751
-19
lines changed

11 files changed

+751
-19
lines changed
 

‎instrumentation/build.gradle

+2-1
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ tasks.whenTaskAdded { task ->
66
apply plugin: 'com.android.application'
77

88
dependencies {
9+
debugImplementation 'androidx.fragment:fragment-testing:1.5.0'
910
annotationProcessor project(":annotation:compiler")
1011
implementation project(":library")
1112

@@ -26,7 +27,7 @@ dependencies {
2627
}
2728

2829
android {
29-
compileSdk 30 as int
30+
compileSdk COMPILE_SDK_VERSION as int
3031

3132
defaultConfig {
3233
applicationId 'com.bumptech.glide.instrumentation'

‎instrumentation/src/androidTest/java/com/bumptech/glide/RequestManagerLifecycleTest.java

+489
Large diffs are not rendered by default.

‎instrumentation/src/main/AndroidManifest.xml

+5-3
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,12 @@
66
<application tools:ignore="MissingApplicationIcon">
77
<activity
88
android:name="com.bumptech.glide.test.GlideWithBeforeSuperOnCreateActivity"
9-
android:exported="false"/>
9+
android:exported="false" />
1010
<activity
1111
android:name="com.bumptech.glide.test.GlideWithAsDifferentSupertypesActivity"
12-
android:exported="false"
13-
/>
12+
android:exported="false" />
13+
<activity
14+
android:name="com.bumptech.glide.test.DefaultFragmentActivity"
15+
android:exported="false" />
1416
</application>
1517
</manifest>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
package com.bumptech.glide.test;
2+
3+
import android.os.Bundle;
4+
import androidx.annotation.Nullable;
5+
import androidx.fragment.app.FragmentActivity;
6+
import com.bumptech.glide.instrumentation.R;
7+
8+
public class DefaultFragmentActivity extends FragmentActivity {
9+
10+
@Override
11+
protected void onCreate(@Nullable Bundle savedInstanceState) {
12+
super.onCreate(savedInstanceState);
13+
setContentView(R.layout.default_fragment_activity);
14+
}
15+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
3+
android:id="@+id/container"
4+
android:layout_width="match_parent"
5+
android:layout_height="match_parent" />

‎library/src/main/java/com/bumptech/glide/Glide.java

+3
Original file line numberDiff line numberDiff line change
@@ -543,8 +543,11 @@ public static RequestManager with(@NonNull Context context) {
543543
*
544544
* @param activity The activity to use.
545545
* @return A RequestManager for the given activity that can be used to start a load.
546+
*
547+
* @deprecated TODO(judds): Figure out the end state and list it here.
546548
*/
547549
@NonNull
550+
@Deprecated
548551
public static RequestManager with(@NonNull Activity activity) {
549552
return getRetriever(activity).get(activity);
550553
}

‎library/src/main/java/com/bumptech/glide/GlideBuilder.java

+20
Original file line numberDiff line numberDiff line change
@@ -487,6 +487,20 @@ public GlideBuilder setImageDecoderEnabledForBitmaps(boolean isEnabled) {
487487
return this;
488488
}
489489

490+
/**
491+
* When given androidx Fragments and Activities, use {@link androidx.lifecycle.Lifecycle} to track
492+
* the Activity or Fragment lifecycle instead of adding custom {@link
493+
* com.bumptech.glide.manager.SupportRequestManagerFragment}s.
494+
*
495+
* <p>This flag is experimental and will be removed without notice in a future version.
496+
*/
497+
public GlideBuilder useLifecycleInsteadOfInjectingFragments(boolean isEnabled) {
498+
glideExperimentsBuilder.update(
499+
new UseLifecycleInsteadOfInjectingFragments(),
500+
isEnabled);
501+
return this;
502+
}
503+
490504
void setRequestManagerFactory(@Nullable RequestManagerFactory factory) {
491505
this.requestManagerFactory = factory;
492506
}
@@ -602,4 +616,10 @@ static final class EnableImageDecoderForBitmaps implements Experiment {}
602616
public static final class LogRequestOrigins implements Experiment {}
603617

604618
static final class EnableLazyGlideRegistry implements Experiment {}
619+
620+
/**
621+
* Use the androidx lifecycle instead of injecting custom fragments when using androidx fragments
622+
* and activities.
623+
*/
624+
public static final class UseLifecycleInsteadOfInjectingFragments implements Experiment {}
605625
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
package com.bumptech.glide.manager;
2+
3+
import androidx.annotation.NonNull;
4+
import androidx.lifecycle.Lifecycle.Event;
5+
import androidx.lifecycle.Lifecycle.State;
6+
import androidx.lifecycle.LifecycleObserver;
7+
import androidx.lifecycle.LifecycleOwner;
8+
import androidx.lifecycle.OnLifecycleEvent;
9+
import com.bumptech.glide.util.Util;
10+
import java.util.HashSet;
11+
import java.util.Set;
12+
13+
final class LifecycleLifecycle implements Lifecycle, LifecycleObserver {
14+
@NonNull
15+
private final Set<LifecycleListener> lifecycleListeners = new HashSet<LifecycleListener>();
16+
@NonNull
17+
private final androidx.lifecycle.Lifecycle lifecycle;
18+
19+
LifecycleLifecycle(androidx.lifecycle.Lifecycle lifecycle) {
20+
this.lifecycle = lifecycle;
21+
lifecycle.addObserver(this);
22+
}
23+
24+
@OnLifecycleEvent(Event.ON_START)
25+
public void onStart(@NonNull LifecycleOwner owner) {
26+
for (LifecycleListener lifecycleListener : Util.getSnapshot(lifecycleListeners)) {
27+
lifecycleListener.onStart();
28+
}
29+
}
30+
31+
@OnLifecycleEvent(Event.ON_STOP)
32+
public void onStop(@NonNull LifecycleOwner owner) {
33+
for (LifecycleListener lifecycleListener : Util.getSnapshot(lifecycleListeners)) {
34+
lifecycleListener.onStop();
35+
}
36+
}
37+
38+
@OnLifecycleEvent(Event.ON_DESTROY)
39+
public void onDestroy(@NonNull LifecycleOwner owner) {
40+
for (LifecycleListener lifecycleListener : Util.getSnapshot(lifecycleListeners)) {
41+
lifecycleListener.onDestroy();
42+
}
43+
owner.getLifecycle().removeObserver(this);
44+
}
45+
46+
@Override
47+
public void addListener(@NonNull LifecycleListener listener) {
48+
lifecycleListeners.add(listener);
49+
50+
if (lifecycle.getCurrentState() == State.DESTROYED) {
51+
listener.onDestroy();
52+
} else if (lifecycle.getCurrentState().isAtLeast(State.STARTED)) {
53+
listener.onStart();
54+
} else {
55+
listener.onStop();
56+
}
57+
}
58+
59+
@Override
60+
public void removeListener(@NonNull LifecycleListener listener) {
61+
lifecycleListeners.remove(listener);
62+
}
63+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
package com.bumptech.glide.manager;
2+
3+
import android.content.Context;
4+
import androidx.annotation.NonNull;
5+
import androidx.fragment.app.Fragment;
6+
import androidx.fragment.app.FragmentManager;
7+
import androidx.lifecycle.Lifecycle;
8+
import com.bumptech.glide.Glide;
9+
import com.bumptech.glide.RequestManager;
10+
import com.bumptech.glide.manager.RequestManagerRetriever.RequestManagerFactory;
11+
import com.bumptech.glide.util.Synthetic;
12+
import com.bumptech.glide.util.Util;
13+
import java.util.HashMap;
14+
import java.util.HashSet;
15+
import java.util.List;
16+
import java.util.Map;
17+
import java.util.Set;
18+
19+
final class LifecycleRequestManagerRetriever {
20+
@Synthetic
21+
final Map<Lifecycle, RequestManager> lifecycleToRequestManager = new HashMap<>();
22+
@NonNull
23+
private final RequestManagerFactory factory;
24+
25+
LifecycleRequestManagerRetriever(@NonNull RequestManagerFactory factory) {
26+
this.factory = factory;
27+
}
28+
29+
RequestManager getOnly(Lifecycle lifecycle) {
30+
Util.assertMainThread();
31+
return lifecycleToRequestManager.get(lifecycle);
32+
}
33+
34+
RequestManager getOrCreate(
35+
Context context,
36+
Glide glide,
37+
final Lifecycle lifecycle,
38+
FragmentManager childFragmentManager,
39+
boolean isParentVisible) {
40+
Util.assertMainThread();
41+
RequestManager result = getOnly(lifecycle);
42+
if (result == null) {
43+
LifecycleLifecycle glideLifecycle = new LifecycleLifecycle(lifecycle);
44+
result =
45+
factory.build(
46+
glide,
47+
glideLifecycle,
48+
new SupportRequestManagerTreeNode(childFragmentManager),
49+
context);
50+
lifecycleToRequestManager.put(lifecycle, result);
51+
glideLifecycle.addListener(new LifecycleListener() {
52+
@Override public void onStart() {}
53+
@Override public void onStop() {}
54+
@Override public void onDestroy() {
55+
lifecycleToRequestManager.remove(lifecycle);
56+
}
57+
});
58+
// This is a bit of hack, we're going to start the RequestManager, but not the
59+
// corresponding Lifecycle. It's safe to start the RequestManager, but starting the
60+
// Lifecycle might trigger memory leaks. See b/154405040
61+
if (isParentVisible) {
62+
result.onStart();
63+
}
64+
}
65+
return result;
66+
}
67+
68+
private final class SupportRequestManagerTreeNode implements RequestManagerTreeNode {
69+
private final FragmentManager childFragmentManager;
70+
71+
SupportRequestManagerTreeNode(FragmentManager childFragmentManager) {
72+
this.childFragmentManager = childFragmentManager;
73+
}
74+
75+
@NonNull
76+
@Override
77+
public Set<RequestManager> getDescendants() {
78+
Set<RequestManager> result = new HashSet<>();
79+
getChildFragmentsRecursive(childFragmentManager, result);
80+
return result;
81+
}
82+
83+
private void getChildFragmentsRecursive(
84+
FragmentManager fragmentManager, Set<RequestManager> requestManagers) {
85+
List<Fragment> children = fragmentManager.getFragments();
86+
for (int i = 0, size = children.size(); i < size; i++) {
87+
Fragment child = children.get(i);
88+
getChildFragmentsRecursive(child.getChildFragmentManager(), requestManagers);
89+
RequestManager fromChild = getOnly(child.getLifecycle());
90+
if (fromChild != null) {
91+
requestManagers.add(fromChild);
92+
}
93+
}
94+
}
95+
}
96+
}

‎library/src/main/java/com/bumptech/glide/manager/RequestManagerFragment.java

+1
Original file line numberDiff line numberDiff line change
@@ -241,4 +241,5 @@ public String toString() {
241241
return super.toString() + "{fragment=" + RequestManagerFragment.this + "}";
242242
}
243243
}
244+
244245
}

‎library/src/main/java/com/bumptech/glide/manager/RequestManagerRetriever.java

+52-15
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
import androidx.fragment.app.FragmentManager;
2424
import androidx.fragment.app.FragmentTransaction;
2525
import com.bumptech.glide.Glide;
26+
import com.bumptech.glide.GlideBuilder;
2627
import com.bumptech.glide.GlideBuilder.WaitForFramesAfterTrimMemory;
2728
import com.bumptech.glide.GlideExperiments;
2829
import com.bumptech.glide.RequestManager;
@@ -70,6 +71,7 @@ public class RequestManagerRetriever implements Handler.Callback {
7071
private final Handler handler;
7172

7273
private final RequestManagerFactory factory;
74+
private final GlideExperiments experiments;
7375

7476
// Objects used to find Fragments and Activities containing views.
7577
private final ArrayMap<View, Fragment> tempViewToSupportFragment = new ArrayMap<>();
@@ -79,12 +81,14 @@ public class RequestManagerRetriever implements Handler.Callback {
7981
// Fragment/Activity extraction logic that already exists here. It's gross, but less likely to
8082
// break.
8183
private final FrameWaiter frameWaiter;
84+
private final LifecycleRequestManagerRetriever lifecycleRequestManagerRetriever;
8285

8386
public RequestManagerRetriever(
8487
@Nullable RequestManagerFactory factory, GlideExperiments experiments) {
8588
this.factory = factory != null ? factory : DEFAULT_FACTORY;
89+
this.experiments = experiments;
8690
handler = new Handler(Looper.getMainLooper(), this /* Callback */);
87-
91+
lifecycleRequestManagerRetriever = new LifecycleRequestManagerRetriever(this.factory);
8892
frameWaiter = buildFrameWaiter(experiments);
8993
}
9094

@@ -149,34 +153,60 @@ public RequestManager get(@NonNull Context context) {
149153
public RequestManager get(@NonNull FragmentActivity activity) {
150154
if (Util.isOnBackgroundThread()) {
151155
return get(activity.getApplicationContext());
156+
}
157+
assertNotDestroyed(activity);
158+
frameWaiter.registerSelf(activity);
159+
FragmentManager fm = activity.getSupportFragmentManager();
160+
boolean isActivityVisible = isActivityVisible(activity);
161+
if (useLifecycleInsteadOfInjectingFragments()) {
162+
Context context = activity.getApplicationContext();
163+
Glide glide = Glide.get(context);
164+
return lifecycleRequestManagerRetriever.getOrCreate(
165+
context,
166+
glide,
167+
activity.getLifecycle(),
168+
activity.getSupportFragmentManager(),
169+
isActivityVisible);
152170
} else {
153-
assertNotDestroyed(activity);
154-
frameWaiter.registerSelf(activity);
155-
FragmentManager fm = activity.getSupportFragmentManager();
156-
return supportFragmentGet(activity, fm, /*parentHint=*/ null, isActivityVisible(activity));
171+
return supportFragmentGet(activity, fm, /*parentHint=*/ null, isActivityVisible);
157172
}
158173
}
159174

175+
private boolean useLifecycleInsteadOfInjectingFragments() {
176+
return experiments.isEnabled(GlideBuilder.UseLifecycleInsteadOfInjectingFragments.class);
177+
}
178+
160179
@NonNull
161180
public RequestManager get(@NonNull Fragment fragment) {
162181
Preconditions.checkNotNull(
163182
fragment.getContext(),
164183
"You cannot start a load on a fragment before it is attached or after it is destroyed");
165184
if (Util.isOnBackgroundThread()) {
166185
return get(fragment.getContext().getApplicationContext());
186+
}
187+
// In some unusual cases, it's possible to have a Fragment not hosted by an activity. There's
188+
// not all that much we can do here. Most apps will be started with a standard activity. If
189+
// we manage not to register the first frame waiter for a while, the consequences are not
190+
// catastrophic, we'll just use some extra memory.
191+
if (fragment.getActivity() != null) {
192+
frameWaiter.registerSelf(fragment.getActivity());
193+
}
194+
FragmentManager fm = fragment.getChildFragmentManager();
195+
Context context = fragment.getContext();
196+
if (useLifecycleInsteadOfInjectingFragments()) {
197+
Glide glide = Glide.get(context.getApplicationContext());
198+
return lifecycleRequestManagerRetriever.getOrCreate(
199+
context, glide, fragment.getLifecycle(), fm, fragment.isVisible());
167200
} else {
168-
// In some unusual cases, it's possible to have a Fragment not hosted by an activity. There's
169-
// not all that much we can do here. Most apps will be started with a standard activity. If
170-
// we manage not to register the first frame waiter for a while, the consequences are not
171-
// catastrophic, we'll just use some extra memory.
172-
if (fragment.getActivity() != null) {
173-
frameWaiter.registerSelf(fragment.getActivity());
174-
}
175-
FragmentManager fm = fragment.getChildFragmentManager();
176-
return supportFragmentGet(fragment.getContext(), fm, fragment, fragment.isVisible());
201+
return supportFragmentGet(context, fm, fragment, fragment.isVisible());
177202
}
178203
}
179204

205+
/**
206+
* @deprecated Use androidx Activities instead (ie {@link FragmentActivity}, or
207+
* {@link androidx.appcompat.app.AppCompatActivity}).
208+
*/
209+
@Deprecated
180210
@SuppressWarnings("deprecation")
181211
@NonNull
182212
public RequestManager get(@NonNull Activity activity) {
@@ -354,6 +384,9 @@ private static void assertNotDestroyed(@NonNull Activity activity) {
354384
}
355385
}
356386

387+
/**
388+
* @deprecated Use androidx fragments instead: {@link Fragment}.
389+
*/
357390
@SuppressWarnings("deprecation")
358391
@Deprecated
359392
@NonNull
@@ -378,6 +411,10 @@ public RequestManager get(@NonNull android.app.Fragment fragment) {
378411
}
379412
}
380413

414+
/**
415+
* @deprecated Use androidx activities like {@link FragmentActivity} or
416+
* {@link androidx.appcompat.app.AppCompatActivity} instead.
417+
*/
381418
@SuppressWarnings("deprecation")
382419
@Deprecated
383420
@NonNull
@@ -407,7 +444,7 @@ private RequestManagerFragment getRequestManagerFragment(
407444
return current;
408445
}
409446

410-
@SuppressWarnings({"deprecation", "DeprecatedIsStillUsed"})
447+
@SuppressWarnings("deprecation")
411448
@Deprecated
412449
@NonNull
413450
private RequestManager fragmentGet(

0 commit comments

Comments
 (0)
Please sign in to comment.