bumptech / glide

An image loading and caching library for Android focused on smooth scrolling
https://bumptech.github.io/glide/
Other
34.67k stars 6.12k forks source link

Glide don't load image when offline #662

Closed mdtuyen closed 9 years ago

mdtuyen commented 9 years ago

I use glide to load picture, my picture have three model:

The first my grid screen load picture with low resolution if sucess and thumbnail if fail

Glide.with(context).load(feedsModel.getImages().getLow_resolution().getUrl()).into(new GlideDrawableImageViewTarget(viewHolder.image.get(j)) {
    @Override
    public void onResourceReady(GlideDrawable resource, GlideAnimation<? super GlideDrawable> animation) {
        super.onResourceReady(resource, animation);
    }

    @Override
    public void onLoadFailed(Exception e, Drawable errorDrawable) {
        Glide.with(context).load(feedsModel.getImages().getThumbnail().getUrl()).into(viewHolder.image.get(j));
    }
});

After click this image it open list image with bigger image i load standard image if success and low image if fail:

Glide.with(context).load(model.getImages().getStandard_resolution().getUrl()).into(new GlideDrawableImageViewTarget(viewHolder2.profile_imagView) {
    @Override
    public void onResourceReady(GlideDrawable resource, GlideAnimation<? super GlideDrawable> animation) {
        super.onResourceReady(resource, animation);
    }

    @Override
    public void onLoadFailed(Exception e, Drawable errorDrawable) {
        Glide.with(context).load(model.getImages().getLow_resolution().getUrl()).into(viewHolder2.profile_imagView);
    }
});

After load grid window, i disable network to work offline. reopen app i see that grid window load but when click any picture list window not load. I think that in grid window i load low resoluton then on list window becase standard image load fail it will load low resolution and it has to show image.

Why in my case i don't see image in list window ?

TWiStErRob commented 9 years ago

Add logging to your targets and also .listener()s to the Glide loads inside onLoadFailed. That should show you what's the order of calls and what's the error (if any). The sizes have to match in order to hit the cached images. Make sure your ImageView in the layout doesn't change.

There are also other methods you can override on Target, I'd also put logging for those.

You could also look into the .thumbnail() API to simplify.

mdtuyen commented 9 years ago

Hi, i add listener and bug. When i open list window glide jump to onLoadFailed:

Unable to resolve host "[low resolution link]": No address associated with hostname

why low resolution link have been load from grid window, it don't load from cached here.

mdtuyen commented 9 years ago

you said: "The sizes have to match in order to hit the cached images. Make sure your ImageView in the layout doesn't change."

in grid window i use three column on a row, but list window only one image fit width of device. imageview size change make glide can't load a link cache ?

TWiStErRob commented 9 years ago

I hope you replaced the URL here for privacy and this is not the actual message: "[low resolution link]".

By default resized image is cached, which means that the actual 3rd-screen wide image is saved in cache. If you go offline and then try to load a screen wide version it'll fail. Try to add.diskCacheStrategy(ALL) to the first (grid) Glide load to save the original as well as the resized version in cache. This means that by visiting the grid you'll have the original image which will be then available when you try to load a bigger version of the same.

mdtuyen commented 9 years ago

I added .diskCacheStrategy(ALL) but evething not change. I don't understand why with a bitmap glide can't load to two imageview with size is difference.

TWiStErRob commented 9 years ago

Oh, wait, I think you need to add ALL to the other one as well. At the first it signifies to save to cache and the second makes Glide read from cache when available and also save if not.

mdtuyen commented 9 years ago

Sorry, i added .diskCacheStrategy(ALL) to all but issue still occur.

TWiStErRob commented 9 years ago

I'm kind of out of ideas here from the info you've given. Can you please try to describe the issue again, in detail, maybe with pictures or video this time?

mdtuyen commented 9 years ago

I make a simple for my issue:

I push Fragment A (Activity contain grid of imageview) to Activity when i click a image Fragment A push to stack, Fragment B contain detail info about feed with this image showed, when cache Fragment A, and Fragment B will view one image link.

TWiStErRob commented 9 years ago

I made an Activtity from all your descriptions, and couldn't make it fail. There's always something loaded, if it is cached; or the error callback is called when all loads fail. Here's the full code I used:

class ThreeLevelModel implements Serializable {
    private static final String PLACEHOLD_IT_FORMAT = // "http://placehold.it/%1$dx%2$d/?text=%3$s %4$s\n(%1$dx%2$d)"
            "https://placeholdit.imgix.net/~text?txtsize=80&txt=%3$s %4$s&w=%1$d&h=%2$d";
    private final String text;
    public ThreeLevelModel(String text) {
        this.text = text;
    }

    public String getStandardUrl() {
        return getLink(1920, 1080, "standard");
    }
    public String getLowUrl() {
        return getLink(1024, 768, "low");
    }
    public String getThumbUrl() {
        return getLink(320, 240, "thumb");
    }
    public String getText() {
        return text;
    }
    private String getLink(int width, int height, String type) {
        return String.format(Locale.ROOT, PLACEHOLD_IT_FORMAT, width, height, text, type);
    }
}

public class TestActivity extends FragmentActivity implements ListFragment.Callback {
    @Override protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        FrameLayout layout = new FrameLayout(this);
        layout.setId(android.R.id.content);
        layout.setLayoutParams(
                new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
        setContentView(layout);

        if (savedInstanceState == null) {
            clearCacheSyncAndHackNetwork();
            getSupportFragmentManager()
                    .beginTransaction()
                    .add(android.R.id.content, new ListFragment())
                    .commit();
        }
    }

    @Override public void selected(int position, ThreeLevelModel model) {
        Bundle args = new Bundle();
        args.putSerializable("model", model);
        Fragment fragment = new DetailFragment();
        fragment.setArguments(args);
        getSupportFragmentManager()
                .beginTransaction()
                .replace(android.R.id.content, fragment)
                .addToBackStack("details")
                .commit();
    }

    private void clearCacheSyncAndHackNetwork() {
        // TODO only for testing: clear all caches before anything is loaded to always have a clean slate
        Glide.get(TestActivity.this).clearMemory();
        final CountDownLatch latch = new CountDownLatch(1);
        new Thread() {
            @Override public void run() {
                Glide.get(TestActivity.this).clearDiskCache();
                latch.countDown();
            }
        }.start();
        try {
            latch.await(); // never do this in production
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        // TODO only for debug: override default Url handler to fail sometimes (50%)
        Glide.get(this).register(GlideUrl.class, InputStream.class, new ModelLoaderFactory<GlideUrl, InputStream>() {
            Random random = new Random(0);
            @Override public ModelLoader<GlideUrl, InputStream> build(Context context, GenericLoaderFactory factories) {
                return new StreamModelLoader<GlideUrl>() {
                    @Override public DataFetcher<InputStream> getResourceFetcher(GlideUrl url, int width, int height) {
                        return random.nextBoolean()? new HttpUrlFetcher(url) : new ImmediateFailureNetworkFetcher(url);
                    }
                };
            }
            @Override public void teardown() {
            }
        });
    }
}

public class ListFragment extends Fragment {
    public interface Callback {
        void selected(int position, ThreeLevelModel model);
    }

    @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        // simulate    <android.support.v7.widget.RecyclerView android:id="@android:id/list"
        // inflate of   android:layout_width="match_parent" android:layout_height="match_parent" />
        RecyclerView view = new RecyclerView(getActivity());
        view.setLayoutParams(
                new LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
        view.setId(android.R.id.list);
        return view;
    }

    @Override public void onViewCreated(View view, Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);
        RecyclerView list = (RecyclerView)view.findViewById(android.R.id.list);
        list.setLayoutManager(new GridLayoutManager(list.getContext(), 2));
        list.setAdapter(new ListAdapter((Callback)getActivity()));
    }

    static class ListAdapter extends RecyclerView.Adapter<ListAdapter.ViewHolder> {
        private final List<ThreeLevelModel> items = Arrays.asList(
                new ThreeLevelModel("first"),
                new ThreeLevelModel("second"),
                new ThreeLevelModel("third"),
                new ThreeLevelModel("fourth"),
                new ThreeLevelModel("fifth"),
                new ThreeLevelModel("sixth"),
                new ThreeLevelModel("seventh"),
                new ThreeLevelModel("eight"),
                new ThreeLevelModel("ninth"),
                new ThreeLevelModel("tenth"),
                new ThreeLevelModel("eleventh"),
                new ThreeLevelModel("twelfth")
        );
        private final Callback cb;

        public ListAdapter(Callback cb) {
            this.cb = cb;
        }

        @Override public int getItemCount() {
            return items.size();
        }
        private ThreeLevelModel getItem(int position) {
            return items.get(position);
        }
        @Override public long getItemId(int position) {
            return getItem(position).getText().hashCode();
        }
        @Override public ViewHolder onCreateViewHolder(ViewGroup parent, int position) {
            ImageView view = new ImageView(parent.getContext());
            int padding = dp(parent.getContext(), 4);
            int height = dp(parent.getContext(), 100);
            MarginLayoutParams params = new MarginLayoutParams(MATCH_PARENT, height);
            params.setMargins(padding, padding, padding, padding);
            view.setLayoutParams(params);
            view.setScaleType(ImageView.ScaleType.CENTER_CROP);
            return new ViewHolder(view, cb);
        }
        private int dp(Context context, float size) {
            return (int)TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, size,
                    context.getResources().getDisplayMetrics());
        }

        @Override public void onBindViewHolder(final ViewHolder holder, int position) {
            holder.bind(getItem(position));
        }

        static class ViewHolder extends RecyclerView.ViewHolder {
            private ImageView image;
            private ThreeLevelModel bound;

            public ViewHolder(View itemView, final Callback cb) {
                super(itemView);
                image = (ImageView)itemView;

                itemView.setOnClickListener(new OnClickListener() {
                    @Override public void onClick(View v) {
                        cb.selected(getAdapterPosition(), bound);
                    }
                });
            }

            private void bind(final ThreeLevelModel model) {
                bound = model;
                Glide
                        .with(itemView.getContext())
                        .load(model.getLowUrl())
                        .diskCacheStrategy(DiskCacheStrategy.ALL)
                        .listener(new LoggingListener<String, GlideDrawable>())
                        .into(new GlideDrawableImageViewTarget(image) {
                            @Override public void onLoadFailed(Exception e, Drawable errorDrawable) {
                                Glide
                                        .with(itemView.getContext())
                                        .load(model.getThumbUrl())
                                            .diskCacheStrategy(DiskCacheStrategy.RESULT) // == default
                                        .error(android.R.color.darker_gray)
                                        .listener(new LoggingListener<String, GlideDrawable>())
                                        .into(image);
                            }
                        });
            }
        }
    }
}

public class DetailFragment extends Fragment {
    protected ImageView imageView;

    @Override public @Nullable View onCreateView(LayoutInflater inflater, ViewGroup container,
            Bundle savedInstanceState) {
        ImageView view = new ImageView(container.getContext());
        view.setId(android.R.id.icon);
        view.setLayoutParams(
                new MarginLayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
        view.setScaleType(ImageView.ScaleType.FIT_CENTER);
        return view;
    }

    @Override public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);
        imageView = (ImageView)view.findViewById(android.R.id.icon);
    }

    @Override public void onActivityCreated(@Nullable Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);
        final ThreeLevelModel model = (ThreeLevelModel)getArguments().getSerializable("model");
        Glide
                .with(this)
                .using(new ImmediateFailureNetworkLoader<String>()) // TODO disables network for debug
                .load(model.getStandardUrl())
                .diskCacheStrategy(DiskCacheStrategy.RESULT) // == default
                .listener(new LoggingListener<String, GlideDrawable>())
                .into(new GlideDrawableImageViewTarget(imageView) {
                    @Override public void onLoadFailed(Exception e, Drawable errorDrawable) {
                        Glide
                                .with(DetailFragment.this)
                                .using(new ImmediateFailureNetworkLoader<String>()) // TODO disables network for debug
                                .load(model.getLowUrl())
                                .diskCacheStrategy(DiskCacheStrategy.ALL)
                                .error(android.R.color.darker_gray)
                                .listener(new LoggingListener<String, GlideDrawable>())
                                .into(imageView);
                    }
                });
    }
}

public class ImmediateFailureNetworkLoader<T> implements StreamModelLoader<T> {
    @Override public DataFetcher<InputStream> getResourceFetcher(final T model, int width, int height) {
        return new ImmediateFailureNetworkFetcher(model);
    }
}

public class ImmediateFailureNetworkFetcher implements DataFetcher<InputStream> {
    private final Object model;
    public ImmediateFailureNetworkFetcher(Object model) {
        this.model = model;
    }
    @Override public InputStream loadData(Priority priority) throws Exception {
        throw new IOException("Fake network error");
    }
    @Override public void cleanup() {
    }
    @Override public String getId() {
        return model.toString();
    }
    @Override public void cancel() {
    }
}
blaksos commented 9 years ago

Hello , I've the same problem Here with Glide. it seems that the RequestListener is never called on my case. I try to load an image from facebook to an image view but the listener is never called. Do you have any clue on this ?

 Glide.with(getContext())
        .load(myUrl)
        .listener(new RequestListener<String, GlideDrawable>() {
            @Override
            public boolean onException(Exception e, String model, Target<GlideDrawable> target, boolean isFirstResource) {
                Log.i("GLIDE", "onException :", e);
                return false;
            }

            @Override
            public boolean onResourceReady(GlideDrawable resource, String model, Target<GlideDrawable> target, boolean isFromMemoryCache, boolean isFirstResource) {
                Log.i("GLIDE", "onResourceReady");
                progressView.setVisibility(View.GONE);
                imageView.setVisibility(View.VISIBLE);
                return false;
            }
        })
        .into(imageView);
TWiStErRob commented 9 years ago

@blaksos Glide reads the ImageView size which is only assigned on layout. Only visible views are laid out for obvious reasons. Make the imageView visible before Glide.with, it should be transparent anyway.

mdtuyen commented 9 years ago

Hi, I added diskCacheStrategy(DiskCacheStrategy.ALL) and many picture have loaded when offline. However some picture error (5-10%).

TWiStErRob commented 9 years ago

Maybe those pictures were not shown in the grid before. You have to be ready for some images missing from cache. A cache is not a reliable way to have an offline experience, it's at best a "best effort" way. Just think about what happens if someone opens your grid without internet connection or it loads the grid, but then the connection is lost.

blaksos commented 9 years ago

@TWiStErRob thanks for your response, that is what I did this morning and it works . Thank you a lot :)

mdtuyen commented 9 years ago

@TWiStErRob Thanks for your help again.