Better error handling for image loading errors on iOS/Android (#849)
* First run at removing async void image update methods Consistent error logging and IsLoading on Android,iOS,UWP Move error logging into image handlers for better messages Add demo of custom ImageRenderer error handling Update docs Make the test smaller so the results don't get pushed offscreen Fix namespace error * Update error handling for fast image renderer * Update 37625 test to use image we control * Add java disposed check to avoid ObjectDisposedException in async operations * Add disposed checks to legacy renderer; null check element before SetIsLoading * Check disposed on GetDesiredSize for fast renderer Use local disposed member where possible for disposed check * Check for disposal after async handlers in iOS * Add disposal checks after async methods in Windows * Reset linker settings on project; reduce redundant casts in ImageViewExtensions
This commit is contained in:
Родитель
9631ec2d8b
Коммит
cdc4055128
|
@ -0,0 +1,38 @@
|
|||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Android.Content;
|
||||
using Android.Graphics;
|
||||
using Xamarin.Forms;
|
||||
using Xamarin.Forms.ControlGallery.Android;
|
||||
using Xamarin.Forms.Controls.Issues;
|
||||
using Xamarin.Forms.Platform.Android;
|
||||
|
||||
[assembly: ExportRenderer(typeof(_51173Image), typeof(_51173CustomImageRenderer))]
|
||||
namespace Xamarin.Forms.ControlGallery.Android
|
||||
{
|
||||
public sealed class BrokenImageSourceHandler : IImageSourceHandler
|
||||
{
|
||||
public Task<Bitmap> LoadImageAsync(ImageSource imagesource, Context context, CancellationToken cancelationToken = default(CancellationToken))
|
||||
{
|
||||
throw new Exception("Fail");
|
||||
}
|
||||
}
|
||||
|
||||
public class _51173CustomImageRenderer : ImageRenderer
|
||||
{
|
||||
protected override async Task TryUpdateBitmap(Image previous = null)
|
||||
{
|
||||
try
|
||||
{
|
||||
await UpdateBitmap(previous).ConfigureAwait(false);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
await Application.Current.MainPage.DisplayAlert("Image Error 51173", $"The image failed to load, here's why: {ex.Message}", "OK");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -2,8 +2,11 @@
|
|||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
using Android.App;
|
||||
using Xamarin.Forms.Controls;
|
||||
using Xamarin.Forms.Platform.Android;
|
||||
using Xamarin.Forms;
|
||||
|
||||
using Xamarin.Forms.ControlGallery.Android;
|
||||
|
||||
// General Information about an assembly is controlled through the following
|
||||
// set of attributes. Change these attribute values to modify the information
|
||||
// associated with an assembly.
|
||||
|
@ -37,6 +40,8 @@ using Xamarin.Forms;
|
|||
[assembly: Android.App.MetaData("com.google.android.maps.v2.API_KEY", Value = "AIzaSyAdstcJQswxEjzX5YjLaMcu2aRVEBJw39Y")]
|
||||
[assembly: Xamarin.Forms.ResolutionGroupName ("XamControl")]
|
||||
|
||||
// Deliberately broken image source and handler so we can test handling of image loading errors
|
||||
[assembly: ExportImageSourceHandler(typeof(FailImageSource), typeof(BrokenImageSourceHandler))]
|
||||
#if TEST_LEGACY_RENDERERS
|
||||
[assembly: ExportRenderer(typeof(Button), typeof(Xamarin.Forms.Platform.Android.AppCompat.ButtonRenderer))]
|
||||
[assembly: ExportRenderer(typeof(Image), typeof(Xamarin.Forms.Platform.Android.ImageRenderer))]
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
This is certainly not a real JPEG.
|
|
@ -26,6 +26,9 @@
|
|||
<RestorePackages>true</RestorePackages>
|
||||
<NuGetPackageImportStamp>
|
||||
</NuGetPackageImportStamp>
|
||||
<AndroidTlsProvider>
|
||||
</AndroidTlsProvider>
|
||||
<JavaMaximumHeapSize>1G</JavaMaximumHeapSize>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)' == 'Debug'">
|
||||
<AndroidKeyStore>True</AndroidKeyStore>
|
||||
|
@ -46,20 +49,27 @@
|
|||
<AndroidLinkMode>Full</AndroidLinkMode>
|
||||
<JavaMaximumHeapSize>1G</JavaMaximumHeapSize>
|
||||
<EmbedAssembliesIntoApk>True</EmbedAssembliesIntoApk>
|
||||
<BundleAssemblies>False</BundleAssemblies>
|
||||
<CustomCommands>
|
||||
<CustomCommands>
|
||||
<Command type="AfterBuild" command="xbuild /t:SignAndroidPackage Xamarin.Forms.ControlGallery.Android/Xamarin.Forms.ControlGallery.Android.csproj" workingdir="${SolutionDir}" />
|
||||
<Command>
|
||||
<type>AfterBuild</type>
|
||||
<command>xbuild /t:SignAndroidPackage Xamarin.Forms.ControlGallery.Android/Xamarin.Forms.ControlGallery.Android.csproj</command>
|
||||
<workingdir>${SolutionDir}</workingdir>
|
||||
</Command>
|
||||
</CustomCommands>
|
||||
</CustomCommands>
|
||||
<AndroidCreatePackagePerAbi>False</AndroidCreatePackagePerAbi>
|
||||
<AndroidSupportedAbis>armeabi;armeabi-v7a;x86</AndroidSupportedAbis>
|
||||
<Debugger>Xamarin</Debugger>
|
||||
<AndroidEnableMultiDex>False</AndroidEnableMultiDex>
|
||||
<Debugger>.Net (Xamarin)</Debugger>
|
||||
<DevInstrumentationEnabled>True</DevInstrumentationEnabled>
|
||||
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
|
||||
<NoWarn>
|
||||
</NoWarn>
|
||||
<BundleAssemblies>False</BundleAssemblies>
|
||||
<AndroidCreatePackagePerAbi>False</AndroidCreatePackagePerAbi>
|
||||
<AotAssemblies>False</AotAssemblies>
|
||||
<EnableLLVM>False</EnableLLVM>
|
||||
<AndroidEnableMultiDex>False</AndroidEnableMultiDex>
|
||||
<EnableProguard>False</EnableProguard>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
|
||||
<DebugType>pdbonly</DebugType>
|
||||
|
@ -73,8 +83,6 @@
|
|||
<JavaMaximumHeapSize>1G</JavaMaximumHeapSize>
|
||||
<AndroidLinkSkip />
|
||||
<EmbedAssembliesIntoApk>True</EmbedAssembliesIntoApk>
|
||||
<BundleAssemblies>False</BundleAssemblies>
|
||||
<AndroidCreatePackagePerAbi>False</AndroidCreatePackagePerAbi>
|
||||
<AndroidSupportedAbis>armeabi-v7a,x86</AndroidSupportedAbis>
|
||||
<AndroidStoreUncompressedFileExtensions />
|
||||
<MandroidI18n />
|
||||
|
@ -97,14 +105,11 @@
|
|||
<AndroidLinkSkip />
|
||||
<AndroidLinkMode>SdkOnly</AndroidLinkMode>
|
||||
<EmbedAssembliesIntoApk>False</EmbedAssembliesIntoApk>
|
||||
<BundleAssemblies>False</BundleAssemblies>
|
||||
<AndroidCreatePackagePerAbi>False</AndroidCreatePackagePerAbi>
|
||||
<AndroidSupportedAbis>armeabi,armeabi-v7a,x86</AndroidSupportedAbis>
|
||||
<AndroidStoreUncompressedFileExtensions />
|
||||
<MandroidI18n />
|
||||
<JavaMaximumHeapSize>1G</JavaMaximumHeapSize>
|
||||
<Debugger>Xamarin</Debugger>
|
||||
<AndroidEnableMultiDex>False</AndroidEnableMultiDex>
|
||||
<DevInstrumentationEnabled>True</DevInstrumentationEnabled>
|
||||
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
|
||||
<NoWarn>
|
||||
|
@ -176,6 +181,7 @@
|
|||
<Compile Include="CustomRenderers.cs" />
|
||||
<Compile Include="ColorPicker.cs" />
|
||||
<Compile Include="_38989CustomRenderer.cs" />
|
||||
<Compile Include="BrokenImageSourceHandler.cs" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<AndroidAsset Include="Assets\default.css" />
|
||||
|
@ -204,6 +210,7 @@
|
|||
<AndroidResource Include="Resources\drawable\coffee.png" />
|
||||
<AndroidResource Include="Resources\drawable\toolbar_close.png" />
|
||||
<AndroidResource Include="Resources\drawable\test.jpg" />
|
||||
<AndroidResource Include="Resources\drawable\invalidimage.jpg" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<AndroidResource Include="Resources\drawable\Icon.png" />
|
||||
|
|
|
@ -30,6 +30,4 @@ namespace Xamarin.Forms.ControlGallery.Android
|
|||
return nativeView;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -1,16 +1,20 @@
|
|||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
using Xamarin.Forms.Controls.Issues;
|
||||
using WImageSource = Windows.UI.Xaml.Media.ImageSource;
|
||||
|
||||
#if WINDOWS_UWP
|
||||
using Xamarin.Forms.Platform.UWP;
|
||||
using Xamarin.Forms.ControlGallery.WindowsUniversal;
|
||||
|
||||
[assembly: ExportRenderer(typeof(_51173Image), typeof(_51173CustomImageRenderer))]
|
||||
namespace Xamarin.Forms.ControlGallery.WindowsUniversal
|
||||
#else
|
||||
using Xamarin.Forms.Platform.WinRT;
|
||||
using Xamarin.Forms.ControlGallery.WinRT;
|
||||
|
||||
[assembly: ExportRenderer(typeof(_51173Image), typeof(_51173CustomImageRenderer))]
|
||||
namespace Xamarin.Forms.ControlGallery.WinRT
|
||||
#endif
|
||||
{
|
||||
|
@ -21,4 +25,19 @@ namespace Xamarin.Forms.ControlGallery.WinRT
|
|||
throw new Exception("Fail");
|
||||
}
|
||||
}
|
||||
|
||||
public class _51173CustomImageRenderer : ImageRenderer
|
||||
{
|
||||
protected override async Task TryUpdateSource()
|
||||
{
|
||||
try
|
||||
{
|
||||
await UpdateSource().ConfigureAwait(false);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
await Xamarin.Forms.Application.Current.MainPage.DisplayAlert("Image Error 51173", $"The image failed to load, here's why: {ex.Message}", "OK");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,36 @@
|
|||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using UIKit;
|
||||
using Xamarin.Forms;
|
||||
using Xamarin.Forms.ControlGallery.iOS;
|
||||
using Xamarin.Forms.Controls.Issues;
|
||||
using Xamarin.Forms.Platform.iOS;
|
||||
|
||||
[assembly: ExportRenderer(typeof(_51173Image), typeof(_51173CustomImageRenderer))]
|
||||
namespace Xamarin.Forms.ControlGallery.iOS
|
||||
{
|
||||
public sealed class BrokenImageSourceHandler : IImageSourceHandler
|
||||
{
|
||||
public Task<UIImage> LoadImageAsync(ImageSource imagesource, CancellationToken cancelationToken = new CancellationToken(),
|
||||
float scale = 1)
|
||||
{
|
||||
throw new Exception("Fail");
|
||||
}
|
||||
}
|
||||
|
||||
public class _51173CustomImageRenderer : ImageRenderer
|
||||
{
|
||||
protected override async Task TrySetImage(Image previous = null)
|
||||
{
|
||||
try
|
||||
{
|
||||
await SetImage(previous).ConfigureAwait(false);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
await Xamarin.Forms.Application.Current.MainPage.DisplayAlert("Image Error 51173", $"The image failed to load, here's why: {ex.Message}", "OK");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,6 +1,9 @@
|
|||
using System.Reflection;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
using Xamarin.Forms;
|
||||
using Xamarin.Forms.ControlGallery.iOS;
|
||||
using Xamarin.Forms.Controls;
|
||||
|
||||
// General Information about an assembly is controlled through the following
|
||||
// set of attributes. Change these attribute values to modify the information
|
||||
|
@ -35,3 +38,6 @@ using System.Runtime.InteropServices;
|
|||
[assembly: AssemblyVersion ("1.0.0.0")]
|
||||
[assembly: AssemblyFileVersion ("1.0.0.0")]
|
||||
[assembly: Xamarin.Forms.ResolutionGroupName("XamControl")]
|
||||
|
||||
// Deliberately broken image source and handler so we can test handling of image loading errors
|
||||
[assembly: ExportImageSourceHandler(typeof(FailImageSource), typeof(BrokenImageSourceHandler))]
|
|
@ -0,0 +1 @@
|
|||
This is certainly not a real JPEG.
|
|
@ -159,6 +159,7 @@
|
|||
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="BrokenImageSourceHandler.cs" />
|
||||
<Compile Include="BrokenNativeControl.cs" />
|
||||
<Compile Include="CustomRenderer40251.cs" />
|
||||
<Compile Include="Main.cs" />
|
||||
|
@ -218,6 +219,7 @@
|
|||
<Content Include="oasis.jpg" />
|
||||
<BundleResource Include="oasissmall.jpg" />
|
||||
<Content Include="coffee%402x.png" />
|
||||
<BundleResource Include="Resources\invalidimage.jpg" />
|
||||
<Content Include="settings%402x.png" />
|
||||
<Content Include="menuIcon%402x.png" />
|
||||
<Content Include="crimsonsmall.jpg" />
|
||||
|
|
|
@ -13,13 +13,13 @@ namespace Xamarin.Forms.Controls.Issues
|
|||
{
|
||||
[Preserve (AllMembers = true)]
|
||||
[Issue (IssueTracker.Bugzilla, 37625, "App crashes when quickly adding/removing Image views (Windows UWP)")]
|
||||
public class Bugzilla37625 : TestContentPage // or TestMasterDetailPage, etc ...
|
||||
public class Bugzilla37625 : TestContentPage
|
||||
{
|
||||
protected override async void Init ()
|
||||
{
|
||||
int retry = 5;
|
||||
while (retry-- >= 0) {
|
||||
var imageUri = new Uri ("https://xamarin.com/content/images/pages/products/platform.png");
|
||||
var imageUri = new Uri ("https://raw.githubusercontent.com/xamarin/Xamarin.Forms/master/Xamarin.Forms.ControlGallery.Android/Assets/WebImages/XamarinLogo.png");
|
||||
Content = new Image () { Source = new UriImageSource () { Uri = imageUri }, BackgroundColor = Color.Black, AutomationId = "success" };
|
||||
|
||||
await Task.Delay (50);
|
||||
|
|
|
@ -0,0 +1,187 @@
|
|||
using System;
|
||||
using System.Globalization;
|
||||
using Xamarin.Forms.CustomAttributes;
|
||||
using Xamarin.Forms.Internals;
|
||||
|
||||
#if UITEST
|
||||
using Xamarin.Forms.Core.UITests;
|
||||
using Xamarin.UITest;
|
||||
using NUnit.Framework;
|
||||
#endif
|
||||
|
||||
namespace Xamarin.Forms.Controls.Issues
|
||||
{
|
||||
#if UITEST
|
||||
[Category(UITestCategories.Image)]
|
||||
#endif
|
||||
|
||||
[Preserve(AllMembers = true)]
|
||||
[Issue(IssueTracker.Bugzilla, 51173, "ImageRenderer, async void SetImage - Cannot catch exceptions", PlatformAffected.All)]
|
||||
public class Bugzilla51173 : TestContentPage
|
||||
{
|
||||
#if UITEST
|
||||
[Test(Description = "Attempt to load an image from a URI which does not exist")]
|
||||
public void Bugzilla51173_NonexistentUri()
|
||||
{
|
||||
|
||||
RunningApp.WaitForElement(q => q.Marked(UriDoesNotExist));
|
||||
|
||||
RunningApp.Tap(UriDoesNotExist);
|
||||
RunningApp.WaitForElement(q => q.Marked(ErrorLogged));
|
||||
RunningApp.WaitForElement(q => q.Marked(NotLoading));
|
||||
}
|
||||
|
||||
[Test(Description = "The IImageSourceHandler itself throws an exception")]
|
||||
public void Bugzilla51173_HandlerThrowsException()
|
||||
{
|
||||
RunningApp.WaitForElement(q => q.Marked(HandlerThrows));
|
||||
|
||||
RunningApp.Tap(HandlerThrows);
|
||||
RunningApp.WaitForElement(q => q.Marked(ErrorLogged));
|
||||
RunningApp.WaitForElement(q => q.Marked(NotLoading));
|
||||
}
|
||||
|
||||
[Test(Description = "The URI is valid, but the image data is not")]
|
||||
public void Bugzilla51173_RealUriWithInvalidImageData()
|
||||
{
|
||||
RunningApp.WaitForElement(q => q.Marked(RealUriInvalidImage));
|
||||
|
||||
RunningApp.Tap(RealUriInvalidImage);
|
||||
RunningApp.WaitForElement(q => q.Marked(ErrorLogged));
|
||||
RunningApp.WaitForElement(q => q.Marked(NotLoading));
|
||||
}
|
||||
|
||||
[Test(Description = "Attempt to load a local image which does not exist")]
|
||||
public void Bugzilla51173_NonexistentImage()
|
||||
{
|
||||
RunningApp.WaitForElement(q => q.Marked(ImageDoesNotExist));
|
||||
|
||||
RunningApp.Tap(ImageDoesNotExist);
|
||||
RunningApp.WaitForElement(q => q.Marked(ErrorLogged));
|
||||
RunningApp.WaitForElement(q => q.Marked(NotLoading));
|
||||
}
|
||||
|
||||
[Test(Description = "Attempt to load a local image which exists, but has corrupt data")]
|
||||
public void Bugzilla51173_InvalidImage()
|
||||
{
|
||||
RunningApp.WaitForElement(q => q.Marked(ImageIsInvalid));
|
||||
|
||||
RunningApp.Tap(ImageIsInvalid);
|
||||
RunningApp.WaitForElement(q => q.Marked(ErrorLogged));
|
||||
RunningApp.WaitForElement(q => q.Marked(NotLoading));
|
||||
}
|
||||
|
||||
[Test(Description = "Load a valid image")]
|
||||
public void Bugzilla51173_ValidImage()
|
||||
{
|
||||
RunningApp.WaitForElement(q => q.Marked(ValidImage));
|
||||
RunningApp.Tap(ValidImage);
|
||||
RunningApp.WaitForElement(q => q.Marked(NotLoading));
|
||||
}
|
||||
#endif
|
||||
|
||||
const string ValidImage = "Valid Image";
|
||||
const string ImageDoesNotExist = "Non-existent Image File";
|
||||
const string ImageIsInvalid = "Invalid Image File (bad data)";
|
||||
const string UriDoesNotExist = "Non-existent URI";
|
||||
const string HandlerThrows = "Source Throws Exception";
|
||||
const string RealUriInvalidImage = "Valid URI with invalid image file";
|
||||
const string ErrorLogged = "Error logged";
|
||||
|
||||
const string Loading = "Loading";
|
||||
const string NotLoading = "Not Loading";
|
||||
|
||||
Label _results;
|
||||
Image _image;
|
||||
|
||||
class LoadingConverter : IValueConverter
|
||||
{
|
||||
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
|
||||
{
|
||||
if ((bool)value)
|
||||
{
|
||||
return Loading;
|
||||
}
|
||||
|
||||
return NotLoading;
|
||||
}
|
||||
|
||||
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
|
||||
protected override void Init()
|
||||
{
|
||||
_results = new Label { Margin = 10, FontAttributes = FontAttributes.Bold, BackgroundColor = Color.Silver, HorizontalTextAlignment = TextAlignment.Center};
|
||||
|
||||
var errorMessage = new Label();
|
||||
|
||||
Log.Listeners.Add(
|
||||
new DelegateLogListener((c, m) => Device.BeginInvokeOnMainThread(() =>
|
||||
{
|
||||
_results.Text = ErrorLogged;
|
||||
errorMessage.Text = m;
|
||||
})));
|
||||
|
||||
var instructions = new Label
|
||||
{
|
||||
Text =
|
||||
"Pressing the 'Valid Image' button should display an image of a coffee cup. Every other button should cause the messager 'Error logged' to appear at the top of the page."
|
||||
};
|
||||
|
||||
_image = new Image { BackgroundColor = Color.Bisque, HeightRequest = 20 };
|
||||
|
||||
var loadingState = new Label();
|
||||
loadingState.SetBinding(Label.TextProperty, new Binding(Image.IsLoadingProperty.PropertyName, BindingMode.Default, new LoadingConverter()));
|
||||
loadingState.BindingContext = _image;
|
||||
|
||||
var legit = CreateTest(() => _image.Source = ImageSource.FromFile("coffee.png"), ValidImage);
|
||||
|
||||
var invalidImageFileName = CreateTest(() => _image.Source = ImageSource.FromFile("fake.png"), ImageDoesNotExist);
|
||||
|
||||
var invalidImageFile = CreateTest(() => _image.Source = ImageSource.FromFile("invalidimage.jpg"), ImageIsInvalid);
|
||||
|
||||
var fakeUri = CreateTest(() => _image.Source = ImageSource.FromUri(new Uri("http://not.real")), UriDoesNotExist);
|
||||
|
||||
var crashImage = CreateTest(() => _image.Source = new FailImageSource(), HandlerThrows);
|
||||
|
||||
var uriInvalidImageData =
|
||||
CreateTest(() => _image.Source = ImageSource.FromUri(new Uri("https://gist.githubusercontent.com/hartez/a2dda6b5c78852bcf4832af18f21a023/raw/39f4cd2e9fe8514694ac7fa0943017eb9308853d/corrupt.jpg")),
|
||||
RealUriInvalidImage);
|
||||
|
||||
Content = new StackLayout
|
||||
{
|
||||
Margin = new Thickness(5, 20, 5, 0),
|
||||
Children =
|
||||
{
|
||||
_image,
|
||||
instructions,
|
||||
legit,
|
||||
invalidImageFileName,
|
||||
invalidImageFile,
|
||||
fakeUri,
|
||||
crashImage,
|
||||
uriInvalidImageData,
|
||||
_results,
|
||||
loadingState,
|
||||
errorMessage
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
Button CreateTest(Action imageLoadAction, string title)
|
||||
{
|
||||
var button = new Button { Text = title };
|
||||
|
||||
button.Clicked += (sender, args) =>
|
||||
{
|
||||
_results.Text = "";
|
||||
imageLoadAction();
|
||||
};
|
||||
|
||||
return button;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,40 @@
|
|||
using Xamarin.Forms.CustomAttributes;
|
||||
using Xamarin.Forms.Internals;
|
||||
|
||||
namespace Xamarin.Forms.Controls.Issues
|
||||
{
|
||||
[Preserve(AllMembers = true)]
|
||||
[Issue(IssueTracker.None, 51173, "Custom ImageRenderer error handling demo", PlatformAffected.All)]
|
||||
public class CustomImageRendererErrorHandling : TestContentPage
|
||||
{
|
||||
protected override void Init()
|
||||
{
|
||||
var layout = new StackLayout { Margin = new Thickness(5, 40, 5, 0) };
|
||||
|
||||
var instructions = new Label
|
||||
{
|
||||
Text =
|
||||
@"
|
||||
Click 'Update Image Source'; it will update the coffee image with an image source which will throw an exception.
|
||||
Instead of just logging an error, the custom renderer will display an alert dialog about the error.
|
||||
"
|
||||
};
|
||||
|
||||
var image = new _51173Image { Source = "coffee.png" };
|
||||
|
||||
var button = new Button { Text = "Update Image Source" };
|
||||
|
||||
button.Clicked += (sender, args) => image.Source = new FailImageSource();
|
||||
|
||||
layout.Children.Add(instructions);
|
||||
layout.Children.Add(image);
|
||||
layout.Children.Add(button);
|
||||
|
||||
Content = layout;
|
||||
}
|
||||
}
|
||||
|
||||
// custom image type for demonstrating custom error handling in a custom renderer
|
||||
[Preserve(AllMembers = true)]
|
||||
public class _51173Image : Image { }
|
||||
}
|
|
@ -1,100 +0,0 @@
|
|||
using System;
|
||||
using Xamarin.Forms.CustomAttributes;
|
||||
using Xamarin.Forms.Internals;
|
||||
|
||||
namespace Xamarin.Forms.Controls
|
||||
{
|
||||
[Preserve(AllMembers = true)]
|
||||
[Issue(IssueTracker.None, 0, "Image Loading Error Handling", PlatformAffected.WinRT | PlatformAffected.UWP)]
|
||||
public class ImageLoadingErrorHandling : TestContentPage
|
||||
{
|
||||
protected override void Init()
|
||||
{
|
||||
Log.Listeners.Add(
|
||||
new DelegateLogListener((c, m) => Device.BeginInvokeOnMainThread(() => DisplayAlert(c, m, "Cool, Thanks"))));
|
||||
|
||||
var image = new Image() {BackgroundColor = Color.White};
|
||||
|
||||
Grid legit = CreateTest(() => image.Source = ImageSource.FromFile("coffee.png"),
|
||||
"Valid Image",
|
||||
"Clicking this button should load an image at the top of the page.",
|
||||
Color.Silver);
|
||||
|
||||
Grid invalidImageFileName = CreateTest(() => image.Source = ImageSource.FromFile("fake.png"),
|
||||
"Non-existent Image File",
|
||||
"Clicking this button should display an alert dialog with an error that the image failed to load.");
|
||||
|
||||
Grid invalidImageFile = CreateTest(() => image.Source = ImageSource.FromFile("invalidimage.jpg"),
|
||||
"Invalid Image File (bad data)",
|
||||
"Clicking this button should display an alert dialog with an error that the image failed to load.",
|
||||
Color.Silver);
|
||||
|
||||
Grid fakeUri = CreateTest(() => image.Source = ImageSource.FromUri(new Uri("http://not.real")),
|
||||
"Non-existent URI",
|
||||
(Device.RuntimePlatform == Device.UWP || Device.RuntimePlatform == Device.WinRT) && Device.Idiom == TargetIdiom.Phone
|
||||
? "Clicking this button should display an alert dialog. The error message should include the text 'NotFound'."
|
||||
: "Clicking this button should display an alert dialog. The error message should include the text 'the server name or address could not be resolved'.");
|
||||
|
||||
// This used to crash the app with an uncatchable error; need to make sure it's not still doing that
|
||||
Grid crashImage = CreateTest(() => image.Source = new FailImageSource(),
|
||||
"Source Throws Exception",
|
||||
"Clicking this button should display an alert dialog. The error messages hould include the test 'error updating image source'.",
|
||||
Color.Silver);
|
||||
|
||||
Grid uriInvalidImageData =
|
||||
CreateTest(() => image.Source = ImageSource.FromUri(new Uri("https://gist.githubusercontent.com/hartez/a2dda6b5c78852bcf4832af18f21a023/raw/39f4cd2e9fe8514694ac7fa0943017eb9308853d/corrupt.jpg")),
|
||||
"Valid URI with invalid image file",
|
||||
"Clicking this button should display an alert dialog. The error message should include the text 'UriImageSourceHandler could not load https://gist.githubusercontent.com/hartez/a2dda6b5c78852bcf4832af18f21a023/raw/39f4cd2e9fe8514694ac7fa0943017eb9308853d/corrupt.jpg'");
|
||||
|
||||
Content = new StackLayout
|
||||
{
|
||||
Children =
|
||||
{
|
||||
image,
|
||||
legit,
|
||||
invalidImageFileName,
|
||||
invalidImageFile,
|
||||
fakeUri,
|
||||
crashImage,
|
||||
uriInvalidImageData
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
static Grid CreateTest(Action imageLoadAction, string title, string instructions, Color? backgroundColor = null)
|
||||
{
|
||||
var button = new Button { Text = "Test" };
|
||||
|
||||
button.Clicked += (sender, args) => imageLoadAction();
|
||||
|
||||
var titleLabel = new Label
|
||||
{
|
||||
Text = title,
|
||||
FontAttributes = FontAttributes.Bold
|
||||
};
|
||||
|
||||
var label = new Label
|
||||
{
|
||||
Text = instructions
|
||||
};
|
||||
|
||||
var grid = new Grid
|
||||
{
|
||||
ColumnDefinitions =
|
||||
new ColumnDefinitionCollection { new ColumnDefinition(), new ColumnDefinition(), new ColumnDefinition() },
|
||||
RowDefinitions = new RowDefinitionCollection { new RowDefinition { Height = 80 } }
|
||||
};
|
||||
|
||||
if (backgroundColor.HasValue)
|
||||
{
|
||||
grid.BackgroundColor = backgroundColor.Value;
|
||||
}
|
||||
|
||||
grid.AddChild(titleLabel, 0, 0);
|
||||
grid.AddChild(label, 1, 0);
|
||||
grid.AddChild(button, 2, 0);
|
||||
|
||||
return grid;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -200,6 +200,7 @@
|
|||
<Compile Include="$(MSBuildThisFileDirectory)Bugzilla34561.cs" />
|
||||
<Compile Include="$(MSBuildThisFileDirectory)Bugzilla34727.cs" />
|
||||
<Compile Include="$(MSBuildThisFileDirectory)ComplexListView.cs" />
|
||||
<Compile Include="$(MSBuildThisFileDirectory)CustomImageRendererErrorHandling.cs" />
|
||||
<Compile Include="$(MSBuildThisFileDirectory)DefaultColorToggleTest.cs" />
|
||||
<Compile Include="$(MSBuildThisFileDirectory)Bugzilla38416.xaml.cs">
|
||||
<DependentUpon>Bugzilla38416.xaml</DependentUpon>
|
||||
|
@ -253,7 +254,7 @@
|
|||
<Compile Include="$(MSBuildThisFileDirectory)TestPages\ScreenshotConditionalApp.cs" />
|
||||
<Compile Include="$(MSBuildThisFileDirectory)Bugzilla41842.cs" />
|
||||
<Compile Include="$(MSBuildThisFileDirectory)Bugzilla42277.cs" />
|
||||
<Compile Include="$(MSBuildThisFileDirectory)ImageLoadingErrorHandling.cs" />
|
||||
<Compile Include="$(MSBuildThisFileDirectory)Bugzilla51173.cs" />
|
||||
<Compile Include="$(MSBuildThisFileDirectory)Bugzilla33561.cs" />
|
||||
<Compile Include="$(MSBuildThisFileDirectory)Bugzilla43214.cs" />
|
||||
<Compile Include="$(MSBuildThisFileDirectory)Bugzilla42602.cs" />
|
||||
|
|
|
@ -18,6 +18,11 @@ namespace Xamarin.Forms
|
|||
return Task.FromResult(false);
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return $"File: {File}";
|
||||
}
|
||||
|
||||
public static implicit operator FileImageSource(string file)
|
||||
{
|
||||
return (FileImageSource)FromFile(file);
|
||||
|
|
|
@ -92,6 +92,11 @@ namespace Xamarin.Forms
|
|||
return stream;
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return $"Uri: {Uri}";
|
||||
}
|
||||
|
||||
static string GetCacheKey(Uri uri)
|
||||
{
|
||||
return Device.PlatformServices.GetMD5Hash(uri.AbsoluteUri);
|
||||
|
|
|
@ -1,28 +1,33 @@
|
|||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using Android.Graphics;
|
||||
using Java.IO;
|
||||
using AImageView = Android.Widget.ImageView;
|
||||
|
||||
namespace Xamarin.Forms.Platform.Android
|
||||
{
|
||||
internal static class ImageViewExtensions
|
||||
{
|
||||
public static async void UpdateBitmap(this AImageView imageView, Image newImage, Image previousImage = null)
|
||||
// TODO hartez 2017/04/07 09:33:03 Review this again, not sure it's handling the transition from previousImage to 'null' newImage correctly
|
||||
public static async Task UpdateBitmap(this AImageView imageView, Image newImage, Image previousImage = null)
|
||||
{
|
||||
if (imageView == null || imageView.IsDisposed())
|
||||
return;
|
||||
|
||||
if (Device.IsInvokeRequired)
|
||||
throw new InvalidOperationException("Image Bitmap must not be updated from background thread");
|
||||
|
||||
if (previousImage != null && Equals(previousImage.Source, newImage.Source))
|
||||
return;
|
||||
|
||||
((IImageController)newImage).SetIsLoading(true);
|
||||
var imageController = newImage as IImageController;
|
||||
|
||||
(imageView as IImageRendererController).SkipInvalidate();
|
||||
imageController?.SetIsLoading(true);
|
||||
|
||||
(imageView as IImageRendererController)?.SkipInvalidate();
|
||||
|
||||
imageView.SetImageResource(global::Android.Resource.Color.Transparent);
|
||||
|
||||
ImageSource source = newImage.Source;
|
||||
ImageSource source = newImage?.Source;
|
||||
Bitmap bitmap = null;
|
||||
IImageSourceHandler handler;
|
||||
|
||||
|
@ -34,10 +39,7 @@ namespace Xamarin.Forms.Platform.Android
|
|||
}
|
||||
catch (TaskCanceledException)
|
||||
{
|
||||
}
|
||||
catch (IOException ex)
|
||||
{
|
||||
Internals.Log.Warning("Xamarin.Forms.Platform.Android.ImageRenderer", "Error updating bitmap: {0}", ex);
|
||||
imageController?.SetIsLoading(false);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -47,14 +49,19 @@ namespace Xamarin.Forms.Platform.Android
|
|||
return;
|
||||
}
|
||||
|
||||
if (bitmap == null && source is FileImageSource)
|
||||
imageView.SetImageResource(ResourceManager.GetDrawableByName(((FileImageSource)source).File));
|
||||
else
|
||||
imageView.SetImageBitmap(bitmap);
|
||||
if (!imageView.IsDisposed())
|
||||
{
|
||||
if (bitmap == null && source is FileImageSource)
|
||||
imageView.SetImageResource(ResourceManager.GetDrawableByName(((FileImageSource)source).File));
|
||||
else
|
||||
{
|
||||
imageView.SetImageBitmap(bitmap);
|
||||
}
|
||||
}
|
||||
|
||||
bitmap?.Dispose();
|
||||
|
||||
((IImageController)newImage).SetIsLoading(false);
|
||||
imageController?.SetIsLoading(false);
|
||||
((IVisualElementController)newImage).NativeSizeChanged();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,12 @@
|
|||
using System;
|
||||
|
||||
namespace Xamarin.Forms.Platform.Android
|
||||
{
|
||||
internal static class JavaObjectExtensions
|
||||
{
|
||||
public static bool IsDisposed(this Java.Lang.Object obj)
|
||||
{
|
||||
return obj.Handle == IntPtr.Zero;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,8 +1,10 @@
|
|||
using System;
|
||||
using System.ComponentModel;
|
||||
using System.Threading.Tasks;
|
||||
using AImageView = Android.Widget.ImageView;
|
||||
using AView = Android.Views.View;
|
||||
using Android.Views;
|
||||
using Xamarin.Forms.Internals;
|
||||
|
||||
namespace Xamarin.Forms.Platform.Android.FastRenderers
|
||||
{
|
||||
|
@ -17,30 +19,32 @@ namespace Xamarin.Forms.Platform.Android.FastRenderers
|
|||
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
base.Dispose(disposing);
|
||||
|
||||
if (_disposed)
|
||||
return;
|
||||
|
||||
_disposed = true;
|
||||
|
||||
if (!disposing)
|
||||
return;
|
||||
|
||||
if (_visualElementTracker != null)
|
||||
if (disposing)
|
||||
{
|
||||
_visualElementTracker.Dispose();
|
||||
_visualElementTracker = null;
|
||||
if (_visualElementTracker != null)
|
||||
{
|
||||
_visualElementTracker.Dispose();
|
||||
_visualElementTracker = null;
|
||||
}
|
||||
|
||||
if (_visualElementRenderer != null)
|
||||
{
|
||||
_visualElementRenderer.Dispose();
|
||||
_visualElementRenderer = null;
|
||||
}
|
||||
|
||||
if (_element != null)
|
||||
{
|
||||
_element.PropertyChanged -= OnElementPropertyChanged;
|
||||
}
|
||||
}
|
||||
|
||||
if (_visualElementRenderer != null)
|
||||
{
|
||||
_visualElementRenderer.Dispose();
|
||||
_visualElementRenderer = null;
|
||||
}
|
||||
|
||||
if (_element != null)
|
||||
_element.PropertyChanged -= OnElementPropertyChanged;
|
||||
base.Dispose(disposing);
|
||||
}
|
||||
|
||||
public override void Invalidate()
|
||||
|
@ -54,9 +58,9 @@ namespace Xamarin.Forms.Platform.Android.FastRenderers
|
|||
base.Invalidate();
|
||||
}
|
||||
|
||||
protected virtual void OnElementChanged(ElementChangedEventArgs<Image> e)
|
||||
protected virtual async void OnElementChanged(ElementChangedEventArgs<Image> e)
|
||||
{
|
||||
this.UpdateBitmap(e.NewElement, e.OldElement);
|
||||
await TryUpdateBitmap(e.OldElement);
|
||||
UpdateAspect();
|
||||
|
||||
ElementChanged?.Invoke(this, new VisualElementChangedEventArgs(e.OldElement, e.NewElement));
|
||||
|
@ -77,6 +81,11 @@ namespace Xamarin.Forms.Platform.Android.FastRenderers
|
|||
|
||||
SizeRequest IVisualElementRenderer.GetDesiredSize(int widthConstraint, int heightConstraint)
|
||||
{
|
||||
if (_disposed)
|
||||
{
|
||||
return new SizeRequest();
|
||||
}
|
||||
|
||||
Measure(widthConstraint, heightConstraint);
|
||||
return new SizeRequest(new Size(MeasuredWidth, MeasuredHeight), MinimumSize());
|
||||
}
|
||||
|
@ -114,7 +123,7 @@ namespace Xamarin.Forms.Platform.Android.FastRenderers
|
|||
|
||||
_element?.SendViewInitialized(Control);
|
||||
}
|
||||
|
||||
|
||||
void IVisualElementRenderer.SetLabelFor(int? id)
|
||||
{
|
||||
if (_defaultLabelFor == null)
|
||||
|
@ -144,18 +153,53 @@ namespace Xamarin.Forms.Platform.Android.FastRenderers
|
|||
{
|
||||
}
|
||||
|
||||
protected virtual void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
|
||||
protected virtual async void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
|
||||
{
|
||||
if (e.PropertyName == Image.SourceProperty.PropertyName)
|
||||
this.UpdateBitmap(_element);
|
||||
await TryUpdateBitmap();
|
||||
else if (e.PropertyName == Image.AspectProperty.PropertyName)
|
||||
UpdateAspect();
|
||||
|
||||
ElementPropertyChanged?.Invoke(this, e);
|
||||
}
|
||||
|
||||
protected virtual async Task TryUpdateBitmap(Image previous = null)
|
||||
{
|
||||
// By default we'll just catch and log any exceptions thrown by UpdateBitmap so they don't bring down
|
||||
// the application; a custom renderer can override this method and handle exceptions from
|
||||
// UpdateBitmap differently if it wants to
|
||||
|
||||
try
|
||||
{
|
||||
await UpdateBitmap(previous);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Warning(nameof(ImageRenderer), "Error loading image: {0}", ex);
|
||||
}
|
||||
finally
|
||||
{
|
||||
((IImageController)_element)?.SetIsLoading(false);
|
||||
}
|
||||
}
|
||||
|
||||
protected async Task UpdateBitmap(Image previous = null)
|
||||
{
|
||||
if (_element == null || _disposed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
await Control.UpdateBitmap(_element, previous);
|
||||
}
|
||||
|
||||
void UpdateAspect()
|
||||
{
|
||||
if (_element == null || _disposed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
ScaleType type = _element.Aspect.ToScaleType();
|
||||
SetScaleType(type);
|
||||
}
|
||||
|
|
|
@ -3,6 +3,7 @@ using System.Threading;
|
|||
using System.Threading.Tasks;
|
||||
using Android.Content;
|
||||
using Android.Graphics;
|
||||
using Xamarin.Forms.Internals;
|
||||
|
||||
namespace Xamarin.Forms.Platform.Android
|
||||
{
|
||||
|
@ -17,10 +18,18 @@ namespace Xamarin.Forms.Platform.Android
|
|||
public async Task<Bitmap> LoadImageAsync(ImageSource imagesource, Context context, CancellationToken cancelationToken = default(CancellationToken))
|
||||
{
|
||||
string file = ((FileImageSource)imagesource).File;
|
||||
Bitmap bitmap;
|
||||
if (File.Exists (file))
|
||||
return !DecodeSynchronously ? (await BitmapFactory.DecodeFileAsync (file).ConfigureAwait (false)) : BitmapFactory.DecodeFile (file);
|
||||
bitmap = !DecodeSynchronously ? (await BitmapFactory.DecodeFileAsync (file).ConfigureAwait (false)) : BitmapFactory.DecodeFile (file);
|
||||
else
|
||||
return !DecodeSynchronously ? (await context.Resources.GetBitmapAsync (file).ConfigureAwait (false)) : context.Resources.GetBitmap (file);
|
||||
bitmap = !DecodeSynchronously ? (await context.Resources.GetBitmapAsync (file).ConfigureAwait (false)) : context.Resources.GetBitmap (file);
|
||||
|
||||
if (bitmap == null)
|
||||
{
|
||||
Log.Warning(nameof(FileImageSourceHandler), "Could not find image or image file was invalid: {0}", imagesource);
|
||||
}
|
||||
|
||||
return bitmap;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -3,6 +3,7 @@ using System.Threading;
|
|||
using System.Threading.Tasks;
|
||||
using Android.Content;
|
||||
using Android.Graphics;
|
||||
using Xamarin.Forms.Internals;
|
||||
|
||||
namespace Xamarin.Forms.Platform.Android
|
||||
{
|
||||
|
@ -11,12 +12,19 @@ namespace Xamarin.Forms.Platform.Android
|
|||
public async Task<Bitmap> LoadImageAsync(ImageSource imagesource, Context context, CancellationToken cancelationToken = default(CancellationToken))
|
||||
{
|
||||
var imageLoader = imagesource as UriImageSource;
|
||||
if (imageLoader != null && imageLoader.Uri != null)
|
||||
Bitmap bitmap = null;
|
||||
if (imageLoader?.Uri != null)
|
||||
{
|
||||
using (Stream imageStream = await imageLoader.GetStreamAsync(cancelationToken).ConfigureAwait(false))
|
||||
return await BitmapFactory.DecodeStreamAsync(imageStream).ConfigureAwait(false);
|
||||
bitmap = await BitmapFactory.DecodeStreamAsync(imageStream).ConfigureAwait(false);
|
||||
}
|
||||
return null;
|
||||
|
||||
if (bitmap == null)
|
||||
{
|
||||
Log.Warning(nameof(ImageLoaderSourceHandler), "Could not retrieve image or image data was invalid: {0}", imageLoader);
|
||||
}
|
||||
|
||||
return bitmap;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,10 +1,10 @@
|
|||
using System;
|
||||
using System.ComponentModel;
|
||||
using System.Threading.Tasks;
|
||||
using Android.Graphics;
|
||||
using Android.Views;
|
||||
using AImageView = Android.Widget.ImageView;
|
||||
using Xamarin.Forms.Internals;
|
||||
using static Xamarin.Forms.Platform.Android.ImageViewExtensions;
|
||||
|
||||
namespace Xamarin.Forms.Platform.Android
|
||||
{
|
||||
|
@ -20,7 +20,6 @@ namespace Xamarin.Forms.Platform.Android
|
|||
|
||||
public ImageRenderer()
|
||||
{
|
||||
System.Diagnostics.Debug.WriteLine(">>>>> Old Image Renderer");
|
||||
AutoPackage = false;
|
||||
}
|
||||
|
||||
|
@ -39,7 +38,7 @@ namespace Xamarin.Forms.Platform.Android
|
|||
return new FormsImageView(Context);
|
||||
}
|
||||
|
||||
protected override void OnElementChanged(ElementChangedEventArgs<Image> e)
|
||||
protected override async void OnElementChanged(ElementChangedEventArgs<Image> e)
|
||||
{
|
||||
base.OnElementChanged(e);
|
||||
|
||||
|
@ -50,28 +49,63 @@ namespace Xamarin.Forms.Platform.Android
|
|||
}
|
||||
|
||||
_motionEventHelper.UpdateElement(e.NewElement);
|
||||
|
||||
Control.UpdateBitmap(e.NewElement, e.OldElement);
|
||||
|
||||
await TryUpdateBitmap(e.OldElement);
|
||||
|
||||
UpdateAspect();
|
||||
}
|
||||
|
||||
protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
|
||||
protected override async void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
|
||||
{
|
||||
base.OnElementPropertyChanged(sender, e);
|
||||
|
||||
if (e.PropertyName == Image.SourceProperty.PropertyName)
|
||||
Control.UpdateBitmap(Element);
|
||||
await TryUpdateBitmap();
|
||||
else if (e.PropertyName == Image.AspectProperty.PropertyName)
|
||||
UpdateAspect();
|
||||
}
|
||||
|
||||
void UpdateAspect()
|
||||
{
|
||||
if (Element == null || Control == null || Control.IsDisposed())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
AImageView.ScaleType type = Element.Aspect.ToScaleType();
|
||||
Control.SetScaleType(type);
|
||||
}
|
||||
|
||||
protected virtual async Task TryUpdateBitmap(Image previous = null)
|
||||
{
|
||||
// By default we'll just catch and log any exceptions thrown by UpdateBitmap so they don't bring down
|
||||
// the application; a custom renderer can override this method and handle exceptions from
|
||||
// UpdateBitmap differently if it wants to
|
||||
|
||||
try
|
||||
{
|
||||
await UpdateBitmap(previous);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Warning(nameof(ImageRenderer), "Error loading image: {0}", ex);
|
||||
}
|
||||
finally
|
||||
{
|
||||
((IImageController)Element)?.SetIsLoading(false);
|
||||
}
|
||||
}
|
||||
|
||||
protected async Task UpdateBitmap(Image previous = null)
|
||||
{
|
||||
if (Element == null || Control == null || Control.IsDisposed())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
await Control.UpdateBitmap(Element, previous);
|
||||
}
|
||||
|
||||
public override bool OnTouchEvent(MotionEvent e)
|
||||
{
|
||||
if (base.OnTouchEvent(e))
|
||||
|
|
|
@ -3,6 +3,7 @@ using System.Threading;
|
|||
using System.Threading.Tasks;
|
||||
using Android.Content;
|
||||
using Android.Graphics;
|
||||
using Xamarin.Forms.Internals;
|
||||
|
||||
namespace Xamarin.Forms.Platform.Android
|
||||
{
|
||||
|
@ -11,12 +12,19 @@ namespace Xamarin.Forms.Platform.Android
|
|||
public async Task<Bitmap> LoadImageAsync(ImageSource imagesource, Context context, CancellationToken cancelationToken = default(CancellationToken))
|
||||
{
|
||||
var streamsource = imagesource as StreamImageSource;
|
||||
if (streamsource != null && streamsource.Stream != null)
|
||||
Bitmap bitmap = null;
|
||||
if (streamsource?.Stream != null)
|
||||
{
|
||||
using (Stream stream = await ((IStreamImageSource)streamsource).GetStreamAsync(cancelationToken).ConfigureAwait(false))
|
||||
return await BitmapFactory.DecodeStreamAsync(stream).ConfigureAwait(false);
|
||||
bitmap = await BitmapFactory.DecodeStreamAsync(stream).ConfigureAwait(false);
|
||||
}
|
||||
return null;
|
||||
|
||||
if (bitmap == null)
|
||||
{
|
||||
Log.Warning(nameof(ImageLoaderSourceHandler), "Image data was invalid: {0}", streamsource);
|
||||
}
|
||||
|
||||
return bitmap;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -103,6 +103,7 @@
|
|||
<Compile Include="AndroidApplicationLifecycleState.cs" />
|
||||
<Compile Include="AndroidTitleBarVisibility.cs" />
|
||||
<Compile Include="AppCompat\FrameRenderer.cs" />
|
||||
<Compile Include="Extensions\JavaObjectExtensions.cs" />
|
||||
<Compile Include="FastRenderers\AccessibilityProvider.cs" />
|
||||
<Compile Include="FastRenderers\ButtonRenderer.cs" />
|
||||
<Compile Include="AppCompat\FormsViewPager.cs" />
|
||||
|
|
|
@ -17,6 +17,7 @@ namespace Xamarin.Forms.Platform.WinRT
|
|||
public class ImageRenderer : ViewRenderer<Image, Windows.UI.Xaml.Controls.Image>
|
||||
{
|
||||
bool _measured;
|
||||
bool _disposed;
|
||||
|
||||
public override SizeRequest GetDesiredSize(double widthConstraint, double heightConstraint)
|
||||
{
|
||||
|
@ -32,10 +33,20 @@ namespace Xamarin.Forms.Platform.WinRT
|
|||
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
if (Control != null)
|
||||
if (_disposed)
|
||||
{
|
||||
Control.ImageOpened -= OnImageOpened;
|
||||
Control.ImageFailed -= OnImageFailed;
|
||||
return;
|
||||
}
|
||||
|
||||
_disposed = true;
|
||||
|
||||
if (disposing)
|
||||
{
|
||||
if (Control != null)
|
||||
{
|
||||
Control.ImageOpened -= OnImageOpened;
|
||||
Control.ImageFailed -= OnImageFailed;
|
||||
}
|
||||
}
|
||||
|
||||
base.Dispose(disposing);
|
||||
|
@ -55,7 +66,7 @@ namespace Xamarin.Forms.Platform.WinRT
|
|||
SetNativeControl(image);
|
||||
}
|
||||
|
||||
await UpdateSource();
|
||||
await TryUpdateSource();
|
||||
UpdateAspect();
|
||||
}
|
||||
}
|
||||
|
@ -65,7 +76,7 @@ namespace Xamarin.Forms.Platform.WinRT
|
|||
base.OnElementPropertyChanged(sender, e);
|
||||
|
||||
if (e.PropertyName == Image.SourceProperty.PropertyName)
|
||||
await UpdateSource();
|
||||
await TryUpdateSource();
|
||||
else if (e.PropertyName == Image.AspectProperty.PropertyName)
|
||||
UpdateAspect();
|
||||
}
|
||||
|
@ -94,7 +105,7 @@ namespace Xamarin.Forms.Platform.WinRT
|
|||
Element?.SetIsLoading(false);
|
||||
}
|
||||
|
||||
void OnImageFailed(object sender, ExceptionRoutedEventArgs exceptionRoutedEventArgs)
|
||||
protected virtual void OnImageFailed(object sender, ExceptionRoutedEventArgs exceptionRoutedEventArgs)
|
||||
{
|
||||
Log.Warning("Image Loading", $"Image failed to load: {exceptionRoutedEventArgs.ErrorMessage}" );
|
||||
Element?.SetIsLoading(false);
|
||||
|
@ -107,6 +118,11 @@ namespace Xamarin.Forms.Platform.WinRT
|
|||
|
||||
void UpdateAspect()
|
||||
{
|
||||
if (_disposed || Element == null || Control == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Control.Stretch = GetStretch(Element.Aspect);
|
||||
if (Element.Aspect == Aspect.AspectFill) // Then Center Crop
|
||||
{
|
||||
|
@ -120,15 +136,40 @@ namespace Xamarin.Forms.Platform.WinRT
|
|||
}
|
||||
}
|
||||
|
||||
async Task UpdateSource()
|
||||
protected virtual async Task TryUpdateSource()
|
||||
{
|
||||
// By default we'll just catch and log any exceptions thrown by UpdateSource so we don't bring down
|
||||
// the application; a custom renderer can override this method and handle exceptions from
|
||||
// UpdateSource differently if it wants to
|
||||
|
||||
try
|
||||
{
|
||||
await UpdateSource().ConfigureAwait(false);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Warning(nameof(ImageRenderer), "Error loading image: {0}", ex);
|
||||
}
|
||||
finally
|
||||
{
|
||||
((IImageController)Element)?.SetIsLoading(false);
|
||||
}
|
||||
}
|
||||
|
||||
protected async Task UpdateSource()
|
||||
{
|
||||
if (_disposed || Element == null || Control == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Element.SetIsLoading(true);
|
||||
|
||||
ImageSource source = Element.Source;
|
||||
IImageSourceHandler handler;
|
||||
if (source != null && (handler = Registrar.Registered.GetHandler<IImageSourceHandler>(source.GetType())) != null)
|
||||
{
|
||||
Windows.UI.Xaml.Media.ImageSource imagesource = null;
|
||||
Windows.UI.Xaml.Media.ImageSource imagesource;
|
||||
|
||||
try
|
||||
{
|
||||
|
@ -138,10 +179,6 @@ namespace Xamarin.Forms.Platform.WinRT
|
|||
{
|
||||
imagesource = null;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Warning("Image Loading", $"Error updating image source: {ex}");
|
||||
}
|
||||
|
||||
// In the time it takes to await the imagesource, some zippy little app
|
||||
// might have disposed of this Image already.
|
||||
|
@ -155,7 +192,7 @@ namespace Xamarin.Forms.Platform.WinRT
|
|||
else
|
||||
{
|
||||
Control.Source = null;
|
||||
Element?.SetIsLoading(false);
|
||||
Element.SetIsLoading(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,6 +5,7 @@ using System.Threading;
|
|||
using System.Threading.Tasks;
|
||||
using Foundation;
|
||||
using UIKit;
|
||||
using Xamarin.Forms.Internals;
|
||||
using RectangleF = CoreGraphics.CGRect;
|
||||
|
||||
namespace Xamarin.Forms.Platform.iOS
|
||||
|
@ -30,8 +31,6 @@ namespace Xamarin.Forms.Platform.iOS
|
|||
{
|
||||
bool _isDisposed;
|
||||
|
||||
IElementController ElementController => Element as IElementController;
|
||||
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
if (_isDisposed)
|
||||
|
@ -43,7 +42,6 @@ namespace Xamarin.Forms.Platform.iOS
|
|||
if (Control != null && (oldUIImage = Control.Image) != null)
|
||||
{
|
||||
oldUIImage.Dispose();
|
||||
oldUIImage = null;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -52,7 +50,7 @@ namespace Xamarin.Forms.Platform.iOS
|
|||
base.Dispose(disposing);
|
||||
}
|
||||
|
||||
protected override void OnElementChanged(ElementChangedEventArgs<Image> e)
|
||||
protected override async void OnElementChanged(ElementChangedEventArgs<Image> e)
|
||||
{
|
||||
if (Control == null)
|
||||
{
|
||||
|
@ -65,18 +63,18 @@ namespace Xamarin.Forms.Platform.iOS
|
|||
if (e.NewElement != null)
|
||||
{
|
||||
SetAspect();
|
||||
SetImage(e.OldElement);
|
||||
await TrySetImage(e.OldElement);
|
||||
SetOpacity();
|
||||
}
|
||||
|
||||
base.OnElementChanged(e);
|
||||
}
|
||||
|
||||
protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
|
||||
protected override async void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
|
||||
{
|
||||
base.OnElementPropertyChanged(sender, e);
|
||||
if (e.PropertyName == Image.SourceProperty.PropertyName)
|
||||
SetImage();
|
||||
await TrySetImage();
|
||||
else if (e.PropertyName == Image.IsOpaqueProperty.PropertyName)
|
||||
SetOpacity();
|
||||
else if (e.PropertyName == Image.AspectProperty.PropertyName)
|
||||
|
@ -85,11 +83,41 @@ namespace Xamarin.Forms.Platform.iOS
|
|||
|
||||
void SetAspect()
|
||||
{
|
||||
if (_isDisposed || Element == null || Control == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Control.ContentMode = Element.Aspect.ToUIViewContentMode();
|
||||
}
|
||||
|
||||
async void SetImage(Image oldElement = null)
|
||||
protected virtual async Task TrySetImage(Image previous = null)
|
||||
{
|
||||
// By default we'll just catch and log any exceptions thrown by SetImage so they don't bring down
|
||||
// the application; a custom renderer can override this method and handle exceptions from
|
||||
// SetImage differently if it wants to
|
||||
|
||||
try
|
||||
{
|
||||
await SetImage(previous).ConfigureAwait(false);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Warning(nameof(ImageRenderer), "Error loading image: {0}", ex);
|
||||
}
|
||||
finally
|
||||
{
|
||||
((IImageController)Element)?.SetIsLoading(false);
|
||||
}
|
||||
}
|
||||
|
||||
protected async Task SetImage(Image oldElement = null)
|
||||
{
|
||||
if (_isDisposed || Element == null || Control == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var source = Element.Source;
|
||||
|
||||
if (oldElement != null)
|
||||
|
@ -108,7 +136,8 @@ namespace Xamarin.Forms.Platform.iOS
|
|||
|
||||
Element.SetIsLoading(true);
|
||||
|
||||
if (source != null && (handler = Internals.Registrar.Registered.GetHandler<IImageSourceHandler>(source.GetType())) != null)
|
||||
if (source != null &&
|
||||
(handler = Internals.Registrar.Registered.GetHandler<IImageSourceHandler>(source.GetType())) != null)
|
||||
{
|
||||
UIImage uiimage;
|
||||
try
|
||||
|
@ -120,22 +149,30 @@ namespace Xamarin.Forms.Platform.iOS
|
|||
uiimage = null;
|
||||
}
|
||||
|
||||
if (_isDisposed)
|
||||
return;
|
||||
|
||||
var imageView = Control;
|
||||
if (imageView != null)
|
||||
imageView.Image = uiimage;
|
||||
|
||||
if (!_isDisposed)
|
||||
((IVisualElementController)Element).NativeSizeChanged();
|
||||
((IVisualElementController)Element).NativeSizeChanged();
|
||||
}
|
||||
else
|
||||
{
|
||||
Control.Image = null;
|
||||
}
|
||||
|
||||
if (!_isDisposed)
|
||||
Element.SetIsLoading(false);
|
||||
Element.SetIsLoading(false);
|
||||
}
|
||||
|
||||
void SetOpacity()
|
||||
{
|
||||
if (_isDisposed || Element == null || Control == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Control.Opaque = Element.IsOpaque;
|
||||
}
|
||||
}
|
||||
|
@ -151,12 +188,15 @@ namespace Xamarin.Forms.Platform.iOS
|
|||
{
|
||||
UIImage image = null;
|
||||
var filesource = imagesource as FileImageSource;
|
||||
if (filesource != null)
|
||||
var file = filesource?.File;
|
||||
if (!string.IsNullOrEmpty(file))
|
||||
image = File.Exists(file) ? new UIImage(file) : UIImage.FromBundle(file);
|
||||
|
||||
if (image == null)
|
||||
{
|
||||
var file = filesource.File;
|
||||
if (!string.IsNullOrEmpty(file))
|
||||
image = File.Exists(file) ? new UIImage(file) : UIImage.FromBundle(file);
|
||||
Log.Warning(nameof(FileImageSourceHandler), "Could not find image: {0}", imagesource);
|
||||
}
|
||||
|
||||
return Task.FromResult(image);
|
||||
}
|
||||
}
|
||||
|
@ -167,7 +207,7 @@ namespace Xamarin.Forms.Platform.iOS
|
|||
{
|
||||
UIImage image = null;
|
||||
var streamsource = imagesource as StreamImageSource;
|
||||
if (streamsource != null && streamsource.Stream != null)
|
||||
if (streamsource?.Stream != null)
|
||||
{
|
||||
using (var streamImage = await ((IStreamImageSource)streamsource).GetStreamAsync(cancelationToken).ConfigureAwait(false))
|
||||
{
|
||||
|
@ -175,6 +215,12 @@ namespace Xamarin.Forms.Platform.iOS
|
|||
image = UIImage.LoadFromData(NSData.FromStream(streamImage), scale);
|
||||
}
|
||||
}
|
||||
|
||||
if (image == null)
|
||||
{
|
||||
Log.Warning(nameof(StreamImagesourceHandler), "Could not load image: {0}", streamsource);
|
||||
}
|
||||
|
||||
return image;
|
||||
}
|
||||
}
|
||||
|
@ -185,7 +231,7 @@ namespace Xamarin.Forms.Platform.iOS
|
|||
{
|
||||
UIImage image = null;
|
||||
var imageLoader = imagesource as UriImageSource;
|
||||
if (imageLoader != null && imageLoader.Uri != null)
|
||||
if (imageLoader?.Uri != null)
|
||||
{
|
||||
using (var streamImage = await imageLoader.GetStreamAsync(cancelationToken).ConfigureAwait(false))
|
||||
{
|
||||
|
@ -193,6 +239,12 @@ namespace Xamarin.Forms.Platform.iOS
|
|||
image = UIImage.LoadFromData(NSData.FromStream(streamImage), scale);
|
||||
}
|
||||
}
|
||||
|
||||
if (image == null)
|
||||
{
|
||||
Log.Warning(nameof(ImageLoaderSourceHandler), "Could not load image: {0}", imageLoader);
|
||||
}
|
||||
|
||||
return image;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -183,5 +183,22 @@
|
|||
<remarks>To be added.</remarks>
|
||||
</Docs>
|
||||
</Member>
|
||||
<Member MemberName="ToString">
|
||||
<MemberSignature Language="C#" Value="public override string ToString ();" />
|
||||
<MemberSignature Language="ILAsm" Value=".method public hidebysig virtual instance string ToString() cil managed" />
|
||||
<MemberType>Method</MemberType>
|
||||
<AssemblyInfo>
|
||||
<AssemblyVersion>2.0.0.0</AssemblyVersion>
|
||||
</AssemblyInfo>
|
||||
<ReturnValue>
|
||||
<ReturnType>System.String</ReturnType>
|
||||
</ReturnValue>
|
||||
<Parameters />
|
||||
<Docs>
|
||||
<summary>To be added.</summary>
|
||||
<returns>To be added.</returns>
|
||||
<remarks>To be added.</remarks>
|
||||
</Docs>
|
||||
</Member>
|
||||
</Members>
|
||||
</Type>
|
||||
|
|
|
@ -114,6 +114,23 @@
|
|||
<remarks>To be added.</remarks>
|
||||
</Docs>
|
||||
</Member>
|
||||
<Member MemberName="ToString">
|
||||
<MemberSignature Language="C#" Value="public override string ToString ();" />
|
||||
<MemberSignature Language="ILAsm" Value=".method public hidebysig virtual instance string ToString() cil managed" />
|
||||
<MemberType>Method</MemberType>
|
||||
<AssemblyInfo>
|
||||
<AssemblyVersion>2.0.0.0</AssemblyVersion>
|
||||
</AssemblyInfo>
|
||||
<ReturnValue>
|
||||
<ReturnType>System.String</ReturnType>
|
||||
</ReturnValue>
|
||||
<Parameters />
|
||||
<Docs>
|
||||
<summary>To be added.</summary>
|
||||
<returns>To be added.</returns>
|
||||
<remarks>To be added.</remarks>
|
||||
</Docs>
|
||||
</Member>
|
||||
<Member MemberName="Uri">
|
||||
<MemberSignature Language="C#" Value="public Uri Uri { get; set; }" />
|
||||
<MemberSignature Language="ILAsm" Value=".property instance class System.Uri Uri" />
|
||||
|
|
Загрузка…
Ссылка в новой задаче