Skip to content

Commit

Permalink
Add support for decoding non-Bitmap drawables using Resources.
Browse files Browse the repository at this point in the history
As a fallback for drawables that we either can’t obtain an InputStream
for or can’t decode as Bitmaps, Glide will now also call 
Resources#getDrawable with the given resource id. 

Fixes #350.
  • Loading branch information
sjudd committed Oct 7, 2017
1 parent d13e97b commit 7614e10
Show file tree
Hide file tree
Showing 5 changed files with 151 additions and 30 deletions.
10 changes: 8 additions & 2 deletions library/src/main/java/com/bumptech/glide/Glide.java
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.os.Build;
import android.os.ParcelFileDescriptor;
Expand Down Expand Up @@ -54,6 +55,7 @@
import com.bumptech.glide.load.resource.bitmap.StreamBitmapDecoder;
import com.bumptech.glide.load.resource.bitmap.VideoBitmapDecoder;
import com.bumptech.glide.load.resource.bytes.ByteBufferRewinder;
import com.bumptech.glide.load.resource.drawable.ResourceDrawableDecoder;
import com.bumptech.glide.load.resource.file.FileDecoder;
import com.bumptech.glide.load.resource.gif.ByteBufferGifDecoder;
import com.bumptech.glide.load.resource.gif.GifDrawable;
Expand Down Expand Up @@ -329,6 +331,8 @@ private static GeneratedAppGlideModule getAnnotationGeneratedGlideModules() {
.append(GifDecoder.class, GifDecoder.class, new UnitModelLoader.Factory<GifDecoder>())
.append(Registry.BUCKET_BITMAP, GifDecoder.class, Bitmap.class,
new GifFrameResourceDecoder(bitmapPool))
/* Drawables */
.append(Uri.class, Drawable.class, new ResourceDrawableDecoder(context))
/* Files */
.register(new ByteBufferRewinder.Factory())
.append(File.class, ByteBuffer.class, new ByteBufferFileLoader.Factory())
Expand All @@ -349,6 +353,8 @@ private static GeneratedAppGlideModule getAnnotationGeneratedGlideModules() {
Integer.class,
ParcelFileDescriptor.class,
new ResourceLoader.FileDescriptorFactory(resources))
.append(Integer.class, Uri.class, new ResourceLoader.UriFactory(resources))
.append(int.class, Uri.class, new ResourceLoader.UriFactory(resources))
.append(String.class, InputStream.class, new DataUrlLoader.StreamFactory())
.append(String.class, InputStream.class, new StringLoader.StreamFactory())
.append(String.class, ParcelFileDescriptor.class, new StringLoader.FileDescriptorFactory())
Expand All @@ -362,8 +368,8 @@ private static GeneratedAppGlideModule getAnnotationGeneratedGlideModules() {
.append(Uri.class, InputStream.class, new MediaStoreVideoThumbLoader.Factory(context))
.append(
Uri.class,
InputStream.class,
new UriLoader.StreamFactory(context.getContentResolver()))
InputStream.class,
new UriLoader.StreamFactory(context.getContentResolver()))
.append(Uri.class, ParcelFileDescriptor.class,
new UriLoader.FileDescriptorFactory(context.getContentResolver()))
.append(Uri.class, InputStream.class, new UrlUriLoader.StreamFactory())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,6 @@ public ResourceLoader(Resources resources, ModelLoader<Uri, Data> uriLoader) {

@Override
public LoadData<Data> buildLoadData(Integer model, int width, int height, Options options) {

Uri uri = getResourceUri(model);
return uri == null ? null : uriLoader.buildLoadData(uri, width, height, options);
}
Expand Down Expand Up @@ -98,4 +97,26 @@ public void teardown() {
// Do nothing.
}
}

/**
* Factory for loading resource {@link Uri}s from Android resource ids.
*/
public static class UriFactory implements ModelLoaderFactory<Integer, Uri> {

private final Resources resources;

public UriFactory(Resources resources) {
this.resources = resources;
}

@Override
public ModelLoader<Integer, Uri> build(MultiModelLoaderFactory multiFactory) {
return new ResourceLoader<>(resources, new UnitModelLoader<Uri>());
}

@Override
public void teardown() {
// Do nothing.
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
package com.bumptech.glide.load.resource.drawable;

import android.content.Context;
import android.content.res.Resources;
import android.content.res.Resources.Theme;
import android.graphics.drawable.Drawable;
import android.support.annotation.DrawableRes;
import android.support.annotation.Nullable;
import android.support.v4.content.res.ResourcesCompat;
import android.support.v7.content.res.AppCompatResources;

/**
* Handles decoding Drawables with the v7 support library if present and falling back to the v4
* support library otherwise.
*/
public final class DrawableDecoderCompat {
private static volatile boolean shouldCallAppCompatResources = true;
private DrawableDecoderCompat() {
// Utility class.
}

/**
* Loads a Drawable using {@link AppCompatResources} if available and {@link ResourcesCompat}
* otherwise, depending on whether or not the v7 support library is included in the application.
*/
public static Drawable getDrawable(Context context, @DrawableRes int id, @Nullable Theme theme) {
try {
// Race conditions may cause us to attempt to load using v7 more than once. That's ok since
// this check is a modest optimization and the output will be correct anyway.
if (shouldCallAppCompatResources) {
return loadDrawableV7(context, id);
}
} catch (NoClassDefFoundError error) {
shouldCallAppCompatResources = false;
}

return loadDrawableV4(context, id, theme != null ? theme : context.getTheme());
}

private static Drawable loadDrawableV7(Context context, @DrawableRes int id) {
return AppCompatResources.getDrawable(context, id);
}

private static Drawable loadDrawableV4(
Context context, @DrawableRes int id, @Nullable Theme theme) {
Resources resources = context.getResources();
return ResourcesCompat.getDrawable(resources, id, theme);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
package com.bumptech.glide.load.resource.drawable;

import android.content.ContentResolver;
import android.content.Context;
import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.support.annotation.Nullable;
import com.bumptech.glide.load.Options;
import com.bumptech.glide.load.ResourceDecoder;
import com.bumptech.glide.load.engine.Resource;
import java.io.IOException;
import java.util.List;

/**
* Decodes {@link Drawable}s given resource {@link Uri}s in the form
* android.resource://<package_name>/<type>/<name>.
*/
public class ResourceDrawableDecoder implements ResourceDecoder<Uri, Drawable> {
private static final int EXPECTED_PATH_SEGMENTS = 2;
private static final int TYPE_PATH_SEGMENT_INDEX = 0;
private static final int NAME_PATH_SEGMENT_INDEX = 1;

private final Context context;

public ResourceDrawableDecoder(Context context) {
this.context = context.getApplicationContext();
}

@Override
public boolean handles(Uri source, Options options) throws IOException {
return source.getScheme().equals(ContentResolver.SCHEME_ANDROID_RESOURCE);
}

@Nullable
@Override
public Resource<Drawable> decode(Uri source, int width, int height, Options options)
throws IOException {
// Parsing is based on the logic in ResourceLoader/the android framework that constructs
// resource Uris.
List<String> segments = source.getPathSegments();
if (segments.size() != EXPECTED_PATH_SEGMENTS) {
throw new IOException("Unexpected path segments for: " + source + " segments: " + segments);
}
String packageName = source.getAuthority();
String typeName = segments.get(TYPE_PATH_SEGMENT_INDEX);
String resourceName = segments.get(NAME_PATH_SEGMENT_INDEX);
int id = context.getResources().getIdentifier(resourceName, typeName, packageName);
Drawable drawable = DrawableDecoderCompat.getDrawable(context, id, null /*theme*/);
if (drawable == null) {
throw new IOException("ContextCompat#getDrawable returned null for: " + source);
}
return new DrawableResource<Drawable>(drawable) {
@SuppressWarnings("unchecked")
@Override
public Class<Drawable> getResourceClass() {
return (Class<Drawable>) drawable.getClass();
}

@Override
public int getSize() {
return 1;
}

@Override
public void recycle() {
// Do nothing.
}
};
}
}
Original file line number Diff line number Diff line change
@@ -1,19 +1,17 @@
package com.bumptech.glide.request;

import android.content.res.Resources;
import android.graphics.drawable.Drawable;
import android.support.annotation.DrawableRes;
import android.support.annotation.Nullable;
import android.support.v4.content.res.ResourcesCompat;
import android.support.v4.util.Pools;
import android.support.v7.content.res.AppCompatResources;
import android.util.Log;
import com.bumptech.glide.GlideContext;
import com.bumptech.glide.Priority;
import com.bumptech.glide.load.DataSource;
import com.bumptech.glide.load.engine.Engine;
import com.bumptech.glide.load.engine.GlideException;
import com.bumptech.glide.load.engine.Resource;
import com.bumptech.glide.load.resource.drawable.DrawableDecoderCompat;
import com.bumptech.glide.request.target.SizeReadyCallback;
import com.bumptech.glide.request.target.Target;
import com.bumptech.glide.request.transition.Transition;
Expand Down Expand Up @@ -107,7 +105,6 @@ private enum Status {
private Drawable fallbackDrawable;
private int width;
private int height;
private static boolean shouldCallAppCompatResources = true;

public static <R> SingleRequest<R> obtain(
GlideContext glideContext,
Expand Down Expand Up @@ -382,29 +379,7 @@ private Drawable getFallbackDrawable() {
}

private Drawable loadDrawable(@DrawableRes int resourceId) {
if (shouldCallAppCompatResources) {
return loadDrawableV7(resourceId);
} else {
return loadDrawableBase(resourceId);
}
}

/**
* Tries to load the drawable thanks to AppCompatResources.<br>
* This allows to parse VectorDrawables on legacy devices if the appcompat v7 is in the classpath.
*/
private Drawable loadDrawableV7(@DrawableRes int resourceId) {
try {
return AppCompatResources.getDrawable(glideContext, resourceId);
} catch (NoClassDefFoundError error) {
shouldCallAppCompatResources = false;
return loadDrawableBase(resourceId);
}
}

private Drawable loadDrawableBase(@DrawableRes int resourceId) {
Resources resources = glideContext.getResources();
return ResourcesCompat.getDrawable(resources, resourceId, requestOptions.getTheme());
return DrawableDecoderCompat.getDrawable(glideContext, resourceId, requestOptions.getTheme());
}

private void setErrorPlaceholder() {
Expand Down

0 comments on commit 7614e10

Please sign in to comment.