bumptech / glide

An image loading and caching library for Android focused on smooth scrolling
https://bumptech.github.io/glide/
Other
34.7k stars 6.13k forks source link

Gif Bug? #5179

Closed Mooop closed 5 months ago

Mooop commented 1 year ago

Using Glide to load GIFs, when my application switches from the foreground to the background and repeats this operation several times, the LifecycleListener of the CustomTarget no longer receives callbacks.

Google Pixel4A, Android 13,API 33 Glide version: 4.15.1. Context:Fragment/Activity

sample code:

public class ATarget extends CustomTarget {

final String TAG = ATarget.class.getSimpleName();

private final TextView view;

private final SpannableStringBuilder builder;
private final int start;
private final int end;

private GifDrawable gif;
private Callback callback = new Callback() {
    @Override
    public void invalidateDrawable(@NonNull Drawable who) {
        view.invalidate();
    }

    @Override
    public void scheduleDrawable(@NonNull Drawable who, @NonNull Runnable what,
        long when) {
        view.postDelayed(what, when);
    }

    @Override
    public void unscheduleDrawable(@NonNull Drawable who, @NonNull Runnable what) {
        view.removeCallbacks(what);
    }
};

/**
 * Constructor that defaults {@code waitForLayout} to {@code false}.
 *
 * @param view
 */
public ATarget(@NonNull TextView view, SpannableStringBuilder builder, int start, int end) {
    this.view = view;
    this.builder = builder;
    this.start = start;
    this.end = end;
}

@Override
public void onResourceReady(@NonNull GifDrawable resource,
    @Nullable Transition<? super GifDrawable> transition) {
    resource.setBounds(0, 0, resource.getIntrinsicWidth(), resource.getIntrinsicHeight());

    resource.setCallback(callback);

    resource.setLoopCount(GifDrawable.LOOP_FOREVER);
    resource.start();
    gif = resource;

    builder.setSpan(new CustomImageSpan(resource, ImageSpan.ALIGN_BOTTOM), start, end,
        Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);

    view.setText(builder);
}

@Override
public void onLoadCleared(@Nullable Drawable placeholder) {
    if (null != gif) {
        gif.stop();
    }
}

@Override
public void onDestroy() {
    super.onDestroy();
    if (null != gif) {
        gif.setCallback(null);
        callback = null;
    }
}

@Override
public void onStart() {
    super.onStart();
    if (null != gif && !gif.isRunning()) {
        gif.start();
    }
}

@Override
public void onStop() {
    super.onStop();
    if (null != gif) {
        gif.stop();
    }
}

static class CustomImageSpan extends ImageSpan {

    public CustomImageSpan(@NonNull Drawable drawable, int verticalAlignment) {
        super(drawable, verticalAlignment);
    }

    @Override
    public Drawable getDrawable() {
        return super.getDrawable();
    }

    public void draw(Canvas canvas, CharSequence text, int start, int end, float x,
        int top, int y, int bottom, Paint paint) {
        Drawable drawable = getDrawable();

        Paint.FontMetricsInt fontMetricsInt = paint.getFontMetricsInt();
        int transY = y + fontMetricsInt.descent / 2 - drawable.getBounds().bottom;
        canvas.save();
        canvas.translate(x, transY);
        drawable.draw(canvas);
        canvas.restore();
    }
}

}

Mooop commented 1 year ago

Resolve the issue by implementing the LifecycleObserver interface to receive callbacks.

sample code:

public class ATarget extends CustomTarget implements LifecycleObserver {

final String TAG = ATarget.class.getSimpleName();

private final TextView view;

private final SpannableStringBuilder builder;
private final int start;
private final int end;
private final CompositeDisposable disposable = new CompositeDisposable();
private GifDrawable gif;
private Callback callback = new Callback() {
    @Override
    public void invalidateDrawable(@NonNull Drawable who) {
        view.invalidate();
    }

    @Override
    public void scheduleDrawable(@NonNull Drawable who, @NonNull Runnable what,
        long when) {
        view.postDelayed(what, when);
    }

    @Override
    public void unscheduleDrawable(@NonNull Drawable who, @NonNull Runnable what) {
        view.removeCallbacks(what);
    }
};

/**
 * Constructor that defaults {@code waitForLayout} to {@code false}.
 *
 * @param view
 */
public ATarget(@NonNull TextView view, SpannableStringBuilder builder, int start, int end) {
    this.view = view;
    this.builder = builder;
    this.start = start;
    this.end = end;
    if (view.getContext() instanceof AppCompatActivity) {
        AppCompatActivity compatActivity = (AppCompatActivity) view.getContext();
        compatActivity.getLifecycle().addObserver(this);
    }
}

@Override
public void onResourceReady(@NonNull GifDrawable resource,
    @Nullable Transition<? super GifDrawable> transition) {
    resource.setBounds(0, 0, resource.getIntrinsicWidth(), resource.getIntrinsicHeight());

    resource.setCallback(callback);

    resource.setLoopCount(GifDrawable.LOOP_FOREVER);
    resource.start();
    gif = resource;

    builder.setSpan(new CustomImageSpan(resource, ImageSpan.ALIGN_BOTTOM), start, end,
        Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);

    view.setText(builder);
}

@OnLifecycleEvent(Event.ON_RESUME)
void activityOnResume() {
    if (null != gif && !gif.isRunning()) {
        gif.start();
        Log.d(TAG, "activityOnResume");
    }
}

@OnLifecycleEvent(Event.ON_STOP)
void activityOnStop() {
    if (null != gif) {
        gif.stop();
        Log.d(TAG, "activityOnStop");
    }
}

@OnLifecycleEvent(Event.ON_DESTROY)
void activityOnDestroy() {
    if (null != gif) {
        gif.stop();
        gif.setCallback(null);
        callback = null;
    }
}

@Override
public void onLoadCleared(@Nullable Drawable placeholder) {
    Log.e(TAG,"onLoadCleared");
    if (null != gif) {
        gif.stop();
    }
}

@Override
public void onDestroy() {
    super.onDestroy();
}

@Override
public void onStart() {
    super.onStart();
}

@Override
public void onStop() {
    super.onStop();
}

static class CustomImageSpan extends ImageSpan {

    public CustomImageSpan(@NonNull Drawable drawable, int verticalAlignment) {
        super(drawable, verticalAlignment);
    }

    @Override
    public Drawable getDrawable() {
        return super.getDrawable();
    }

    public void draw(Canvas canvas, CharSequence text, int start, int end, float x,
        int top, int y, int bottom, Paint paint) {
        Drawable drawable = getDrawable();

        Paint.FontMetricsInt fontMetricsInt = paint.getFontMetricsInt();
        int transY = y + fontMetricsInt.descent / 2 - drawable.getBounds().bottom;
        canvas.save();
        canvas.translate(x, transY);
        drawable.draw(canvas);
        canvas.restore();
    }
}

}

Mooop commented 1 year ago

Are there any other better methods to solve the problem?