From e6a4ea3642159d62fc53389875e2ea890cc430b9 Mon Sep 17 00:00:00 2001 From: Sam Judd Date: Wed, 21 Jan 2015 09:39:26 -0800 Subject: [PATCH] Add GlideModule for lazily initialization. Fixes #299. --- .../okhttp/src/main/AndroidManifest.xml | 7 +- .../integration/okhttp/OkHttpGlideModule.java | 27 ++++ .../volley/src/main/AndroidManifest.xml | 6 +- .../integration/volley/VolleyGlideModule.java | 27 ++++ .../glide/module/ManifestParserTest.java | 145 ++++++++++++++++++ .../main/java/com/bumptech/glide/Glide.java | 6 + .../bumptech/glide/module/GlideModule.java | 57 +++++++ .../bumptech/glide/module/ManifestParser.java | 63 ++++++++ samples/flickr/src/main/AndroidManifest.xml | 3 + .../samples/flickr/FlickrGlideModule.java | 19 +++ .../samples/flickr/FlickrSearchActivity.java | 2 - samples/giphy/src/main/AndroidManifest.xml | 4 + .../glide/samples/giphy/GiphyGlideModule.java | 18 +++ .../glide/samples/giphy/MainActivity.java | 8 +- 14 files changed, 381 insertions(+), 11 deletions(-) create mode 100644 integration/okhttp/src/main/java/com/bumptech/glide/integration/okhttp/OkHttpGlideModule.java create mode 100644 integration/volley/src/main/java/com/bumptech/glide/integration/volley/VolleyGlideModule.java create mode 100644 library/src/androidTest/java/com/bumptech/glide/module/ManifestParserTest.java create mode 100644 library/src/main/java/com/bumptech/glide/module/GlideModule.java create mode 100644 library/src/main/java/com/bumptech/glide/module/ManifestParser.java create mode 100644 samples/flickr/src/main/java/com/bumptech/glide/samples/flickr/FlickrGlideModule.java create mode 100644 samples/giphy/src/main/java/com/bumptech/glide/samples/giphy/GiphyGlideModule.java diff --git a/integration/okhttp/src/main/AndroidManifest.xml b/integration/okhttp/src/main/AndroidManifest.xml index 53b73913d..4ab340867 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 000000000..1c780d879 --- /dev/null +++ b/integration/okhttp/src/main/java/com/bumptech/glide/integration/okhttp/OkHttpGlideModule.java @@ -0,0 +1,27 @@ +package com.bumptech.glide.integration.okhttp; + +import android.content.Context; + +import com.bumptech.glide.Glide; +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 initialize(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 c2d679aab..554a22600 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 000000000..5db09b365 --- /dev/null +++ b/integration/volley/src/main/java/com/bumptech/glide/integration/volley/VolleyGlideModule.java @@ -0,0 +1,27 @@ +package com.bumptech.glide.integration.volley; + +import android.content.Context; + +import com.bumptech.glide.Glide; +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 initialize(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 000000000..0e8914a1d --- /dev/null +++ b/library/src/androidTest/java/com/bumptech/glide/module/ManifestParserTest.java @@ -0,0 +1,145 @@ +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 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 initialize(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 initialize(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 9461b2522..4c0569a9f 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.GlideBitmapDrawableTranscoder; 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; @@ -241,6 +243,10 @@ public class Glide { bitmapFitCenter = new FitCenter(bitmapPool); drawableFitCenter = new GifBitmapWrapperTransformation(bitmapPool, bitmapFitCenter); + + for (GlideModule module : new ManifestParser(context).parse()) { + module.initialize(context, this); + } } /** 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 000000000..26217054f --- /dev/null +++ b/library/src/main/java/com/bumptech/glide/module/GlideModule.java @@ -0,0 +1,57 @@ +package com.bumptech.glide.module; + +import android.content.Context; + +import com.bumptech.glide.Glide; + +/** + * An interface allowing lazy registration of {@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 initialize(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. + *

+ */ +public interface GlideModule { + void initialize(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 000000000..4419e03d7 --- /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/samples/flickr/src/main/AndroidManifest.xml b/samples/flickr/src/main/AndroidManifest.xml index 242d2cd15..338a135dc 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 000000000..405acbb0b --- /dev/null +++ b/samples/flickr/src/main/java/com/bumptech/glide/samples/flickr/FlickrGlideModule.java @@ -0,0 +1,19 @@ +package com.bumptech.glide.samples.flickr; + +import android.content.Context; + +import com.bumptech.glide.Glide; +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 initialize(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 66fba9810..4b57a0e33 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.Api; 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 class FlickrSearchActivity extends ActionBarActivity { @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 c5dbccb8f..5d353dcbc 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 000000000..0e5844b2b --- /dev/null +++ b/samples/giphy/src/main/java/com/bumptech/glide/samples/giphy/GiphyGlideModule.java @@ -0,0 +1,18 @@ +package com.bumptech.glide.samples.giphy; + +import android.content.Context; + +import com.bumptech.glide.Glide; +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 initialize(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 e5dd70310..a99cbdc25 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.ListPreloader; 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 @@ public class MainActivity extends Activity implements Api.Monitor { 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); -- GitLab