0, background

The latest work is to make an IM SDK and plug-in for Android.

Browsing pictures in social software is a basic function, and our IM is no exception. It supports basic operations such as sending and receiving pictures and previewing pictures. However, with the advent of the bucket image era, higher requirements for IM image processing, IM PC has begun to support the sending of GIF image messages, so Android is also ready to support the sending, receiving and displaying of GIF images.

Here are some of the IM photo library requirements:

  1. The ability to customize network requests is required because IM chat images need to be verified against Http headers.
  2. Support Gif image format, and need to be able to automatically recognize the image format, without manual judgment
  3. Built-in support for image cutting deformation and other functions
  4. The performance must be good enough, or the IM experience will be severely compromised

Article 2 is the newly added demand, while 1, 3 and 4 are the original demand.

Popular images on Android include libraries like ImageLoader, Picasso, Glide and Fresco.

Originally, IM used Picasso to load pictures. The functions of downloading, loading and caching of pictures were handed over to Picasso. 1, 3 and 4 above were satisfactory. Picasso’s strengths are its small size and speed, and it is made by Square, which works well with its own library (OkHttp, etc.). The downside is that there is no GIF support.

In fact, IM originally also used Gif library Android-gif-drawable, but can only display the Gif has been downloaded. Image download, cache and other functions need to be re-implemented, more trouble.

So after comprehensive consideration, we choose Glide as the new choice of IM photo library. About the photo contrast can refer to these – Android four image cache greatly (Imageloader, Picasso was, Glide, Fresco “) principle, characteristic contrast

So that’s the background


Since Picasso was packaged in IM, the changes to the image library are not extensive, nor are there any advanced principles or techniques, and there have been many tutorials on Glide, so the focus of this article is not to introduce how Glide was connected, but to document some of the problems I encountered in the process. It is convenient for friends who also encounter problems to troubleshoot errors.

The use of Glide

Glide’s API is basically the same as Picasso’s, except that image morphing, placeholder Settings, and error Settings are put into the Apply () method, and it takes a little learning to get the hang of it. As follows:

Glide.with(context)
          .load(path)
          .apply(RequestOptions.centerInsideTransform()
              .error(context.getResources().getDrawable(errorResId))
              .placeholder(context.getResources().getDrawable(placeholderResId)))
          .into(target);
Copy the code

The load() method does not distinguish between URL, File, and drawableID, and does not need to specify JPG or Gif format. It automatically recognizes the image type and completes the loading.

Considerations for Glide to replace Picasso

When we use Picasso or ImageLoader, we have a few usages:

  1. Add a Tag to the ImageView to prevent images from being displayed incorrectly.
  2. In Recyclerview or ListView to load pictures, set sliding listening, scrolling pause loading, stop scrolling restore loading, in order to make smooth scrolling, the disadvantage is that in the process of scrolling, pictures can only display a placeholder, can not dynamically display;
  3. Cancel the loading image when the Activity or Fragment exits.

If you continue to use these methods on Glide, it will cause problems: 1. If the ImageView has a Tag set, Glide will throw an exception, because the Tag is already set inside Glide, so be sure to look for the Tag set in the code; 2, Glide internal judge View in Recyclerview or ListView display state, will automatically judge whether the picture should be suspended loading, sliding display effect is very good, can immediately show the picture to, if still use pause loading operation, may lead to rolling stuck, If you find a problem with caton, be sure to check it out first. 3, Glide must start with Glide. With (), and the parameter is Activity, Fragment, View, or Context. Glide will automatically bind the life cycle of these objects, and automatically cancel the image loading when it exits.

If the width and height of the ImageView are set to WRAP_content, giFs may display incorrectly and become too small, depending on the layout. Adjust the layout properties.

How does Glide realize the verification request when loading network pictures

That is, how to realize the “requirement 1” mentioned above, there are two ways:

  1. Use ModelLoader to customize AppGlideModule, which can customize Glide, in addition to customize network request, can also customize cache policy, this is the official provided method to use ModelLoader
@GlideModule public class YourAppGlideModule extends AppGlideModule { @Override public void registerComponents(Context context, Glide glide, Registry registry) { registry.replace(GlideUrl.class, InputStream.class, new OkHttpUrlLoader.Factory()); }}Copy the code

Do you feel strange, feeling in the clouds, it doesn’t matter, after reading this article you will understand.

  1. Wrap the url of the network request with GlideUrl:
GlideUrl glideUrl = new GlideUrl("url", new LazyHeaders.Builder() .addHeader("key1", "value") .addHeader("key2", new LazyHeaderFactory() { @Override public String buildHeader() { String expensiveAuthHeader = computeExpensiveAuthHeader(); return expensiveAuthHeader; } }) .build()); Glide.... load(glideUrl).... ;Copy the code

Iv. Customization of Glide4.0

As we all know, the most important step is to customize the image library. It is not to rewrite the code, but to set the cache size, cache strategy, network request mode and other functions according to the API provided by the image library, so as to meet the application scenario. Many problems have been encountered in customizing Glide library in IM.

The latest version 4.0 is not much different from the 3.* version, and the custom way is also similar — The Use of Android picture loading library Glide introduction.

Glide starts with 3.0 and customizes Glide with annotations, also known as the GlideModule. GlideModule can be used to: 1, global change glide load policy 2, custom disk cache directory 3, set image load quality 4,… How to operate:

  1. First, define a class that implements GlideModule
@GlideModule public class FlickrGlideModule extends AppGlideModule { @Override public void registerComponents(Context context, Glide glide, Registry registry) { registry.append(Photo.class, InputStream.class, new FlickrModelLoader.Factory()); } @Override public void applyOptions(Context context, GlideBuilder builder) { MemorySizeCalculator calculator = new MemorySizeCalculator.Builder(context) .setMemoryCacheScreens(2) .build(); builder.setMemoryCache(new LruResourceCache(calculator.getMemoryCacheSize())); }}Copy the code
  1. Then declare your GlideModule in androidmanifest.xml:
<meta-data
    android:name="package.path.of.FlickrGlideModule "
    android:value="GlideModule" />
Copy the code

In general, this completes the custom Glide. I haven’t yet understood the benefits of such customization. The following source code is Glide. Java file parse AndroidManifest file and generate Glide object logic:

private static void initializeGlide(Context context) { Context applicationContext = context.getApplicationContext(); GeneratedAppGlideModule annotationGeneratedModule = getAnnotationGeneratedGlideModules(); List<GlideModule> manifestModules = Collections.emptyList(); if (annotationGeneratedModule == null || annotationGeneratedModule.isManifestParsingEnabled()) { manifestModules = new ManifestParser(applicationContext).parse(); }... RequestManagerRetriever.RequestManagerFactory factory = annotationGeneratedModule ! = null ? annotationGeneratedModule.getRequestManagerFactory() : null; GlideBuilder builder = new GlideBuilder() .setRequestManagerFactory(factory); for (GlideModule module : manifestModules) { module.applyOptions(applicationContext, builder); } if (annotationGeneratedModule ! = null) { annotationGeneratedModule.applyOptions(applicationContext, builder); } Glide glide = builder.build(applicationContext); for (GlideModule module : manifestModules) { module.registerComponents(applicationContext, glide, glide.registry); } if (annotationGeneratedModule ! = null) { annotationGeneratedModule.registerComponents(applicationContext, glide, glide.registry); } context.getApplicationContext().registerComponentCallbacks(glide); Glide.glide = glide; }Copy the code

If you look at the call to this method in Glide, you’ll see that an instance of Glide. Glide can only be generated from this method. The GlideModule applyOptions() method is called before Glide is generated, and the registerComponents() method is called after Glide is generated to complete the different stages of customization.

But this creates a problem: Glide must be customized using the GlideModule. IM is mainly used through the form of plug-in, so it can not read the content of their own AndroidManifest file, but IM must customize Glide, in the case of no other method, IM is finally using GlideBuilder to create Glide object, Glide. Glide object is replaced by reflection, so as to achieve the purpose of customization, the code is very simple, will not paste.

Five, IM access Glide in the process of the pit

Please look at the current account:

  1. The first problem is that ordinary pictures (non-GIF) loading is not smooth, the solution is to customize Glide, set up a larger memory cache space, and it was successfully solved;
  2. Gif image loading is not smooth, the result found that Gif image loading is not smooth, Baidu, Google, StackOverflow, the result directly refers to Glide Gif loading efficiency, I mistake it for true, so try to use Glide combined with Android-GIF-drawable implementation: Glide for download, cache, Android-GIF-drawable for display.
  3. Glide combined with Android-GIF-drawable, the next section explains how to do this separately. In short, after the implementation of the very difficult, found…… Or caton, vomiting blood a liter;
  4. Look again… Glide. With (context).pauserequests () deleted by chance. It was so smooth that I found the reason.
  5. Delete Glide combined with Android-GIF-drawable logic, recover Glide load alone, found that it is still smooth, although Glide display GIF or a disadvantage: the same GIF source display animation is synchronous, but also belong to the acceptable range, finish. Lesson learned: you can’t trust all the old information on Baidu, Google, StackOverflow. Glide is progressing and showing high efficiency.

Glide combined with Android-GIF-drawable

A little bit of theory first

By default, Glide determines the image format based on the first two bytes of the image and automatically converts it to the appropriate object to load and display. Glide also opens up the interface so that we can customize the image downloading, loading, decoding, encoding (saving) process. This capability is implemented through the Registry class.

While looking at the source code, while listening to the explanation, the following source code is Glide object constructor, which through Registry registered all the default support for the format processing process, look at the code will find that the parameters are very regular, rough through the code.

Glide(...) {... registry = new Registry(); registry.register(new DefaultImageHeaderParser()); . registry.register(ByteBuffer.class, new ByteBufferEncoder()) .register(InputStream.class, new StreamEncoder(arrayPool)) /* Bitmaps */ .append(ByteBuffer.class, Bitmap.class, new ByteBufferBitmapDecoder(downsampler)) .append(InputStream.class, Bitmap.class, new StreamBitmapDecoder(downsampler, arrayPool)) .append(ParcelFileDescriptor.class, Bitmap.class, new VideoBitmapDecoder(bitmapPool)) .register(Bitmap.class, new BitmapEncoder()) /* GlideBitmapDrawables */ .append(ByteBuffer.class, BitmapDrawable.class, new BitmapDrawableDecoder<>(resources, bitmapPool, new ByteBufferBitmapDecoder(downsampler))) .append(InputStream.class, BitmapDrawable.class, new BitmapDrawableDecoder<>(resources, bitmapPool, new StreamBitmapDecoder(downsampler, arrayPool))) .append(ParcelFileDescriptor.class, BitmapDrawable.class, new BitmapDrawableDecoder<>(resources, bitmapPool, new VideoBitmapDecoder(bitmapPool))) .register(BitmapDrawable.class, new BitmapDrawableEncoder(bitmapPool, new BitmapEncoder())) /* GIFs */ .prepend(InputStream.class, GifDrawable.class, new StreamGifDecoder(registry.getImageHeaderParsers(), byteBufferGifDecoder, arrayPool)) .prepend(ByteBuffer.class, GifDrawable.class, byteBufferGifDecoder) .register(GifDrawable.class, new GifDrawableEncoder()) /* GIF Frames */ .append(GifDecoder.class, GifDecoder.class, new UnitModelLoader.Factory<GifDecoder>()) .append(GifDecoder.class, Bitmap.class, new GifFrameResourceDecoder(bitmapPool)) /* Files */ .register(new ByteBufferRewinder.Factory()) .append(File.class, ByteBuffer.class, new ByteBufferFileLoader.Factory()) .append(File.class, InputStream.class, new FileLoader.StreamFactory()) .append(File.class, File.class, new FileDecoder()) .append(File.class, ParcelFileDescriptor.class, new FileLoader.FileDescriptorFactory()) .append(File.class, File.class, new UnitModelLoader.Factory<File>()) /* Models */ .register(new InputStreamRewinder.Factory(arrayPool)) .append(int.class, InputStream.class, new ResourceLoader.StreamFactory(resources)) .append( int.class, ParcelFileDescriptor.class, new ResourceLoader.FileDescriptorFactory(resources)) .append(Integer.class, InputStream.class, new ResourceLoader.StreamFactory(resources)) .append( Integer.class, ParcelFileDescriptor.class, new ResourceLoader.FileDescriptorFactory(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()) .append(Uri.class, InputStream.class, new HttpUriLoader.Factory()) .append(Uri.class, InputStream.class, new AssetUriLoader.StreamFactory(context.getAssets())) .append( Uri.class, ParcelFileDescriptor.class, new AssetUriLoader.FileDescriptorFactory(context.getAssets())) .append(Uri.class, InputStream.class, new MediaStoreImageThumbLoader.Factory(context)) .append(Uri.class, InputStream.class, new MediaStoreVideoThumbLoader.Factory(context)) .append( Uri.class, 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()) .append(URL.class, InputStream.class, new UrlLoader.StreamFactory()) .append(Uri.class, File.class, new MediaStoreFileLoader.Factory(context)) .append(GlideUrl.class, InputStream.class, new HttpGlideUrlLoader.Factory()) .append(byte[].class, ByteBuffer.class, new ByteArrayLoader.ByteBufferFactory()) .append(byte[].class, InputStream.class, new ByteArrayLoader.StreamFactory()) /* Transcoders */ .register(Bitmap.class, BitmapDrawable.class, new BitmapDrawableTranscoder(resources, bitmapPool)) .register(Bitmap.class, byte[].class, new BitmapBytesTranscoder()) .register(GifDrawable.class, byte[].class, new GifDrawableBytesTranscoder()); ImageViewTargetFactory imageViewTargetFactory = new ImageViewTargetFactory(); glideContext = new GlideContext( context, registry, imageViewTargetFactory, defaultRequestOptions, defaultTransitionOptions, engine, logLevel); }Copy the code

If you look at the code, there seems to be a pattern:

  1. Class, inputStream. class, byteBuffer.class, File., int. J class, the class Integer. Class, String. Class, Uri. Class, URL. The class seems like can indicate the type of source images
  2. Some of parameters of the Bitmap. The class, GifDrawable class seems to be used to indicate the picture type
  3. There are also many Factory, Encoder, Decoder, Transcoder types in the parameter

With that in mind, check out Registy’s source code:

. public <Data> Registry register(Class<Data> dataClass, Encoder<Data> encoder) { encoderRegistry.add(dataClass, encoder);  return this; } public <TResource> Registry register(Class<TResource> resourceClass, ResourceEncoder<TResource> encoder) { resourceEncoderRegistry.add(resourceClass, encoder); return this; } public Registry register(DataRewinder.Factory factory) { dataRewinderRegistry.register(factory); return this; } public <TResource, Transcode> Registry register(Class<TResource> resourceClass, Class<Transcode> transcodeClass, ResourceTranscoder<TResource, Transcode> transcoder) { transcoderRegistry.register(resourceClass, transcodeClass, transcoder); return this; } public <Model, Data> Registry append(Class<Model> modelClass, Class<Data> dataClass, ModelLoaderFactory<Model, Data> factory) { modelLoaderRegistry.append(modelClass, dataClass, factory); return this; }...Copy the code

You will find that the source code uses a word as a generic name: Model, Data, TResource, presumably this has a lot of meaning. I’m not going to keep you in suspense.

  • Model: refers to the type of the image source, such as File, Uri, Url, String, byte[], etc.
  • Data: refers to the type of Data read from the image, such as InputStream, ByteBuffer, etc.
  • TResource: Refers to the type of data parsed, such as Bitmap, GifDrawable, etc.

Factory, ModelLoaderFactory, Encoder, Decoder, Transcoder are the Bridges connecting the above three types:

  • Factory is a Factory class that generates instance objects of type Data.
  • The prototype of ModelLoaderFactory is the generic interface ModelLoader

    , which can also be guessed from the generic parameter name. It is used for conversion from Model to Data;
    ,>
  • Decoder for parsing, from Data to TResource conversion;
  • Encoder for coding, conversion from TResource to Data, generally used to save images to a file cache;
  • Transcoder is used to convert between multiple TResources, such as from Bitmap to BitmapDrawable.

It may be easy to understand the role of each part by showing it with pictures:


Glide data flow chart

It is easy to understand how Glide can automatically recognize the source of the picture, decode the picture, and use It to customize the download, load, decode, encode (save) process of the picture. It’s also easier to look back at the way custom network requests are implemented using ModelLoader in Section 3.

So how do you combine Glide with Android-GIF-drawable

Glide and Android-GIF-drawable (drawable) :

  1. Replace ImageView with GifImageView to display images;

  2. Android-gif-drawable has its own GifDrawable (A), and Glide GifDrawable (B) is not the same, so we need to register A Decoder from Data to GifDrawable (A) conversion;

  3. Since GifDrawable (A) is A default getConstantState() method that returns null, but Glide needs this method, you need to customize ChatGifDrawable to inherit GifDrawable (A) and implement this method.

  4. If you need a Gif file cache, implement an Encoder;

  5. Register these classes and let Glide do the rest of the transformations automatically.

Glide.getRegistry()
   .prepend(InputStream.class, ChatGifDrawable.class,
       new StreamGifDrawableResourceDecoder(sGlide.getRegistry().getImageHeaderParsers(),
           sGlide.getArrayPool()));
Glide.getRegistry()
   .prepend(ByteBuffer.class, ChatGifDrawable.class,
       new ByteBufferGifDrawableResourceDecoder(
           sGlide.getRegistry().getImageHeaderParsers(), Glide.getArrayPool()));
Glide.getRegistry().register(GifDrawable.class, new ChatGifDrawableEncoder())
Copy the code

As for the specific ChatGifDrawable, StreamGifDrawableResourceDecoder, ByteBufferGifDrawableResourceDecoder and ChatGifDrawableEncoder code implementation is not Yes, it’s been deleted from IM anyway…

conclusion

Through this smelly and long ledger, I hope to help you more understand the principle and use of Glide, to avoid similar pit in use again in the future!