Issue 1:
Glide Version/Integration library (if any): 3.5.2

Device/Android Version: N/A

Issue details/Repro steps: I was trying to display an image with an SVG placeholder while it's loading.

Before: So far I was using a hand-crafted Weak HashMap cache for Pictures rendered from SVGs, and wrapped that in a PictureDrawable when I needed a .placeholder. Sadly this has a drawback of requiring layerType="software" on the ImageView, but I was able to use the .placeholder(Drawable) Glide API.

After: Yesterday while looking for a memory leak and investigating slow scrolling I realized that the layerType="software" on a hardware accelerated phone is really bad, it renders a drawing cache to create the display list, duplicating the Bitmap in memory. So I changed my SVG approach: the still loading from @RawRes, but I'm decoding them as Bitmap of a given size. This allows for RESULT caching. This made the list scroll crazy fast and no GC anywhere, because of BitmapPool, yay!

Comments/Questions

Note that I had to transition to the .thumbnail API. (#262)

Sadly, the .thumbnail drives what the outer request is allowed to be, because the thumb is more special a constrained. (#107)

Is there any difference between two versions other than the .crossFade VS .animate and that weird .decoder construction?

What do you think in general about the two approaches?

Can you think of a simpler way of doing OPTION 2, mostly because of crossfade?

How do I know which outer .xxx() calls are passed to the thumbnail request?

Will I ever see the .placeholder?

Glide load line:

private void loadImage(ImageView image, @RawRes int typeID, String imagePath) {
Context context = image.getContext();
BitmapPool pool = Glide.get(context).getBitmapPool();

// OPTION 1 Bitmap
Glide
.with(image.getContext())
.load(imagePath)
.asBitmap()
.animate(android.R.anim.fade_in)
.placeholder(R.drawable.image_loading)
.error(R.drawable.image_error)
.thumbnail(Glide
.with(context)
.load(typeID)
.asBitmap()
.imageDecoder(new SvgBitmapDecoder(pool)) // implements ResourceDecoder<InputStream, Bitmap>
)
.into(image)
;

// OPTION 2 GlideDrawable
Glide
.with(image.getContext())
.load(imagePath)
.crossFade()
.placeholder(R.drawable.image_loading)
.error(R.drawable.image_error)
.thumbnail(Glide
.with(context)
.load(typeID)
.decoder(new GifBitmapWrapperResourceDecoder(
new ImageVideoBitmapDecoder(
new SvgBitmapDecoder(pool),
null /*fileDescriptorDecoder*/
),
// just to satisfy GifBitmapWrapperResourceDecoder.getId() which throws NPE otherwise
new GifResourceDecoder(context, pool),
pool
)
)
)
.into(image)
;
}

Appendix SvgBitmapDecoder:

import com.caverock.androidsvg.*;

public class SvgBitmapDecoder implements ResourceDecoder<InputStream, Bitmap> {
private final BitmapPool bitmapPool;

public SvgBitmapDecoder(Context context) {
this(Glide.get(context).getBitmapPool());
}

public SvgBitmapDecoder(BitmapPool bitmapPool) {
this.bitmapPool = bitmapPool;
}

public Resource<Bitmap> decode(InputStream source, int width, int height) throws IOException {
try {
SVG svg = SVG.getFromInputStream(source);
Bitmap bitmap = findBitmap(width, height);
Canvas canvas = new Canvas(bitmap);
svg.renderToCanvas(canvas);
return BitmapResource.obtain(bitmap, bitmapPool);
} catch (SVGParseException ex) {
throw new IOException("Cannot load SVG from stream", ex);
}
}

private Bitmap findBitmap(int width, int height) {
Bitmap bitmap = bitmapPool.get(width, height, Config.ARGB_8888);
if (bitmap == null) {
bitmap = Bitmap.createBitmap(width, height, Config.ARGB_8888);
}
return bitmap;
}

@Override
public String getId() {
return "";
}
}

Issue 2:
Glide Version: 4.2.0

Integration libraries:

compile "com.github.bumptech.glide:glide:$versions.glide"
compile "com.github.bumptech.glide:annotations:$versions.glide"
annotationProcessor "com.github.bumptech.glide:compiler:$versions.glide"
compile "com.github.bumptech.glide:okhttp3-integration:$versions.glide"

Device/Android Version:

Can be reproduced on a 4.4 device and 5.0 device.

Cannot be reproduced on more recent devices.

Issue details / Repro steps / Use case background:

Images cannot be loaded, with an exception about StrictMode:

11-07 11:39:42.603 18267-18409/? W/LessApplication/LessGlideModule: lambda$applyOptions$0$LessGlideModule Unable to get or display image glide-disk-cache-thread-1
android.os.StrictMode$StrictModeViolation: policy=68 violation=4
at android.os.StrictMode.executeDeathPenalty(StrictMode.java:1380)
at android.os.StrictMode.access$1300(StrictMode.java:116)
at android.os.StrictMode$AndroidBlockGuardPolicy.handleViolation(StrictMode.java:1373)
at android.os.StrictMode$AndroidBlockGuardPolicy.handleViolationWithTimingAttempt(StrictMode.java:1200)
at android.os.StrictMode$AndroidBlockGuardPolicy.startHandlingViolationException(StrictMode.java:1170)
at android.os.StrictMode$AndroidBlockGuardPolicy.onNetwork(StrictMode.java:1155)
at com.google.android.gms.org.conscrypt.Platform.blockGuardOnNetwork(:com.google.android.gms@11745036)
at com.google.android.gms.org.conscrypt.ConscryptFileDescriptorSocket$SSLInputStream.read(:com.google.android.gms@11745036:2)
at e.n$2.a(Okio.java:139)
at e.a$2.a(AsyncTimeout.java:237)
at e.p.a(RealBufferedSource.java:46)
at d.a.d.a$a.a(Http1Codec.java:352)
at d.a.d.a$e.a(Http1Codec.java:396)
at d.a.c.b(Util.java:175)
at d.a.c.a(Util.java:157)
at d.a.d.a$e.close(Http1Codec.java:413)
at e.p.close(RealBufferedSource.java:455)
at e.p$1.close(RealBufferedSource.java:443)
at java.io.FilterInputStream.close(FilterInputStream.java:64)
at com.bumptech.glide.integration.okhttp3.b.a(OkHttpStreamFetcher.java:92)
at com.bumptech.glide.c.c.q$a.a(MultiModelLoader.java:106)
at com.bumptech.glide.c.b.w.b(SourceGenerator.java:87)
at com.bumptech.glide.c.b.w.a(SourceGenerator.java:45)
at com.bumptech.glide.c.b.f.k(DecodeJob.java:293)
at com.bumptech.glide.c.b.f.i(DecodeJob.java:263)
at com.bumptech.glide.c.b.f.run(DecodeJob.java:230)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1112)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:587)
at java.lang.Thread.run(Thread.java:841)
at com.bumptech.glide.c.b.c.a$a$1.run(GlideExecutor.java:386)

The glide-disk-cache-thread-1 part is the name of the thread.

We only reproduce this issue on "release" builds.

Glide load line / GlideModule (if any) / list Adapter code (if any):

@GlideModule
@Keep
public class LessGlideModule extends AppGlideModule {

private static final int DISK_CACHE_SIZE_B = 1024 * 1024 * 100; // 100 MB

@Override
public void applyOptions(Context context, GlideBuilder builder) {
// Memory cache size: equivalent of one screen
MemorySizeCalculator calculator = new MemorySizeCalculator.Builder(context)
.setMemoryCacheScreens(1)
.build();
builder.setMemoryCache(new LruResourceCache(calculator.getMemoryCacheSize()));

// Disk cache size
builder.setDiskCache(new ExternalCacheDiskCacheFactory(context, DISK_CACHE_SIZE_B));

// Uncaught throwable strategy
GlideExecutor.UncaughtThrowableStrategy uncaughtThrowableStrategy = throwable -> Log.w(throwable, "Unable to get or display image " + Thread.currentThread().getName());
builder.setDiskCacheExecutor(GlideExecutor.newDiskCacheExecutor(uncaughtThrowableStrategy));
builder.setResizeExecutor(GlideExecutor.newSourceExecutor(uncaughtThrowableStrategy));

// Decode format - RGB565 is enough for us
builder.setDefaultRequestOptions(new RequestOptions().format(DecodeFormat.PREFER_RGB_565));
}

@Override
public void registerComponents(Context context, Glide glide, Registry registry) {
String appName = context.getString(R.string.userAgent_appName);
String versionName = AppUtil.getVersionName(context);

if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.LOLLIPOP) {
// For some reason, the image server uses a version of SSL that causes problems on old OS versions.
// Using the Play Services provider fixes the issue.
try {
ProviderInstaller.installIfNeeded(context);
} catch (Exception e) {
Log.w(e, "Could not install SSL provider");
}
}

// Use our OkHttp configuration
registry.replace(GlideUrl.class, InputStream.class,
new OkHttpUrlLoader.Factory(createOkHttpClient(BuildConfig.LOG_HTTP, appName, versionName)));
}

private OkHttpClient createOkHttpClient(boolean logRequests, String appName, String versionName) {
OkHttpClient.Builder httpClientBuilder = new OkHttpClient.Builder();

// User agent
httpClientBuilder.addInterceptor(new UserAgentInterceptor(appName, versionName));

if (logRequests) {
// TODO: 22/05/2017 Add a way to pass arbitrary interceptors so we can still use Stetho
// // Stetho HTTP logs
// httpClientBuilder.addNetworkInterceptor(new StethoInterceptor());

// OkHttp Logs
HttpLoggingInterceptor logging = new HttpLoggingInterceptor(new LessHttpLoggingInterceptor());
logging.setLevel(HttpLoggingInterceptor.Level.BODY);
httpClientBuilder.addInterceptor(logging);
}

return httpClientBuilder.build();
}
}

public static void load(@Nullable String path, @Nullable ImageView imageView, boolean round) {
if (path == null) {
Log.w("Load called with a null path");
return;
}

if (imageView == null) {
Log.w("Load called with a null imageView");
return;
}

GlideRequest<Drawable> request = GlideApp.with(imageView)
.load(path)
.listener(sRequestListener);
if (round) {
request.circleCrop();
} else {
request.centerCrop();
}

request.into(imageView);
}

Layout XML: Just a regular ImageView

Stack trace / LogCat:

11-07 11:55:54.743 18267-18267/? V/Engine: Started new load in 0.215886ms, key: EngineKey{model=https://img.less.com/profile-pictures/v1/9c0b7976-b0d7-42fd-9aa1-db8c9aeed5ce, width=240, height=240, resourceClass=class java.lang.Object, transcodeClass=class android.graphics.drawable.Drawable, signature=EmptySignature, hashCode=-1205594164, transformations={class android.graphics.drawable.BitmapDrawable=com.bumptech.glide.c.d.a.c@41aadb8c, class android.graphics.Bitmap=com.bumptech.glide.c.d.a.j@41aadb8c, class com.bumptech.glide.c.d.e.c=com.bumptech.glide.c.d.e.f@41aadb8c}, options=Options{values={Option{key='com.bumptech.glide.load.resource.bitmap.Downsampler.DownsampleStrategy'}=com.bumptech.glide.c.d.a.l$c@423219b0, Option{key='com.bumptech.glide.load.resource.bitmap.Downsampler.DecodeFormat'}=PREFER_RGB_565}}}
11-07 11:55:54.753 282-18961/? D/libc: _dns_getaddrinfo: iptype =1
11-07 11:55:54.753 282-18961/? D/libc: _dns_getaddrinfo: query_ipv4=1, query_ipv6=0
11-07 11:55:54.763 1031-1053/? I/ActivityManager: Timeline: Activity_windows_visible id: ActivityRecord{42139f70 u0 com.less.line/.app.user.details.UserDetailsActivity t31} time:7884242
11-07 11:55:54.803 1643-1647/? D/dalvikvm: GC_CONCURRENT freed 1952K, 16% free 14718K/17328K, paused 7ms+9ms, total 112ms
11-07 11:55:54.813 18267-18267/? I/ActivityManager: Timeline: Activity_idle id: android.os.BinderProxy@42751990 time:7884297
11-07 11:55:54.813 1031-1743/? V/ActivityManager: Moving to STOPPING: ActivityRecord{4346ac28 u0 com.less.line/.app.map.MapActivity t31} (stop requested)
11-07 11:55:54.823 18267-18267/? D/LessApplication/a$a: f
11-07 11:55:54.823 18267-18267/? D/LessApplication/a$a: f
11-07 11:55:54.823 1406-1406/? I/[SystemUI]LocationController: action : android.location.HIGH_POWER_REQUEST_CHANGE
11-07 11:55:54.833 1406-1406/? I/[SystemUI]LocationController: action : android.location.HIGH_POWER_REQUEST_CHANGE
11-07 11:55:54.843 18267-18267/? I/SensorManager: unregisterListenerImpl sensor: LGE Rotation Vector Sensor, listener: com.mapbox.mapboxsdk.maps.widgets.MyLocationView$CompassListener@42443c00 by pid 18267
11-07 11:55:54.853 1031-1444/? V/ActivityManager: Moving to STOPPED: ActivityRecord{4346ac28 u0 com.less.line/.app.map.MapActivity t31} (stop complete)
11-07 11:55:54.853 278-318/? I/Vold: [LGE][VOLD][NetlinkHandler.cpp][onEvent()] subsys:cpu, action:0
11-07 11:55:54.883 18885-18885/? D/MusicLifecycle: com.google.android.music.autocache.AutoCacheSchedulingService$EnablingReceiver generated event: Broadcast received with context android.app.ReceiverRestrictedContext@42683130 and intent Intent { act=com.google.android.music.START_DOWNLOAD_SCHEDULING flg=0x10 cmp=com.google.android.music/.autocache.AutoCacheSchedulingService$EnablingReceiver }
11-07 11:55:54.893 18885-18885/? D/MusicLifecycle: com.google.android.music.autocache.AutoCacheSchedulingService generated event: Service created
11-07 11:55:54.903 18885-18885/? D/MusicLifecycle: com.google.android.music.autocache.AutoCacheSchedulingService generated event: Service started with intent Intent { act=com.google.android.music.leanback.APP_IN_USE cmp=com.google.android.music/.autocache.AutoCacheSchedulingService (has extras) }
11-07 11:55:54.903 18885-18885/? D/MusicLifecycle: com.google.android.music.wear.WearBroadcastReceiver generated event: Broadcast received with context android.app.ReceiverRestrictedContext@42683130 and intent Intent { act=com.google.android.music.START_DOWNLOAD_SCHEDULING flg=0x10 cmp=com.google.android.music/.wear.WearBroadcastReceiver }
11-07 11:55:54.933 18885-18967/? I/MusicLeanback: Conditions not met for autocaching. okToAttempt=false
11-07 11:55:54.933 18885-18967/? I/MusicLeanback: Stop autocaching.
11-07 11:55:54.943 18885-18885/? D/MusicLifecycle: com.google.android.music.autocache.AutoCacheSchedulingService generated event: Service destroyed
11-07 11:55:54.943 18267-18897/? D/DecodeJob: DecodeJob threw unexpectedly, isCancelled: false, stage: SOURCE
android.os.StrictMode$StrictModeViolation: policy=68 violation=4
at android.os.StrictMode.executeDeathPenalty(StrictMode.java:1380)
at android.os.StrictMode.access$1300(StrictMode.java:116)
at android.os.StrictMode$AndroidBlockGuardPolicy.handleViolation(StrictMode.java:1373)
at android.os.StrictMode$AndroidBlockGuardPolicy.handleViolationWithTimingAttempt(StrictMode.java:1200)
at android.os.StrictMode$AndroidBlockGuardPolicy.startHandlingViolationException(StrictMode.java:1170)
at android.os.StrictMode$AndroidBlockGuardPolicy.onNetwork(StrictMode.java:1155)
at com.google.android.gms.org.conscrypt.Platform.blockGuardOnNetwork(:com.google.android.gms@11745036)
at com.google.android.gms.org.conscrypt.ConscryptFileDescriptorSocket$SSLInputStream.read(:com.google.android.gms@11745036:2)
at e.n$2.a(Okio.java:139)
at e.a$2.a(AsyncTimeout.java:237)
at e.p.a(RealBufferedSource.java:46)
at d.a.d.a$a.a(Http1Codec.java:352)
at d.a.d.a$e.a(Http1Codec.java:396)
at d.a.c.b(Util.java:175)
at d.a.c.a(Util.java:157)
at d.a.d.a$e.close(Http1Codec.java:413)
at e.p.close(RealBufferedSource.java:455)
at e.p$1.close(RealBufferedSource.java:443)
at java.io.FilterInputStream.close(FilterInputStream.java:64)
at com.bumptech.glide.integration.okhttp3.b.a(OkHttpStreamFetcher.java:92)
at com.bumptech.glide.c.c.q$a.a(MultiModelLoader.java:106)
at com.bumptech.glide.c.b.w.b(SourceGenerator.java:87)
at com.bumptech.glide.c.b.w.a(SourceGenerator.java:45)
at com.bumptech.glide.c.b.f.k(DecodeJob.java:293)
at com.bumptech.glide.c.b.f.i(DecodeJob.java:263)
at com.bumptech.glide.c.b.f.run(DecodeJob.java:230)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1112)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:587)
at java.lang.Thread.run(Thread.java:841)
at com.bumptech.glide.c.b.c.a$a$1.run(GlideExecutor.java:386)
11-07 11:55:54.943 18267-18267/? W/Glide: Load failed for https://img.less.com/profile-pictures/v1/9c0b7976-b0d7-42fd-9aa1-db8c9aeed5ce with size [240x240]
class com.bumptech.glide.c.b.o: Failed to load resource
11-07 11:55:54.943 18267-18267/? W/LessApplication/e: a Could not load image https://img.less.com/profile-pictures/v1/9c0b7976-b0d7-42fd-9aa1-db8c9aeed5ce
class com.bumptech.glide.c.b.o: Failed to load resource
11-07 11:55:54.943 18267-18897/? W/LessApplication/LessGlideModule: lambda$applyOptions$0$LessGlideModule Unable to get or display image glide-disk-cache-thread-3
android.os.StrictMode$StrictModeViolation: policy=68 violation=4
at android.os.StrictMode.executeDeathPenalty(StrictMode.java:1380)
at android.os.StrictMode.access$1300(StrictMode.java:116)
at android.os.StrictMode$AndroidBlockGuardPolicy.handleViolation(StrictMode.java:1373)
at android.os.StrictMode$AndroidBlockGuardPolicy.handleViolationWithTimingAttempt(StrictMode.java:1200)
at android.os.StrictMode$AndroidBlockGuardPolicy.startHandlingViolationException(StrictMode.java:1170)
at android.os.StrictMode$AndroidBlockGuardPolicy.onNetwork(StrictMode.java:1155)
at com.google.android.gms.org.conscrypt.Platform.blockGuardOnNetwork(:com.google.android.gms@11745036)
at com.google.android.gms.org.conscrypt.ConscryptFileDescriptorSocket$SSLInputStream.read(:com.google.android.gms@11745036:2)
at e.n$2.a(Okio.java:139)
at e.a$2.a(AsyncTimeout.java:237)
at e.p.a(RealBufferedSource.java:46)
at d.a.d.a$a.a(Http1Codec.java:352)
at d.a.d.a$e.a(Http1Codec.java:396)
at d.a.c.b(Util.java:175)
at d.a.c.a(Util.java:157)
at d.a.d.a$e.close(Http1Codec.java:413)
at e.p.close(RealBufferedSource.java:455)
at e.p$1.close(RealBufferedSource.java:443)
at java.io.FilterInputStream.close(FilterInputStream.java:64)
at com.bumptech.glide.integration.okhttp3.b.a(OkHttpStreamFetcher.java:92)
at com.bumptech.glide.c.c.q$a.a(MultiModelLoader.java:106)
at com.bumptech.glide.c.b.w.b(SourceGenerator.java:87)
at com.bumptech.glide.c.b.w.a(SourceGenerator.java:45)
at com.bumptech.glide.c.b.f.k(DecodeJob.java:293)
at com.bumptech.glide.c.b.f.i(DecodeJob.java:263)
at com.bumptech.glide.c.b.f.run(DecodeJob.java:230)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1112)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:587)
at java.lang.Thread.run(Thread.java:841)
at com.bumptech.glide.c.b.c.a$a$1.run(GlideExecutor.java:386)
1