Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Loading images from assets #155

Closed
vpratfr opened this issue Sep 24, 2014 · 14 comments
Closed

Loading images from assets #155

vpratfr opened this issue Sep 24, 2014 · 14 comments
Milestone

Comments

@vpratfr
Copy link

vpratfr commented Sep 24, 2014

Hi,

I have switched my current project from Picasso to Glide. However, I cannot manage to load an image from the application assets. Is that even possible?

Sample code:

String assetPath = "file:///android_asset/flags/FR.jpg";
Glide.with(getContext())
                .load(Uri.parse(assetPath))
                .placeholder(missingFlagDrawable)
                .centerCrop()
                .crossFade()
                .into(flag);

I have also tried the load(String) method without success and I don't see a load method taking for instance an InputStream or a FileDescriptor I could have obtained with the AssetManager class.

That exact same code was working in Picasso.

Glide is working properly in the rest of the app and loading nicely remote images.

@sjudd
Copy link
Collaborator

sjudd commented Sep 24, 2014

Thanks for reporting this, in theory the Uri is sufficient. What failure do you see (if you don't see any exception you can turn on logging using adb shell setprop log.tag.GenericRequest, see https://github.com/bumptech/glide/wiki/Debugging-and-Error-Handling)?

If using the AssetManager is a requirement you can always define a custom ModelLoader that uses the AssetManager to return an InputStream and pass it in to your request with the using() syntax:

Glide.with(yourFragment)
    .using(yourCustomModelLoader)
    .load(Uri.parse(assetPath))
   ...

If we need to use the AssetManager to parse these urls rather than just relying on a ContentResolver, the best fix for the library would be to modify the existing UriLoader and adding another case if we're given an asset Uri.

@vpratfr
Copy link
Author

vpratfr commented Sep 24, 2014

I get this exception thrown:

09-24 17:58:12.790  22860-22860/com.example D/GenericRequest﹕ load failed
java.io.FileNotFoundException: No such file or directory
        at android.os.Parcel.openFileDescriptor(Native Method)
        at android.os.ParcelFileDescriptor.openInternal(ParcelFileDescriptor.java:252)
        at android.os.ParcelFileDescriptor.open(ParcelFileDescriptor.java:198)
        at android.content.ContentResolver.openAssetFileDescriptor(ContentResolver.java:899)
        at android.content.ContentResolver.openAssetFileDescriptor(ContentResolver.java:831)
        at com.bumptech.glide.load.data.FileDescriptorLocalUriFetcher.loadResource(FileDescriptorLocalUriFetcher.java:20)
        at com.bumptech.glide.load.data.FileDescriptorLocalUriFetcher.loadResource(FileDescriptorLocalUriFetcher.java:13)
        at com.bumptech.glide.load.data.LocalUriFetcher.loadData(LocalUriFetcher.java:49)
        at com.bumptech.glide.load.data.LocalUriFetcher.loadData(LocalUriFetcher.java:21)
        at com.bumptech.glide.load.model.ImageVideoModelLoader$ImageVideoFetcher.loadData(ImageVideoModelLoader.java:82)
        at com.bumptech.glide.load.model.ImageVideoModelLoader$ImageVideoFetcher.loadData(ImageVideoModelLoader.java:52)
        at com.bumptech.glide.load.engine.SourceResourceRunner.decodeFromSource(SourceResourceRunner.java:178)
        at com.bumptech.glide.load.engine.SourceResourceRunner.runWrapped(SourceResourceRunner.java:144)
        at com.bumptech.glide.load.engine.SourceResourceRunner.run(SourceResourceRunner.java:123)
        at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:422)
        at java.util.concurrent.FutureTask.run(FutureTask.java:237)
        at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1112)
        at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:587)
        at java.lang.Thread.run(Thread.java:841)
        at com.bumptech.glide.load.engine.executor.FifoPriorityThreadPoolExecutor$DefaultThreadFactory$1.run(FifoPriorityThreadPoolExecutor.java:52)

However the file does exist in the asset folder. By using a custom loader/fetcher, I manage to load those files properly. Here are the corresponding classes: https://gist.github.com/vpratfr/5e61fd452a27e964ea63

Then I am loading the assets with:

Glide.with(getContext())
                .using(new AssetUriLoader(getContext()))
                .load(Uri.parse(flagAsset.get()))

So something is wrong with the default loading, I don't know if that's a bug on your side or on mine.

One problem remains : inside a GridView (probably in a ListView too), I cannot see the image without scrolling the view out of sight and getting it back on screen. Does it have something to do with caching? Or with my custom loader/fetcher?

@sjudd
Copy link
Collaborator

sjudd commented Sep 25, 2014

Thanks for the code and report I'm looking into asset manager uris.

I'm not sure where to point you to with regards to the GridView issue. Glide's Flickr sample app uses grid views without a problem. The only problem I see in your code is that you don't need to use a weak reference for a Context in your DataFetcher. Instead just pass in the application Context (context.getApplicationContext()). That said, I don't think using a weak reference would cause the image to fail to load.

Do you see a debug log when the image fails to load the first time? What width and height do your views have?

@TWiStErRob
Copy link
Collaborator

As it should be loaded

image.setImageBitmap(BitmapFactory.decodeStream(getAssets().open("test.jpg")));

As @vpratfr wants to load it

image.setImageURI(Uri.parse("file:///android_asset/test.jpg"));

it gives the following messages:

java.io.FileNotFoundException: /android_asset/test.jpg: open failed: ENOENT (No such file or directory)
resolveUri failed on bad bitmap uri: file:///android_asset/test.jpg

and it is because ContentResolver doesn't support android_asset Uris (see openInputStream and openAssetFileDescriptor): https://code.google.com/p/android/issues/detail?id=42675

Why Picasso works

we want the following to work (which is roughly equivalent to the above setImageURI version):

Glide.with(this).load("file:///android_asset/test.jpg").into(image);

java.io.FileNotFoundException: No such file or directory

It fails because Glide doesn't handle the android_asset "folder":
https://github.com/bumptech/glide/blob/v3.3.1/library/src/main/java/com/bumptech/glide/load/model/UriLoader.java#L29
compare that to:
late AssetBitmapHunter which is now AssetRequestHandler

Solution

Add if to UriLoader for checking "root folder" android_asset and implement an AssetFetcher like @vpratfr did; however I think relativePath.replace("file:///android_asset/", ""); should be outside of the class in UriLoader, because there is no such thing as an "asset Uri" in the Android framework, assets are referenced by relative path to the assets folder.

Other comments

As for the GridView, it really depends on how you declared the ImageView in XML and what methods you call on them. Aside @sjudd's suggestions you can try adding a .listener() and log/debug whether the Bitmap arrives at the target when it's not shown.

I agree that these kind of Uris should be supported for ease of use, but why don't you put your images into raw or drawable-nodpi, or even simpler: use drawables?

@vpratfr
Copy link
Author

vpratfr commented Sep 25, 2014

Hi,

Currently doing some other tasks, will try to debug the GridView in the afternoon. Thanks for having had a look at it.

I think too the right thing to do would be to add an additional if statement in the UriLoader class to detect assets and use an appropriate Fetcher.

I have not directly implemented it in the library and submitted a PR because I still have not taken the time to understand the Model things and would not want to break something (and honestly don't have much because my client is pushing). From the code I sent I think that should be an easy fix though.

I agree that these kind of Uris should be supported for ease of use, but why don't you put your images into raw or drawable-nodpi, or even simpler: use drawables?

Because the file name is dynamically determined. Those are flags and I get the country list from a webservice. The flag name is taken from the ISO code (for instance FR.png).

If I was using a resource, that name would get compiled to an int (R.raw.FR) and I would need some reflection magic (or an ugly switch statement) to link the ISO code to the flag resource.

Hence the asset is required in my case.

@TWiStErRob
Copy link
Collaborator

If I was using a resource, that name would get compiled to an int (R.raw.FR) and I would need some reflection magic (or an ugly switch statement) to link the ISO code to the flag resource.

... or you could use the method provided by the framework:

public static int getRawResourceID(Context context, String rawResourceName) {
    return context.getResources().getIdentifier(rawResourceName, "raw", context.getPackageName());
}
public static int getDrawableResourceID(Context context, String drawableResourceName) {
    return context.getResources().getIdentifier(drawableResourceName, "drawable", context.getPackageName());
}

I'm doing the same, I have a DB table for categories and the icon name is stored there (I'm loading SVG files from raw this way via Glide). Also "ugly switch" usually converts nicely to lookup Map.

Or if you really want to pass Uri around:

android.resource://[package]/[res type]/[res name]
android.resource://com.your.employer/drawable/ic_launcher

This even works if you put it in XML (android:src), because ContentResolver/Android framework supports this. I think the above uses the same method I laid out above, but also involves parsing the Uri first.

@vpratfr
Copy link
Author

vpratfr commented Sep 25, 2014

Ok. Back with some tests. I have implemented SVG support (using your sample as a base) and I am now using the method you describe (SVGs in the raw resource folder and some low-res versions in the drawable resource folder).

More info about the GridView bug:
1.
I am using com.makeramen.RoundedImageView to host the image. That is a subclasse of ImageView.

SVGs get properly loaded in that view. Drawables however are only loaded when the GridView row is out of sight and back (using an adapter and following the ViewHolder pattern).
1.
If I switch to Picasso, drawables are properly loaded in that RoundedImageView. Apart from the Glide.with... code changed to the similar Picasso.with... code, all the rest is the same.

So my conclusion : Picasso is doing something different than Glide when it comes to setting a drawable.

My first guess : it may be related to an issue logged in the RoundedImageView repo if you are using the setImageURI method and Picasso is using another method (like directly calling setImageResource). This would explain too why SVGs get shown properly as they are not using the setImageURI method but probably setImageDrawable instead.

@sjudd
Copy link
Collaborator

sjudd commented Sep 25, 2014

What api level are you using? Not 4.0.4 by any chance?

If you use Glide.with(xxx).load(xxx).asBitmap() does it work as you expect?

Glide uses setImageDrawable, not setImageUri. Using asBitmap() will cause it to use setImageBitmap instead.

Thanks for looking more into this!

@vpratfr
Copy link
Author

vpratfr commented Sep 25, 2014

ANDROID_BUILD_MIN_SDK_VERSION=15
ANDROID_BUILD_TARGET_SDK_VERSION=20

Testing on Android 4.4.4 (Galaxy Note 2 with Slimkat ROM)

@vpratfr
Copy link
Author

vpratfr commented Sep 25, 2014

It works when using asBistmap

@sjudd
Copy link
Collaborator

sjudd commented Sep 26, 2014

Any chance you could add a bit of code or a sample project demonstrating the issue? Even just the xml you're using to specify your grid and the items in it would be helpful.

As I mentioned earlier I know the drawables can work in a GridView because the Flickr sample works fine, but clearly something is a bit different here.

Thanks for the follow up.

@sjudd
Copy link
Collaborator

sjudd commented Sep 28, 2014

I spent a little while looking in to this, it seems like RoundedImageView doesn't handle TransitionDrawables quite right. Removing the crossfade using dontAnimate() or using a normal fade in solved the problem. Using asBitmap() will also work because Glide doesn't allow you to cross fade bitmaps.

I think the ideal solution is to follow the pattern the author setup for Picasso and create your own custom Glide transformation: https://github.com/vinc3m1/RoundedImageView/blob/master/roundedimageview/src/main/java/com/makeramen/RoundedTransformationBuilder.java.

I'll leave this open to track the asset manager uris, but otherwise everything seems WAI from Glide's point of view.

@sjudd sjudd closed this as completed in b51cd25 Sep 28, 2014
@sjudd sjudd added this to the 3.4 milestone Dec 26, 2014
@androiddeveloperPooja30

Hi,
We are updating image but does not change file path. In this case, glide does not refresh image.
Please tell me how to resolve this issue.

Thanks in advance.

@sjudd
Copy link
Collaborator

sjudd commented Feb 12, 2015

@androiddeveloperPooja30 Check out the wiki on cache invalidation: https://github.com/bumptech/glide/wiki/Caching-and-Cache-Invalidation#custom-cache-invalidation. You will want to pass in a new signature each time you update the file.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

4 participants