diff --git a/integration/okhttp/src/main/AndroidManifest.xml b/integration/okhttp/src/main/AndroidManifest.xml index 53b73913db..4ab3408677 100644 --- a/integration/okhttp/src/main/AndroidManifest.xml +++ b/integration/okhttp/src/main/AndroidManifest.xml @@ -1,5 +1,8 @@ - - + + + diff --git a/integration/okhttp/src/main/java/com/bumptech/glide/integration/okhttp/OkHttpGlideModule.java b/integration/okhttp/src/main/java/com/bumptech/glide/integration/okhttp/OkHttpGlideModule.java new file mode 100644 index 0000000000..fd9a901cf6 --- /dev/null +++ b/integration/okhttp/src/main/java/com/bumptech/glide/integration/okhttp/OkHttpGlideModule.java @@ -0,0 +1,33 @@ +package com.bumptech.glide.integration.okhttp; + +import android.content.Context; + +import com.bumptech.glide.Glide; +import com.bumptech.glide.GlideBuilder; +import com.bumptech.glide.load.model.GlideUrl; +import com.bumptech.glide.module.GlideModule; + +import java.io.InputStream; + +/** + * A {@link com.bumptech.glide.module.GlideModule} implementation to replace Glide's default + * {@link java.net.HttpURLConnection} based {@link com.bumptech.glide.load.model.ModelLoader} with an OkHttp based + * {@link com.bumptech.glide.load.model.ModelLoader}. + * + *

+ * If you're using gradle, you can include this module simply by depending on the aar, the module will be merged + * in by manifest merger. For other build systems or for more more information, see + * {@link com.bumptech.glide.module.GlideModule}. + *

+ */ +public class OkHttpGlideModule implements GlideModule { + @Override + public void applyOptions(Context context, GlideBuilder builder) { + // Do nothing. + } + + @Override + public void registerComponents(Context context, Glide glide) { + glide.register(GlideUrl.class, InputStream.class, new OkHttpUrlLoader.Factory()); + } +} diff --git a/integration/volley/src/main/AndroidManifest.xml b/integration/volley/src/main/AndroidManifest.xml index c2d679aabb..554a226006 100644 --- a/integration/volley/src/main/AndroidManifest.xml +++ b/integration/volley/src/main/AndroidManifest.xml @@ -1,5 +1,9 @@ - + + + diff --git a/integration/volley/src/main/java/com/bumptech/glide/integration/volley/VolleyGlideModule.java b/integration/volley/src/main/java/com/bumptech/glide/integration/volley/VolleyGlideModule.java new file mode 100644 index 0000000000..eaa05dba7c --- /dev/null +++ b/integration/volley/src/main/java/com/bumptech/glide/integration/volley/VolleyGlideModule.java @@ -0,0 +1,33 @@ +package com.bumptech.glide.integration.volley; + +import android.content.Context; + +import com.bumptech.glide.Glide; +import com.bumptech.glide.GlideBuilder; +import com.bumptech.glide.load.model.GlideUrl; +import com.bumptech.glide.module.GlideModule; + +import java.io.InputStream; + +/** + * A {@link com.bumptech.glide.module.GlideModule} implementation to replace Glide's default + * {@link java.net.HttpURLConnection} based {@link com.bumptech.glide.load.model.ModelLoader} with a Volley based + * {@link com.bumptech.glide.load.model.ModelLoader}. + * + *

+ * If you're using gradle, you can include this module simply by depending on the aar, the module will be merged + * in by manifest merger. For other build systems or for more more information, see + * {@link com.bumptech.glide.module.GlideModule}. + *

+ */ +public class VolleyGlideModule implements GlideModule { + @Override + public void applyOptions(Context context, GlideBuilder builder) { + // Do nothing. + } + + @Override + public void registerComponents(Context context, Glide glide) { + glide.register(GlideUrl.class, InputStream.class, new VolleyUrlLoader.Factory(context)); + } +} diff --git a/library/src/androidTest/java/com/bumptech/glide/module/ManifestParserTest.java b/library/src/androidTest/java/com/bumptech/glide/module/ManifestParserTest.java new file mode 100644 index 0000000000..e2c37e2d0c --- /dev/null +++ b/library/src/androidTest/java/com/bumptech/glide/module/ManifestParserTest.java @@ -0,0 +1,152 @@ +package com.bumptech.glide.module; + +import static com.google.common.truth.Truth.assertThat; +import static org.mockito.Matchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import android.content.Context; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageManager; +import android.os.Bundle; + +import com.bumptech.glide.Glide; +import com.bumptech.glide.GlideBuilder; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.robolectric.RobolectricTestRunner; +import org.robolectric.annotation.Config; + +import java.util.List; + +@RunWith(RobolectricTestRunner.class) +@Config(manifest = Config.NONE, emulateSdk = 18) +public class ManifestParserTest { + private static final String MODULE_VALUE = "GlideModule"; + + @Mock Context context; + private ManifestParser parser; + private ApplicationInfo applicationInfo; + + @Before + public void setUp() throws PackageManager.NameNotFoundException { + MockitoAnnotations.initMocks(this); + applicationInfo = new ApplicationInfo(); + applicationInfo.metaData = new Bundle(); + + String packageName = "com.bumptech.test"; + when(context.getPackageName()).thenReturn(packageName); + + PackageManager pm = mock(PackageManager.class); + when(pm.getApplicationInfo(eq(packageName), eq(PackageManager.GET_META_DATA))) + .thenReturn(applicationInfo); + when(context.getPackageManager()).thenReturn(pm); + + parser = new ManifestParser(context); + } + + @Test + public void testParse_returnsEmptyListIfNoModulesListed() { + assertThat(parser.parse()).isEmpty(); + } + + @Test + public void testParse_withSingleValidModuleName_returnsListContainingModule() { + addModuleToManifest(TestModule1.class); + + List modules = parser.parse(); + assertThat(modules).hasSize(1); + assertThat(modules.get(0)).isInstanceOf(TestModule1.class); + } + + @Test + public void testParse_withMultipleValidModuleNames_returnsListContainingModules() { + addModuleToManifest(TestModule1.class); + addModuleToManifest(TestModule2.class); + + List modules = parser.parse(); + assertThat(modules).hasSize(2); + + assertThat(modules).contains(new TestModule1()); + assertThat(modules).contains(new TestModule2()); + } + + @Test + public void testParse_withValidModuleName_ignoresMetadataWithoutGlideModuleValue() { + applicationInfo.metaData.putString(TestModule1.class.getName(), MODULE_VALUE + "test"); + + assertThat(parser.parse()).isEmpty(); + } + + @Test(expected = RuntimeException.class) + public void testThrows_whenModuleNameNotFound() { + addToManifest("fakeClassName"); + + parser.parse(); + } + + @Test(expected = RuntimeException.class) + public void testThrows_whenClassInManifestIsNotAModule() { + addModuleToManifest(InvalidClass.class); + + parser.parse(); + } + + @Test(expected = RuntimeException.class) + public void testThrows_whenPackageNameNotFound() { + when(context.getPackageName()).thenReturn("fakePackageName"); + + parser.parse(); + } + + private void addModuleToManifest(Class moduleClass) { + addToManifest(moduleClass.getName()); + } + + private void addToManifest(String key) { + applicationInfo.metaData.putString(key, MODULE_VALUE); + } + + public static class InvalidClass { } + + public static class TestModule1 implements GlideModule { + @Override + public void applyOptions(Context context, GlideBuilder builder) { } + + @Override + public void registerComponents(Context context, Glide glide) { } + + @Override + public boolean equals(Object o) { + return o instanceof TestModule1; + } + + @Override + public int hashCode() { + return super.hashCode(); + } + } + + public static class TestModule2 implements GlideModule { + + @Override + public void applyOptions(Context context, GlideBuilder builder) { } + + @Override + public void registerComponents(Context context, Glide glide) { } + + @Override + public boolean equals(Object o) { + return o instanceof TestModule2; + } + + @Override + public int hashCode() { + return super.hashCode(); + } + } +} \ No newline at end of file diff --git a/library/src/main/java/com/bumptech/glide/Glide.java b/library/src/main/java/com/bumptech/glide/Glide.java index 9461b2522e..73b5581ae1 100644 --- a/library/src/main/java/com/bumptech/glide/Glide.java +++ b/library/src/main/java/com/bumptech/glide/Glide.java @@ -56,6 +56,8 @@ import com.bumptech.glide.load.resource.transcode.ResourceTranscoder; import com.bumptech.glide.load.resource.transcode.TranscoderRegistry; import com.bumptech.glide.manager.RequestManagerRetriever; +import com.bumptech.glide.module.GlideModule; +import com.bumptech.glide.module.ManifestParser; import com.bumptech.glide.provider.DataLoadProvider; import com.bumptech.glide.provider.DataLoadProviderRegistry; import com.bumptech.glide.request.FutureTarget; @@ -69,6 +71,7 @@ import java.io.File; import java.io.InputStream; import java.net.URL; +import java.util.List; /** * A singleton to present a simple static interface for building requests with {@link BitmapRequestBuilder} and @@ -144,7 +147,17 @@ public static Glide get(Context context) { if (glide == null) { synchronized (Glide.class) { if (glide == null) { - glide = new GlideBuilder(context).createGlide(); + Context applicationContext = context.getApplicationContext(); + List modules = new ManifestParser(applicationContext).parse(); + + GlideBuilder builder = new GlideBuilder(applicationContext); + for (GlideModule module : modules) { + module.applyOptions(applicationContext, builder); + } + glide = builder.createGlide(); + for (GlideModule module : modules) { + module.registerComponents(applicationContext, glide); + } } } } @@ -157,7 +170,10 @@ public static Glide get(Context context) { * {@link #setup(GlideBuilder)}. * * @see #setup(GlideBuilder) + * + * @deprecated Use {@link com.bumptech.glide.module.GlideModule} instead. Scheduled to be removed in Glide 4.0. */ + @Deprecated public static boolean isSetup() { return glide != null; } @@ -168,9 +184,11 @@ public static boolean isSetup() { * * @see #isSetup() * + * @deprecated Use {@link com.bumptech.glide.module.GlideModule} instead. Scheduled to be removed in Glide 4.0. * @param builder The builder. * @throws IllegalArgumentException if the Glide singleton has already been created. */ + @Deprecated public static void setup(GlideBuilder builder) { if (isSetup()) { throw new IllegalArgumentException("Glide is already setup, check with isSetup() first"); @@ -461,11 +479,14 @@ public void register(Class modelClass, Class resourceClass, ModelLo * Removes any {@link ModelLoaderFactory} registered for the given model and resource classes if one exists. If a * {@link ModelLoaderFactory} is removed, its {@link ModelLoaderFactory#teardown()}} method will be called. * + * @deprecated Use {@link #register(Class, Class, com.bumptech.glide.load.model.ModelLoaderFactory)} to replace + * a registered loader rather than simply removing it. * @param modelClass The model class. * @param resourceClass The resource class. * @param The type of the model. * @param The type of the resource. */ + @Deprecated public void unregister(Class modelClass, Class resourceClass) { ModelLoaderFactory removed = loaderFactory.unregister(modelClass, resourceClass); if (removed != null) { diff --git a/library/src/main/java/com/bumptech/glide/module/GlideModule.java b/library/src/main/java/com/bumptech/glide/module/GlideModule.java new file mode 100644 index 0000000000..76b45fd724 --- /dev/null +++ b/library/src/main/java/com/bumptech/glide/module/GlideModule.java @@ -0,0 +1,97 @@ +package com.bumptech.glide.module; + +import android.content.Context; + +import com.bumptech.glide.Glide; +import com.bumptech.glide.GlideBuilder; + +/** + * An interface allowing lazy configuration of Glide including setting options using + * {@link com.bumptech.glide.GlideBuilder} and registering + * {@link com.bumptech.glide.load.model.ModelLoader ModelLoaders}. + * + *

+ * To use this interface: + *

    + *
  1. + * Implement the GlideModule interface in a class with public visibility, calling + * {@link com.bumptech.glide.Glide#register(Class, Class, com.bumptech.glide.load.model.ModelLoaderFactory)} + * for each {@link com.bumptech.glide.load.model.ModelLoader} you'd like to register: + *
    + *                  
    + *                      public class FlickrGlideModule implements GlideModule {
    + *                          {@literal @}Override
    + *                          public void applyOptions(Context context, GlideBuilder builder) {
    + *                              buidler.setDecodeFormat(DecodeFormat.ALWAYS_ARGB_8888);
    + *                          }
    + *
    + *                          {@literal @}Override
    + *                          public void registerComponents(Context context, Glide glide) {
    + *                              glide.register(Model.class, Data.class, new MyModelLoader());
    + *                          }
    + *                      }
    + *                  
    + *             
    + *
  2. + *
  3. + * Add your implementation to your list of keeps in your proguard.cfg file: + *
    + *                  {@code
    + *                      -keepnames class * com.bumptech.glide.samples.flickr.FlickrGlideModule
    + *                  }
    + *              
    + *
  4. + *
  5. + * Add a metadata tag to your AndroidManifest.xml with your GlideModule implementation's fully qualified + * classname as the key, and {@code GlideModule} as the value: + *
    + *                 {@code
    + *                      
    + *                 }
    + *             
    + *
  6. + *
+ *

+ * + *

+ * All implementations must be publicly visible and contain only an empty constructor so they can be instantiated + * via reflection when Glide is lazily initialized. + *

+ * + *

+ * There is no defined order in which modules are called, so projects should be careful to avoid applying + * conflicting settings in different modules. If an application depends on libraries that have conflicting + * modules, the application should consider avoiding the library modules and instead providing their required + * dependencies in a single application module. + *

+ */ +public interface GlideModule { + + /** + * Lazily apply options to a {@link com.bumptech.glide.GlideBuilder} immediately before the Glide singleton is + * created. + * + *

+ * This method will be called once and only once per implementation. + *

+ * + * @param context An Application {@link android.content.Context}. + * @param builder The {@link com.bumptech.glide.GlideBuilder} that will be used to create Glide. + */ + void applyOptions(Context context, GlideBuilder builder); + + /** + * Lazily register components immediately after the Glide singleton is created but before any requests can be + * started. + * + *

+ * This method will be called once and only once per implementation. + *

+ * + * @param context An Application {@link android.content.Context}. + * @param glide The newly created Glide singleton. + */ + void registerComponents(Context context, Glide glide); +} diff --git a/library/src/main/java/com/bumptech/glide/module/ManifestParser.java b/library/src/main/java/com/bumptech/glide/module/ManifestParser.java new file mode 100644 index 0000000000..4419e03d79 --- /dev/null +++ b/library/src/main/java/com/bumptech/glide/module/ManifestParser.java @@ -0,0 +1,63 @@ +package com.bumptech.glide.module; + +import android.content.Context; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageManager; + +import java.util.ArrayList; +import java.util.List; + +/** + * Parses {@link com.bumptech.glide.module.GlideModule} references out of the AndroidManifest file. + */ +public final class ManifestParser { + private static final String GLIDE_MODULE_VALUE = "GlideModule"; + + private final Context context; + + public ManifestParser(Context context) { + this.context = context; + } + + public List parse() { + List modules = new ArrayList(); + try { + ApplicationInfo appInfo = context.getPackageManager().getApplicationInfo( + context.getPackageName(), PackageManager.GET_META_DATA); + if (appInfo.metaData != null) { + for (String key : appInfo.metaData.keySet()) { + if (GLIDE_MODULE_VALUE.equals(appInfo.metaData.getString(key))) { + modules.add(parseModule(key)); + } + } + } + } catch (PackageManager.NameNotFoundException e) { + throw new RuntimeException("Unable to find metadata to parse GlideModules", e); + } + + return modules; + } + + private static GlideModule parseModule(String className) { + Class clazz; + try { + clazz = Class.forName(className); + } catch (ClassNotFoundException e) { + throw new IllegalArgumentException("Unable to find GlideModule implementation", e); + } + + Object module; + try { + module = clazz.newInstance(); + } catch (InstantiationException e) { + throw new RuntimeException("Unable to instantiate GlideModule implementation for " + clazz, e); + } catch (IllegalAccessException e) { + throw new RuntimeException("Unable to instantiate GlideModule implementation for " + clazz, e); + } + + if (!(module instanceof GlideModule)) { + throw new RuntimeException("Expected instanceof GlideModule, but found: " + module); + } + return (GlideModule) module; + } +} diff --git a/library/src/main/java/com/bumptech/glide/util/ViewPreloadSizeProvider.java b/library/src/main/java/com/bumptech/glide/util/ViewPreloadSizeProvider.java index 477708be0c..ec309b634a 100644 --- a/library/src/main/java/com/bumptech/glide/util/ViewPreloadSizeProvider.java +++ b/library/src/main/java/com/bumptech/glide/util/ViewPreloadSizeProvider.java @@ -24,7 +24,7 @@ public class ViewPreloadSizeProvider implements ListPreloader.PreloadSizeProv /** * Constructor that does nothing by default and requires users to call {@link #setView(android.view.View)} when a - * View is available to initialize the dimensions returned by this class. + * View is available to registerComponents the dimensions returned by this class. */ public ViewPreloadSizeProvider() { // This constructor is intentionally empty. Nothing special is needed here. diff --git a/samples/flickr/src/main/AndroidManifest.xml b/samples/flickr/src/main/AndroidManifest.xml index 242d2cd15e..338a135dcb 100644 --- a/samples/flickr/src/main/AndroidManifest.xml +++ b/samples/flickr/src/main/AndroidManifest.xml @@ -23,6 +23,9 @@ + diff --git a/samples/flickr/src/main/java/com/bumptech/glide/samples/flickr/FlickrGlideModule.java b/samples/flickr/src/main/java/com/bumptech/glide/samples/flickr/FlickrGlideModule.java new file mode 100644 index 0000000000..a018b268da --- /dev/null +++ b/samples/flickr/src/main/java/com/bumptech/glide/samples/flickr/FlickrGlideModule.java @@ -0,0 +1,25 @@ +package com.bumptech.glide.samples.flickr; + +import android.content.Context; + +import com.bumptech.glide.Glide; +import com.bumptech.glide.GlideBuilder; +import com.bumptech.glide.module.GlideModule; +import com.bumptech.glide.samples.flickr.api.Photo; + +import java.io.InputStream; + +/** + * {@link com.bumptech.glide.module.GlideModule} for the Flickr sample app. + */ +public class FlickrGlideModule implements GlideModule { + @Override + public void applyOptions(Context context, GlideBuilder builder) { + // Do nothing. + } + + @Override + public void registerComponents(Context context, Glide glide) { + glide.register(Photo.class, InputStream.class, new FlickrModelLoader.Factory()); + } +} diff --git a/samples/flickr/src/main/java/com/bumptech/glide/samples/flickr/FlickrSearchActivity.java b/samples/flickr/src/main/java/com/bumptech/glide/samples/flickr/FlickrSearchActivity.java index 66fba98104..4b57a0e33c 100644 --- a/samples/flickr/src/main/java/com/bumptech/glide/samples/flickr/FlickrSearchActivity.java +++ b/samples/flickr/src/main/java/com/bumptech/glide/samples/flickr/FlickrSearchActivity.java @@ -33,7 +33,6 @@ import com.bumptech.glide.samples.flickr.api.Photo; import java.io.File; -import java.io.InputStream; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; @@ -90,7 +89,6 @@ public void onAttachFragment(Fragment fragment) { @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - Glide.get(this).register(Photo.class, InputStream.class, new FlickrModelLoader.Factory()); backgroundThread = new HandlerThread("BackgroundThumbnailHandlerThread"); backgroundThread.start(); backgroundHandler = new Handler(backgroundThread.getLooper()); diff --git a/samples/giphy/src/main/AndroidManifest.xml b/samples/giphy/src/main/AndroidManifest.xml index c5dbccb8f3..5d353dcbcd 100644 --- a/samples/giphy/src/main/AndroidManifest.xml +++ b/samples/giphy/src/main/AndroidManifest.xml @@ -19,6 +19,10 @@ + + diff --git a/samples/giphy/src/main/java/com/bumptech/glide/samples/giphy/GiphyGlideModule.java b/samples/giphy/src/main/java/com/bumptech/glide/samples/giphy/GiphyGlideModule.java new file mode 100644 index 0000000000..4913e778a7 --- /dev/null +++ b/samples/giphy/src/main/java/com/bumptech/glide/samples/giphy/GiphyGlideModule.java @@ -0,0 +1,24 @@ +package com.bumptech.glide.samples.giphy; + +import android.content.Context; + +import com.bumptech.glide.Glide; +import com.bumptech.glide.GlideBuilder; +import com.bumptech.glide.module.GlideModule; + +import java.io.InputStream; + +/** + * {@link com.bumptech.glide.module.GlideModule} implementation for the Giphy sample app. + */ +public class GiphyGlideModule implements GlideModule { + @Override + public void applyOptions(Context context, GlideBuilder builder) { + // Do nothing. + } + + @Override + public void registerComponents(Context context, Glide glide) { + glide.register(Api.GifResult.class, InputStream.class, new GiphyModelLoader.Factory()); + } +} diff --git a/samples/giphy/src/main/java/com/bumptech/glide/samples/giphy/MainActivity.java b/samples/giphy/src/main/java/com/bumptech/glide/samples/giphy/MainActivity.java index e5dd70310e..a99cbdc251 100644 --- a/samples/giphy/src/main/java/com/bumptech/glide/samples/giphy/MainActivity.java +++ b/samples/giphy/src/main/java/com/bumptech/glide/samples/giphy/MainActivity.java @@ -20,7 +20,6 @@ import com.bumptech.glide.load.engine.DiskCacheStrategy; import com.bumptech.glide.util.ViewPreloadSizeProvider; -import java.io.InputStream; import java.util.ArrayList; import java.util.List; @@ -31,15 +30,12 @@ public class MainActivity extends Activity implements Api.Monitor { private static final String TAG = "GiphyActivity"; private GifAdapter adapter; - private DrawableRequestBuilder gifItemRequest; - private ViewPreloadSizeProvider preloadSizeProvider; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); - Glide.get(this).register(Api.GifResult.class, InputStream.class, new GiphyModelLoader.Factory()); ImageView giphyLogoView = (ImageView) findViewById(R.id.giphy_logo_view); Glide.with(this) @@ -49,12 +45,12 @@ protected void onCreate(Bundle savedInstanceState) { ListView gifList = (ListView) findViewById(R.id.gif_list); - gifItemRequest = Glide.with(this) + DrawableRequestBuilder gifItemRequest = Glide.with(this) .from(Api.GifResult.class) .diskCacheStrategy(DiskCacheStrategy.SOURCE) .fitCenter(); - preloadSizeProvider = new ViewPreloadSizeProvider(); + ViewPreloadSizeProvider preloadSizeProvider = new ViewPreloadSizeProvider(); adapter = new GifAdapter(this, gifItemRequest, preloadSizeProvider); gifList.setAdapter(adapter); ListPreloader preloader = new ListPreloader(adapter, preloadSizeProvider, 2);