Hot questions for Using Glide in image loading

Top 10 Java Open Source / Glide / image loading

Question:

I am using Glide image loader to load an image from a specific URL, now if I update the image to the same URL, Glide is still showing the cached image in my imageview. How to reload the image from the same URL?


Answer:

As per the Glide wiki Caching-and-Cache-Invalidation

Option 1 (Glide v4): Use ObjectKey to change the file's date modified time.

Glide.with(requireContext())
    .load(myUrl)
    .signature(ObjectKey(System.currentTimeMillis().toString()))
    .into(myImageView)

Option 1 (Glide v3): Use StringSignature to change the file's date modified time.

URLs - Although the best way to invalidate URLs is to make sure the server changes the URL and updates the client when the content at the URL changes, you can also use StringSignature to mix in arbitrary metadata

Glide.with(yourFragment)
    .load(url)
    .signature(new StringSignature(String.valueOf(System.currentTimeMillis()))
    .into(yourImageView);

If all else fails and you can neither change your identifier nor keep track of any reasonable version metadata,

Option 2: You can also disable disk caching entirely using diskCacheStrategy() and DiskCacheStrategy.NONE

Glide.with(Activity.this).load(url)
    .diskCacheStrategy(DiskCacheStrategy.NONE )
    .skipMemoryCache(true)
    .into(imageView);

Reference: https://github.com/bumptech/glide/wiki/Caching-and-Cache-Invalidation

Question:

I used the `Glide library with AppGlideModule, version of library 4.1.1. Here is my glide module class:

@GlideModule
public class GlideUtil extends AppGlideModule {

    private final int IMAGE_CACHE_SIZE = 20 * 1024 * 1024; // 20 MB
    private final String IMAGE_FOLDER = "/User/Images";

    @Override
    public void applyOptions(Context context, GlideBuilder builder) {
        RequestOptions requestOptions = new RequestOptions();
        requestOptions.format(DecodeFormat.PREFER_ARGB_8888);
        requestOptions.diskCacheStrategy(DiskCacheStrategy.ALL);
        builder.setDefaultRequestOptions(requestOptions);
        InternalCacheDiskCacheFactory factory = new InternalCacheDiskCacheFactory(context, IMAGE_FOLDER, IMAGE_CACHE_SIZE);
        builder.setDiskCache(factory);

    }

    @Override
    public boolean isManifestParsingEnabled() {
        return false;
    }

This code worked successfully. But when i updated version of glide library to 4.3.1

compile 'com.github.bumptech.glide:glide:4.3.1' 
annotationProcessor 'com.github.bumptech.glide:compiler:4.3.1'

in GlideUtil class i saw messages: "The result of format is not used", "The result of diskCacheStrategyis not used":

So, how to resolve this? And do the methods diskCacheStrategy and format work on Glide 4.3.1 ?


Answer:

The problem is, that you are not using the builder object, that is returned by format(), thus your actions become pointless, that's why lint warns you. You can see that method annotated with @CheckResult, that's how lint understands, that you are on a wrong way, because you are "not checking the result" returned by that method.

Instead perform following:


    RequestOptions requestOptions = new RequestOptions();
    requestOptions = requestOptions.format(DecodeFormat.PREFER_ARGB_8888);
    requestOptions = requestOptions.diskCacheStrategy(DiskCacheStrategy.ALL);

Now the warning would be gone.

Or you may perform following directly:


    builder.setDefaultRequestOptions(new RequestOptions()
                                        .format(DecodeFormat.PREFER_ARGB_8888)
                                        .diskCacheStrategy(DiskCacheStrategy.ALL));

Question:

I am using Glide library for image loading. There is lot of images in my app so I want to clear cache once cache size is larger than 50 mb. Can someone help me to do so?


Answer:

Call Glide.get(context).clearDiskCache() on outside the UI thread. (also consider clearMemory() too to prevent surprises after clearing disk cache) this worked for me

new Thread(new Runnable() {
          @Override
          public void run() {
             Glide.get(MainActivity.this).clearDiskCache();
          }
     }).start();

There is lot of images in my app so I want to clear cache once cache size is larger than 50 mb.

in case you want to put limit 50 mb you can implement glide module

import android.annotation.TargetApi;
import android.content.Context;
import android.os.Build;
import android.os.Environment;
import android.os.StatFs;
import android.util.Log;

import com.bumptech.glide.Glide;
import com.bumptech.glide.GlideBuilder;
import com.bumptech.glide.load.engine.cache.InternalCacheDiskCacheFactory;
import com.bumptech.glide.module.GlideModule;
import com.example.MyApplication;

import java.util.Locale;

public class LimitCacheSizeGlideModule implements GlideModule {
    // Modern device should have 8GB (=7.45GiB) or more!
    private static final int SMALL_INTERNAL_STORAGE_THRESHOLD_GIB = 6;
    private static final int DISK_CACHE_SIZE_FOR_SMALL_INTERNAL_STORAGE_MIB = 50*1024*1024;

    @Override
    public void applyOptions(Context context, GlideBuilder builder) {
        if (MyApplication.from(context).isTest()) return; // NOTE: StatFs will crash on robolectric.

        double totalGiB = getTotalBytesOfInternalStorage() / 1024.0 / 1024.0 / 1024.0;
        Log.i(String.format(Locale.US, "Internal Storage Size: %.1fGiB", totalGiB));
        if (totalGiB < SMALL_INTERNAL_STORAGE_THRESHOLD_GIB) {
            Log.i("Limiting image cache size to " + DISK_CACHE_SIZE_FOR_SMALL_INTERNAL_STORAGE_MIB + "MiB");
            builder.setDiskCache(new InternalCacheDiskCacheFactory(context, DISK_CACHE_SIZE_FOR_SMALL_INTERNAL_STORAGE_MIB));
        }
    }

    @Override
    public void registerComponents(Context context, Glide glide) {
    }

    private long getTotalBytesOfInternalStorage() {
        // http://stackoverflow.com/a/4595449/1474113
        StatFs stat = new StatFs(Environment.getDataDirectory().getPath());
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) {
            return getTotalBytesOfInternalStorageWithStatFs(stat);
        } else {
            return getTotalBytesOfInternalStorageWithStatFsPreJBMR2(stat);
        }
    }

    @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR2)
    private long getTotalBytesOfInternalStorageWithStatFs(StatFs stat) {
        return stat.getTotalBytes();
    }

    @SuppressWarnings("deprecation")
    private long getTotalBytesOfInternalStorageWithStatFsPreJBMR2(StatFs stat) {
        return (long) stat.getBlockSize() * stat.getBlockCount();
    }
}

and then in your manifest add it like this

<manifest

    ...

    <application>

        <meta-data
            android:name="YourPackageNameHere.LimitCacheSizeGlideModule"
            android:value="GlideModule" />

        ...

    </application>
</manifest>    

Question:

Server provides the API to load image through Post request, whose body looks like

{
 "image_id": someId,
 "session_id": "someId"
}
Response - stream. How to download an image using Glide via HTTP POST method?


Answer:

Algorithm for loading images using Glide & Retrofit 2 via HTTP POST method:

1) Create interface, which should be implemented by all requests for image loading via HTTP POST method:

public interface CacheableRequest {
    String getCacheKey();
}

2) Create model to load, which will be used as a parameter for Glide.with(context).load(model):

import java.io.IOException;
import java.io.InputStream;

import okhttp3.ResponseBody;
import retrofit2.Call;

public class RetrofitStream {

    private final Call<ResponseBody> call;
    private final CacheableRequest request;

    public RetrofitStream(Call<ResponseBody> call, CacheableRequest request) {
        this.call = call;
        this.request = request;
    }

    public InputStream getStream() throws IOException {
        return call.execute().body().byteStream();
    }

    public String getCacheKey() {
        return request.getCacheKey();
    }
}

3) Create an implementation of com.bumptech.glide.load.data.DataFetcher:

import android.support.annotation.Nullable;

import com.bumptech.glide.Priority;
import com.bumptech.glide.load.data.DataFetcher;

import java.io.IOException;
import java.io.InputStream;

public class RetrofitFetcher implements DataFetcher<InputStream> {

    private final RetrofitStream retrofitStream;
    private InputStream stream;

    public RetrofitFetcher(RetrofitStream retrofitStream) {
        this.retrofitStream = retrofitStream;
    }

    @Nullable
    @Override
    public InputStream loadData(Priority priority) throws Exception {
        return stream = retrofitStream.getStream();
    }

    @Override
    public void cleanup() {
        if (stream != null) {
            try {
                stream.close();
            } catch (IOException ignored) {
            }
        }
    }

    @Override
    public String getId() {
        return retrofitStream.getCacheKey();
    }

    @Override
    public void cancel() {

    }
}

4) Create an implementation of com.bumptech.glide.load.model.ModelLoader:

import android.content.Context;
import android.support.annotation.Keep;
import android.support.annotation.Nullable;

import com.bumptech.glide.load.data.DataFetcher;
import com.bumptech.glide.load.model.GenericLoaderFactory;
import com.bumptech.glide.load.model.ModelCache;
import com.bumptech.glide.load.model.ModelLoader;
import com.bumptech.glide.load.model.ModelLoaderFactory;

import java.io.InputStream;

public class RetrofitStreamLoader implements ModelLoader<RetrofitStream, InputStream> {

    private final ModelCache<RetrofitStream, RetrofitStream> modelCache;

    @Keep
    public RetrofitStreamLoader() {
        this(null);
    }

    public RetrofitStreamLoader(@Nullable ModelCache<RetrofitStream, RetrofitStream> modelCache) {
        this.modelCache = modelCache;
    }

    @Override
    public DataFetcher<InputStream> getResourceFetcher(RetrofitStream model, int width, int height) {
        // GlideUrls memoize parsed URLs so caching them saves a few object instantiations and time spent parsing urls.
        RetrofitStream stream = model;
        if (modelCache != null) {
            stream = modelCache.get(model, 0, 0);
            if (stream == null) {
                modelCache.put(model, 0, 0, model);
                stream = model;
            }
        }
        return new RetrofitFetcher(stream);
    }

    /**
     * The default factory for {@link RetrofitStreamLoader}s.
     */
    public static class Factory implements ModelLoaderFactory<RetrofitStream, InputStream> {

        private final ModelCache<RetrofitStream, RetrofitStream> modelCache = new ModelCache<>(500);

        @Override
        public ModelLoader<RetrofitStream, InputStream> build(Context context, GenericLoaderFactory factories) {
            return new RetrofitStreamLoader(modelCache);
        }

        @Override
        public void teardown() {
            // Do nothing.
        }
    }
}

5) Create an implementation of GlideModule, which allows configure Glide

import android.content.Context;

import com.bumptech.glide.Glide;
import com.bumptech.glide.GlideBuilder;
import com.bumptech.glide.load.DecodeFormat;
import com.bumptech.glide.load.engine.cache.InternalCacheDiskCacheFactory;
import com.bumptech.glide.module.GlideModule;

import java.io.InputStream;

public class RetrofitGlideModule implements GlideModule {

    private final static int IMAGES_CACHE_MAX_BYTE_SIZE = 20 * 1024 * 1024;
    private final static String IMAGES_CACHE_PATH = "images";

    @Override
    public void applyOptions(Context context, GlideBuilder builder) {
        builder.setDiskCache(new InternalCacheDiskCacheFactory(context, IMAGES_CACHE_PATH, IMAGES_CACHE_MAX_BYTE_SIZE))
                .setDecodeFormat(DecodeFormat.PREFER_RGB_565);
    }

    @Override
    public void registerComponents(Context context, Glide glide) {
        glide.register(RetrofitStream.class, InputStream.class, new RetrofitStreamLoader.Factory());
    }
}

6) Add meta data about created RetrofitGlideModule in AndroidManifest.xml

<application...>
<meta-data
        android:name="<package>.RetrofitGlideModule"
        android:value="GlideModule" />
</application>

Now you can download images next way:

Call<ResponseBody> call = retrofit.getImage(cacheableRequest);
Glide.with(context).load(new RetrofitStream(call, cacheableRequest)).into(imageView);

Question:

I am loading image from server using Glide with this code:

private void loadImage(Context context, String url, ImageView imageView, int x1, int y1, int x2, int y2) {
    Glide.with(context)
       .load(url)
       .into(imageView);
}

I need to show only piece of image in my ImageView. Coordinates of this piece placed in variables x1, x2, y1 and y2. How to cut only needed part of image using Glide?


Answer:

AFAIK, there is no such an API in glide. But you can do that manually:

private void loadImage(Context context, String url, ImageView imageView, int x1, int y1, int x2, int y2) {
    Glide.with(context)
       .load(url)
       .asBitmap()
       .into(new SimpleTarget<Bitmap>(Target.SIZE_ORIGINAL, Target.SIZE_ORIGINAL) {
        @Override
        public void onResourceReady(Bitmap resource, GlideAnimation glideAnimation) {
            Bitmap cropeedBitmap = Bitmap.createBitmap(resource, x1, y1, x2, y2);
            imageView.setImageBitmap(cropeedBitmap);
        }
    });
}

Note, a relatively heavy Bitmap.createBitmap() operation is being performed on the main thread. You should consider doing that in background thread if that affects overall performance.

Question:

Just getting into the Glide image loading library for Android. Currently working with some code from here: https://github.com/bumptech/glide/issues/459

My full project is here is you want to look at it:

https://github.com/mhurwicz/glide02

I'm getting the following exception when I run the app in the emulator in Android Studio:

java.lang.NullPointerException: Attempt to invoke virtual method 'android.content.res.XmlResourceParser android.content.pm.ProviderInfo.loadXmlMetaData(android.content.pm.PackageManager, java.lang.String)' on a null object reference

This is the key statement in MainActivity:

new ShareTask(this).execute("http://thelink");

(thelink is actually goo.gl/gEgYUd -- couldn't leave that in above because stackoverflow doesn't allow URL shorteners. )

Here is my code for the ShareTask class

class ShareTask extends AsyncTask<String, Void, File> {
    private final Context context;

    public ShareTask(Context context) {
        this.context = context;
    }
    @Override protected File doInBackground(String... params) {
        String url = params[0]; // should be easy to extend to share multiple images at once
        try {
            return Glide
                    .with(context)
                    .load(url)
                    .downloadOnly(Target.SIZE_ORIGINAL, Target.SIZE_ORIGINAL)
                    .get() // needs to be called on background thread
                    ;
        } catch (Exception ex) {
            Log.w("SHARE", "Sharing " + url + " failed", ex);
            return null;
        }
    }
    @Override protected void onPostExecute(File result) {
        if (result == null) { return; }
        Uri uri = FileProvider.getUriForFile(context, context.getPackageName(), result);
        share(uri); // startActivity probably needs UI thread
    }

    private void share(Uri result) {
        Intent intent = new Intent(Intent.ACTION_SEND);
        intent.setType("image/jpeg");
        intent.putExtra(Intent.EXTRA_SUBJECT, "Shared image");
        intent.putExtra(Intent.EXTRA_TEXT, "Look what I found!");
        intent.putExtra(Intent.EXTRA_STREAM, result);
        context.startActivity(Intent.createChooser(intent, "Share image"));
    }
}

Using debug, it seems I may be running into trouble at the get() statement. For one thing, the width and the height are very large negative numbers. (See the code highlighted in green below.) Then the get() statement returns null. (See the code highlighted in red below.)

Thanks in advance for any help you can provide!


Answer:

The NPE is coming from FileProvider.getUriForFile because you're passing in the wrong authority. You declared android:authorities="com.example.fileprovider" in the manifest, but you're using the package name at the call. This fails to resolve the info in FileProvider.parsePathStrategy. Match those two strings up and you'll be good to go.

The easiest fix is to use android:authorities="${applicationId}", this leads to 0 hardcoded strings, so you can keep using context.getPackageName().

Regarding your concerns during debug:

  • Target.SIZE_ORIGINAL is declared to be MIN_VALUE, hence the large number
  • it's not returning null, IDEA is just confused about where it is in the method, that return null; shouldn't be executed if it fails in the FileProvider code.
  • doGet(null): null is the timeout here, it's guarded properly in code

I've run the app and weirdly I got a log line saying

W/SHARE: Sharing http://... failed

but not a stack trace, which is weird, because ex cannot be null in a catch!

Question:

I know how to use glide to insert an image into a regual ImageView

(Glide.with(context) .load(url).morepossiblestuff() .into(imageView);)

But now I have an ArrayList into which an image ('till now from the drawable, means as int) and text is inserted and finally this whole item gets displayed as a cardview item in a RecyclerView...

But my RecyclerView has become extremley laggy probably beacause of loading everything directly from the drawable, without being resized(?).

So I tought I'd put the .png's in a folder in firebase..

But if one item looks like this:

        List.add(new Item(R.drawable.resource, "Title", "Description"));

How can I insert an Image from Firebase with glide instead of the R.drawable.resource?

Here's the onBindViewHolder Adapter: (Think you only need this?)

  @Override
public void onBindViewHolder(@NonNull ViewHolder ViewHolder, int position){
    Item currentItem = mList.get(position);
    ViewHolder.Cover.setImageResource(currentItem.getCover());
    ViewHolder.Title.setText(currentItem.getTitle());
    ViewHolder.Description.setText(currentItem.getDescription());

And here's the item:

public class GameItem {
private int Cover;
private String Title;
private String Description;

public Item(int CoverImage, String TextTitle, String TextDescription) {
    Cover = CoverImage;
    Title = TextTitle;
    Description = TextDescription;
}


public int getCover() {
    return Cover;
}

TLDR:

How do I load an image from Firebase into a single element of the ArrayList? Every item is supposed to have different images. If it isn't, how do I smoothen the lag?

Thank you, really.


Answer:

Inside your onBindViewHolder call

Glide.with(ViewHolder.Cover).load(currentItem.getCover()).into(ViewHolder.Cover);