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

Improve Glide usage for Image Loading on Android #5198

Merged
merged 35 commits into from
Mar 24, 2022
Merged

Improve Glide usage for Image Loading on Android #5198

merged 35 commits into from
Mar 24, 2022

Conversation

Redth
Copy link
Member

@Redth Redth commented Mar 10, 2022

This makes use of the Glide API's for loading into the ImageView directly instead of returning a Drawable.

Redth added 4 commits March 14, 2022 12:11
The old one was a png and blurry at the resizes, this one is svg to get the right density versions to make it clear we don't have a scaling issue in the code.
@Redth Redth requested a review from jonathanpeppers March 14, 2022 18:21
@Redth Redth marked this pull request as ready for review March 14, 2022 18:25
@@ -13,24 +13,24 @@
Style="{StaticResource Headline}"/>
<Image
Source="https://aka.ms/campus.jpg"
HeightRequest="200"
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can make requests here, but if we just leave the default, Glide will try and make the imageview bigger which seems to play well with layouts so far...

var glyph = fontImageSource.Glyph;

var size = FontManager.GetFontSize(fontImageSource.Font);
var unit = fontImageSource.FontAutoScalingEnabled ? ComplexUnitType.Sp : ComplexUnitType.Dip;
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This uses SP units if we have font auto scaling enabled, otherwise just DP... The SP ones will scale with font accessibility settings on the device.

Comment on lines 23 to 25
var builder = glide
.Load(fileImageSource.File, imageView.Context!)
.AddListener(listener);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I should add the benchmarks from here:

main...jonathanpeppers:ImageBenchmarks

The problem I was seeing was that ImageView.Set* was so much faster than Glide:

Method Mean Error StdDev Gen 0 Allocated
SetImageResource 7.631 µs 0.0165 µs 0.0146 µs - -
SetImageDrawable 55.096 µs 1.2665 µs 3.6135 µs 0.1221 736 B
GlideWithTarget 209.044 µs 4.3352 µs 12.2275 µs 0.2441 1,544 B

It could be there is just way more interop from C# to Java? The example above would many extra JNI calls.

We would potentially have to write the Glide code in Java so we can do a single interop call?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah that was my thought too... we really just want to throw the filename/url/font info/stream over the fence to java, in a single interop call and then wait for the callback.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jonathanpeppers I moved most of the logic into the android aar project... Would you be able to test the timings here again?

@Redth Redth changed the title [WIP] Improve Glide usage for Image Loading on Android Improve Glide usage for Image Loading on Android Mar 14, 2022
Copy link
Member

@jonathanpeppers jonathanpeppers left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can probably add my benchmark to this branch and get an idea of the time here:

main...jonathanpeppers:ImageBenchmarks

But I will be FTO tomorrow and back on Thursday.

Comment on lines 9 to 28
internal class ImageLoaderCallback : Java.Lang.Object, IImageLoaderCallback
{
TaskCompletionSource<IImageSourceServiceResult<bool>> tcsResult = new();

public Task<IImageSourceServiceResult<bool>> Result
=> tcsResult.Task;

public void OnComplete(Java.Lang.Boolean? success, Java.Lang.IRunnable? dispose)
{
var s = success?.BooleanValue() ?? false;

Action? disposeWrapper = null;
if (dispose != null)
disposeWrapper = dispose.Run;

tcsResult.TrySetResult(new ImageSourceServiceResult(s, disposeWrapper));
}
}

internal class ImageLoaderDrawableCallback : Java.Lang.Object, IImageLoaderDrawableCallback
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a way these two could be the same type? You pay a cost per C# type that subclasses Java or per Java type you call from C#.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Made these share a type as suggested.

Comment on lines 26 to 30
public static void loadFromResourceId(ImageView imageView, int resourceId, ImageLoaderCallback callback)
{
imageView.setImageResource(resourceId);
callback.onComplete(true, null, null);
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could this one not have a callback at all? Then the calling code would just assume it's synchronous and finished.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh if this is synchronous, you can probably delete this method and just call ImageView.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed this. I was already bypassing it but forgot one place.

So really, testing loading a file is kind of unnecessary for resource drawable files since they'll never hit this code path now, as we check for the drawable reasource id by filename and use imageView.SetImageResourceId(..) with it instead of calling out to this Image Loader.

It's really just files elsewhere on disk that will hit this path now I think, and fonts, and urls.

import com.bumptech.glide.load.engine.bitmap_recycle.BitmapPoolAdapter;
import com.bumptech.glide.load.resource.bitmap.BitmapResource;

public class FontModelResourceDecoder implements ResourceDecoder<FontModel, Bitmap> {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If classes like this are only used from Java, try removing:

Suggested change
public class FontModelResourceDecoder implements ResourceDecoder<FontModel, Bitmap> {
class FontModelResourceDecoder implements ResourceDecoder<FontModel, Bitmap> {

And see if that prevents us getting a C# binding for this type.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We don't get bindings for these already as I've <remove-node'd them.

Redth added 6 commits March 23, 2022 16:58
If we don't run these on the main thread we can see: Java.Lang.IllegalStateException : You can't start or clear loads in RequestListener or Target callbacks. If you're trying to start a fallback request when a load fails, use RequestBuilder#error(RequestBuilder). Otherwise consider posting your into() or clear() calls to the main thread using a Handler instead.
@Redth Redth merged commit 680f1fb into main Mar 24, 2022
@Redth Redth deleted the glidier branch March 24, 2022 14:56
@github-actions github-actions bot locked and limited conversation to collaborators Dec 23, 2023
@samhouts samhouts added the fixed-in-6.0.300-rc.1 Look for this fix in 6.0.300-rc.1! label Aug 2, 2024
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
fixed-in-6.0.300-rc.1 Look for this fix in 6.0.300-rc.1!
Projects
None yet
Development

Successfully merging this pull request may close these issues.

[Bug] ImageButtons Aspect behave diferent depending on platform
4 participants