-
Notifications
You must be signed in to change notification settings - Fork 1.8k
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
Conversation
src/Core/src/ImageSources/UriImageSourceService/UriImageSourceService.Android.cs
Outdated
Show resolved
Hide resolved
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.
@@ -13,24 +13,24 @@ | |||
Style="{StaticResource Headline}"/> | |||
<Image | |||
Source="https://aka.ms/campus.jpg" | |||
HeightRequest="200" |
There was a problem hiding this comment.
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...
src/Core/src/ImageSources/Android/Glide/RequestBuilderExtensions.cs
Outdated
Show resolved
Hide resolved
var glyph = fontImageSource.Glyph; | ||
|
||
var size = FontManager.GetFontSize(fontImageSource.Font); | ||
var unit = fontImageSource.FontAutoScalingEnabled ? ComplexUnitType.Sp : ComplexUnitType.Dip; |
There was a problem hiding this comment.
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.
src/Core/src/ImageSources/FontImageSourceService/FontImageSourceService.Android.cs
Outdated
Show resolved
Hide resolved
var builder = glide | ||
.Load(fileImageSource.File, imageView.Context!) | ||
.AddListener(listener); |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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?
There was a problem hiding this 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.
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 |
There was a problem hiding this comment.
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#.
There was a problem hiding this comment.
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.
public static void loadFromResourceId(ImageView imageView, int resourceId, ImageLoaderCallback callback) | ||
{ | ||
imageView.setImageResource(resourceId); | ||
callback.onComplete(true, null, null); | ||
} |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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
.
There was a problem hiding this comment.
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> { |
There was a problem hiding this comment.
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:
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.
There was a problem hiding this comment.
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.
Original logic was a bit incorrect from reworking this code.
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.
This makes use of the Glide API's for loading into the ImageView directly instead of returning a Drawable.