This repository has been deprecated in favor of https://github.com/roubachof/Xamarin.Forms.Nuke
This repository was inspired by Jonathan Peppers GlideX
implementation of the new IImageViewHandler
interface for Xamarin.Forms
(https://github.com/jonathanpeppers/glidex).
Its goal is to provide the same kind of implementation for iOS
, achieving a complete image caching solution for Xamarin.Forms
: you don't have to change any line of your existing project, the Xamarin.Forms
image source handlers will just be overridden with cache-enabled ones.
On iOS
the ImageSourceHandler
is implemented with FFImageLoading
(https://github.com/luberda-molinet/FFImageLoading).
I changed a bit the glidex
benchmark samples to have a more fair comparison. I switched from a random distribution of the images to a deterministic one to be sure we are comparing the same data set.
I used System.Diagnostics.Process.GetCurrentProcess().WorkingSet64
to have the memory workload of the process. The value given in the results are the consumed bytes between the MainPage
and the complete loading of the target page.
For each test:
- Launch simulator
- Wait 4-5 seconds on
MainPage
- Launch a Page
- Scroll till the end of page
- Get consumed bytes in the output window
Page | Data Type | Xamarin.Forms | FFIL.ImageSourceHandler |
---|---|---|---|
GridOnlyRemotePage | Remote only | 247 828 480 | 18 698 240 (-92%) |
GridPage | Remote and local mix | 186 748 928 | 18 644 992 (-90%) |
ViewCellPage | Remote and local mix | 36 646 912 | 17 829 888 (-51%) |
ImageCellPage | Remote and local mix | 81 604 608 | 12 218 368 (-85%) |
HugeImagePage | Local only | 124 104 704 | 11 902 976 (-90%) |
- Install https://www.nuget.org/packages/ffimageloading.imagesourcehandler/ in your xamarin forms iOS project
- Add this Init method after
Forms.Init
call:
Xamarin.Forms.Forms.Init();
FFImageLoading.FormsHandler.Init(debug: false);
LoadApplication(new SDWebImageForms.Sample.Views.App());
- Install https://www.nuget.org/packages/glidex.forms/ in your xamarin forms Android project
- Add this one liner after your app's
Forms.Init
call:
Xamarin.Forms.Forms.Init (this, bundle);
//This forces the custom renderers to be used
Android.Glide.Forms.Init ();
LoadApplication (new App ());
Jou just achieved 90%+ memory reduction when manipulating Image
views on both platforms.
Read the complete post on Sharpnado: https://www.sharpnado.com/ultimate-image-caching
I had an issue with the naming. Following Jonathan's convention was leading me to FFImageLoading.Forms
, and well it's already taken.
So I leaned towards a more describing name:
FFImageLoading.ImageSourceHandler
Otherwise the project structure is the same that GlideX.Forms
really. FFImageLoading
supports the 3 Xamarin.Form
s image sources.
[assembly: ExportImageSourceHandler(
typeof(FileImageSource), typeof(FFImageLoading.ImageSourceHandler))]
[assembly: ExportImageSourceHandler(
typeof(StreamImageSource), typeof(FFImageLoading.ImageSourceHandler))]
[assembly: ExportImageSourceHandler(
typeof(UriImageSource), typeof(FFImageLoading.ImageSourceHandler))]
namespace FFImageLoadingX
{
[Preserve (AllMembers = true)]
public class ImageSourceHandler : IImageSourceHandler
{
public Task<UIImage> LoadImageAsync(
ImageSource imageSource,
CancellationToken cancellationToken = new CancellationToken(),
float scale = 1)
{
return FFImageLoadingHelper.LoadViaFFImageLoading(
imageSource, cancellationToken);
}
}
}
And then FFImageLoadingHelper
is calling the following methods based on the type of the ImageSource
:
private static Task<UIImage> LoadImageAsync(string urlString)
{
return ImageService.Instance
.LoadUrl(urlString)
.AsUIImageAsync();
}
private static Task<UIImage> LoadFileAsync(string filePath)
{
return ImageService.Instance
.LoadFile(filePath)
.AsUIImageAsync();
}
private static Task<UIImage> LoadStreamAsync(StreamImageSource streamSource)
{
return ImageService.Instance
.LoadStream(token => ((IStreamImageSource)streamSource).GetStreamAsync(token))
.AsUIImageAsync();
}
- It handles local files
- UIImage are correctly created (it respects scale factor)
- It's 100% C#
- The library is really small
Well FFImageLoading
is still in my opinion a really decent choice.
Nonetheless, glidex
outperforms FFImageLoading
on Android
.
I ran several tests.
And GlideX
is loading faster that FFImageLoading
everytime.
This is from a cold start:
GlideX | FFImageLoading |
---|---|
On first run FFImageLoading
has a smaller memory footprint. But after some back and forth it uses more memory compared to Glide
. Glide
clearly balances memory and loading speed, we can suspect some advanced memory management / speeding technics.
Also you can use regular Xamarin.Forms
Image
view instead of a custom view.
It really shines on existing projects: if you want to give a boost to your Xamarin.Forms
app, you just have to install 2 nuget packages and BOOM -95% of memory used :)
It's not the philosophy of the lib. The lib is clearly a 100% C# lib, binding existing native library defeats the purpose.
It really depends. If you have some issues with performance on Android you can give it a try. But if all is already working smoothly, I don't really see why you would do it.
I shamelessly took the Jonathan Peppers repo and just renamed everything with SDWebImage
to have a real symetrical implementation.
![](__Docs__/sdwebimage_project.png) | ![](__Docs__/glidex_project.png) |
Any resemblance to real projects, living or dead, is purely coincidental |
Thanks to Jonathan and since Xamarin.Forms 3.3.0
, we have now a IImageViewHandler
interface on the Android platform. It allows to directly deal with ImageView
to implement the ImageSource
on the platform.
[assembly: ExportImageSourceHandler(
typeof (FileImageSource), typeof (Android.Glide.ImageViewHandler))]
[assembly: ExportImageSourceHandler(
typeof (StreamImageSource), typeof (Android.Glide.ImageViewHandler))]
[assembly: ExportImageSourceHandler(
typeof (UriImageSource), typeof (Android.Glide.ImageViewHandler))]
namespace Android.Glide
{
[Preserve (AllMembers = true)]
public class ImageViewHandler : IImageViewHandler
{
public ImageViewHandler ()
{
Forms.Debug (
"IImageViewHandler of type `{0}`, instance created.",
GetType ());
}
public async Task LoadImageAsync(
ImageSource source,
ImageView imageView,
CancellationToken token = default(CancellationToken))
{
Forms.Debug(
"IImageViewHandler of type `{0}`, `{1}` called.",
GetType(),
nameof(LoadImageAsync));
await imageView.LoadViaGlide(source, token);
}
}
}
On the iOS
side, we can deal directly with the IImageSourceHandler
returning an UIImage
.
[assembly: ExportImageSourceHandler(
typeof(UriImageSource), typeof(SDWebImage.Forms.ImageSourceHandler))]
namespace SDWebImage.Forms
{
[Preserve (AllMembers = true)]
public class ImageSourceHandler : IImageSourceHandler
{
public Task<UIImage> LoadImageAsync(
ImageSource imageSource,
CancellationToken cancellationToken = new CancellationToken(),
float scale = 1)
{
return SDWebImageViewHelper.LoadViaSDWebImage(
imageSource, cancellationToken);
}
}
}
SDWebImage
has a simple api to download and cache UIImage
.
private static Task<UIImage> LoadImageAsync(string urlString)
{
var tcs = new TaskCompletionSource<UIImage>();
SDWebImageManager.SharedManager.LoadImage(
new NSUrl(urlString),
SDWebImageOptions.ScaleDownLargeImages,
null,
(image, data, error, cacheType, finished, url) =>
{
if (image == null)
{
Forms.Debug("Fail to load image: {0}", url.AbsoluteUrl);
}
tcs.SetResult(image);
});
return tcs.Task;
}
However it doesn't support local files, only remote ones. That means that local resources are still processed by Xamarin.Forms
.
public sealed class FileImageSourceHandler : IImageSourceHandler
{
public Task<UIImage> LoadImageAsync(
ImageSource imagesource,
CancellationToken cancelationToken = default(CancellationToken),
float scale = 1f)
{
UIImage image = null;
var filesource = imagesource as FileImageSource;
var file = filesource?.File;
if (!string.IsNullOrEmpty(file))
image = File.Exists(file)
? new UIImage(file)
: UIImage.FromBundle(file);
if (image == null)
{
Log.Warning(
nameof(FileImageSourceHandler),
"Could not find image: {0}",
imagesource);
}
return Task.FromResult(image);
}
}
Remark: I don't really know if it would be interesting to have also a IUIImageViewHandler
interface on iOS
. I don't know if it would improve the performance vs a UIImage
cache.
Since all test pages use a mix of local resources and remote ones, and SDImageWeb
only process remote files, I created a special page named GridOnlyRemotePage
.
Page | Data Type | Xamarin.Forms | SDWebImage |
---|---|---|---|
GridOnlyRemotePage | Remote only | 247828480 | 14229504 (-94%) |
GridPage | Remote and local mix | 186748928 | 92033024 (-50%) |
ViewCellPage | Remote and local mix | 36646912 | 18288640 (-50%) |
ImageCellPage | Remote and local mix | 81604608 | 25874432 (-68%) |
HugeImagePage | Local only | 124104704 | Same as XF (0%) |
Of course the impact is huge for remote files (memory footprint of SDWebImage
is 5% of Xamarin.Forms
!) but at this point I was pretty disappointed:
SDWebImage
doesn't handle local resources- It doesn't take into account the scale (x2 per instance for retina) of the screen. So the
UIImage
instead of being scale 2 and size{Width=150, Height=150}
was scale 1 and size{Width=300, Height=300}
- The lib is HUGE (well of course it would have been linked, but still):