--- /dev/null
+using System;
+using Xamarin.Forms.Controls.Issues;
+using Xamarin.Forms.CustomAttributes;
+using Xamarin.Forms.Internals;
+
+namespace Xamarin.Forms.Controls
+{
+ [Preserve(AllMembers = true)]
+ [Issue(IssueTracker.Bugzilla, 42069, "Garbage Collector can not collect pages that use ImageSource as a StaticResource",
+ PlatformAffected.All)]
+ public class Bugzilla42069 : TestNavigationPage
+ {
+ protected override void Init()
+ {
+ if (Application.Current.Resources == null)
+ {
+ Application.Current.Resources = new ResourceDictionary();
+ }
+
+ if (!Application.Current.Resources.ContainsKey("SomeSmallImage"))
+ {
+ var smallImage = new OnPlatform<ImageSource> {
+ Android = ImageSource.FromFile("coffee.png"),
+ WinPhone = ImageSource.FromFile("bank.png"),
+ iOS = ImageSource.FromFile("coffee.png")
+ };
+
+ Application.Current.Resources.Add("SomeSmallImage", smallImage);
+ }
+
+ const string instructions1 = @"Tap the Start button and follow the instructions on the next page.";
+ string instructions2 =
+ $"When you return to this page, tap the Collect button. The message \n'{Bugzilla42069_Page.DestructorMessage}'\n should appear at least once in the debug output.";
+
+ var label1 = new Label { Text = instructions1 };
+ var label2 = new Label { Text = instructions2, HorizontalTextAlignment = TextAlignment.Center };
+
+ var startButton = new Button { Text = "Start" };
+ startButton.Clicked += (sender, args) =>
+ {
+ // We have to do the push-pop-push dance because NavigationPage
+ // holds a reference to its last page for unrelated reasons; our concern
+ // here is that the first Bugzilla42069_Page that we pushed gets collected
+ PushAsync(new Bugzilla42069_Page(), false);
+ PopAsync(false);
+ PushAsync(new Bugzilla42069_Page(), false);
+ };
+
+ var collectButton = new Button { Text = "Collect" };
+ collectButton.Clicked += (sender, args) =>
+ {
+ GC.Collect();
+ GC.Collect();
+ GC.Collect();
+ };
+
+ var startPage = new ContentPage
+ {
+ Content = new StackLayout
+ {
+ Children =
+ {
+ label1,
+ startButton,
+ label2,
+ collectButton
+ }
+ }
+ };
+
+ PushAsync(startPage);
+ }
+ }
+}
\ No newline at end of file
--- /dev/null
+<?xml version="1.0" encoding="utf-8" ?>
+<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
+ xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
+ x:Class="Xamarin.Forms.Controls.Issues.Bugzilla42069_Page">
+ <StackLayout>
+ <Image x:Name="ChangingImage" />
+ <Button x:Name="Button2" Text="Change Image"></Button>
+ <Label Text="Tap the 'Change Image' button; the image at the top should toggle between two images. If the image doesn't toggle, the test has failed." ></Label>
+ <Label Text="There should be several instances of the same image below." ></Label>
+ <Button x:Name="Button" Text="Back"></Button>
+ <Label Text="Tap the 'Back' button." ></Label>
+ <Image Source="{StaticResource SomeSmallImage}" />
+ <Image Source="{StaticResource SomeSmallImage} "/>
+ <Image Source="{StaticResource SomeSmallImage}" />
+ <Image Source="{StaticResource SomeSmallImage}" />
+ <Image Source="{StaticResource SomeSmallImage}" />
+ <Image Source="{StaticResource SomeSmallImage}" />
+ <Image Source="{StaticResource SomeSmallImage}" />
+ <Image Source="{StaticResource SomeSmallImage}" />
+ <Image Source="{StaticResource SomeSmallImage}" />
+ <Image Source="{StaticResource SomeSmallImage}" />
+ <Image Source="{StaticResource SomeSmallImage}" />
+ <Image Source="{StaticResource SomeSmallImage}" />
+ <Image Source="{StaticResource SomeSmallImage}" />
+ <Image Source="{StaticResource SomeSmallImage}" />
+ <Image Source="{StaticResource SomeSmallImage}" />
+ <Image Source="{StaticResource SomeSmallImage}" />
+ <Image Source="{StaticResource SomeSmallImage}" />
+ <Image Source="{StaticResource SomeSmallImage}" />
+ <Image Source="{StaticResource SomeSmallImage}" />
+ <Image Source="{StaticResource SomeSmallImage}" />
+ <Image Source="{StaticResource SomeSmallImage}" />
+ <Image Source="{StaticResource SomeSmallImage}" />
+ <Image Source="{StaticResource SomeSmallImage}" />
+ <Image Source="{StaticResource SomeSmallImage}" />
+ <Image Source="{StaticResource SomeSmallImage}" />
+ <Image Source="{StaticResource SomeSmallImage}" />
+ <Image Source="{StaticResource SomeSmallImage}" />
+ <Image Source="{StaticResource SomeSmallImage}" />
+ <Image Source="{StaticResource SomeSmallImage}" />
+ <Image Source="{StaticResource SomeSmallImage}" />
+ <Image Source="{StaticResource SomeSmallImage}" />
+ <Image Source="{StaticResource SomeSmallImage}" />
+ <Image Source="{StaticResource SomeSmallImage}" />
+ <Image Source="{StaticResource SomeSmallImage}" />
+ <Image Source="{StaticResource SomeSmallImage}" />
+ <Image Source="{StaticResource SomeSmallImage}" />
+ <Image Source="{StaticResource SomeSmallImage}" />
+ <Image Source="{StaticResource SomeSmallImage}" />
+ <Image Source="{StaticResource SomeSmallImage}" />
+ <Image Source="{StaticResource SomeSmallImage}" />
+ <Image Source="{StaticResource SomeSmallImage}" />
+ <Image Source="{StaticResource SomeSmallImage}" />
+ <Image Source="{StaticResource SomeSmallImage}" />
+ <Image Source="{StaticResource SomeSmallImage}" />
+ <Image Source="{StaticResource SomeSmallImage}" />
+ <Image Source="{StaticResource SomeSmallImage}" />
+ </StackLayout>
+</ContentPage>
\ No newline at end of file
--- /dev/null
+using System;
+using System.Diagnostics;
+using System.Threading;
+
+namespace Xamarin.Forms.Controls.Issues
+{
+ public partial class Bugzilla42069_Page : ContentPage
+ {
+ public const string DestructorMessage = ">>>>>>>>>> Bugzilla42069_Page destructor <<<<<<<<<<";
+
+ public Bugzilla42069_Page()
+ {
+ InitializeComponent();
+
+ ImageWhichChanges = ImageSource.FromFile("oasissmall.jpg") as FileImageSource;
+
+ ChangingImage.SetBinding(Image.SourceProperty, nameof(ImageWhichChanges));
+
+ Button.Clicked += (sender, args) => Navigation.PopAsync(false);
+
+ Button2.Clicked += (sender, args) =>
+ {
+ ImageWhichChanges.File = ImageWhichChanges.File == "bank.png" ? "oasissmall.jpg" : "bank.png";
+ };
+
+ BindingContext = this;
+ }
+
+ ~Bugzilla42069_Page()
+ {
+ Debug.WriteLine(DestructorMessage);
+ }
+
+ public FileImageSource ImageWhichChanges { get; set; }
+ }
+}
\ No newline at end of file
<Compile Include="$(MSBuildThisFileDirectory)Bugzilla40998.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Bugzilla41205.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Bugzilla41424.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)Bugzilla42069.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)Bugzilla42069_Page.xaml.cs">
+ <DependentUpon>Bugzilla42069_Page.xaml</DependentUpon>
+ <SubType>Code</SubType>
+ </Compile>
<Compile Include="$(MSBuildThisFileDirectory)Bugzilla42074.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Bugzilla42075.cs" />
<Compile Include="$(MSBuildThisFileDirectory)CarouselAsync.cs" />
<Generator>MSBuild:UpdateDesignTimeXaml</Generator>
</EmbeddedResource>
</ItemGroup>
+ <ItemGroup>
+ <EmbeddedResource Include="$(MSBuildThisFileDirectory)Bugzilla42069_Page.xaml">
+ <SubType>Designer</SubType>
+ <Generator>MSBuild:UpdateDesignTimeXaml</Generator>
+ </EmbeddedResource>
+ </ItemGroup>
</Project>
\ No newline at end of file
using System;
+using System.Collections.Generic;
+using System.Reflection;
using Xamarin.Forms.Internals;
using Xamarin.Forms.Platform;
[RenderWith(typeof(_ImageRenderer))]
public class Image : View, IImageController
{
- public static readonly BindableProperty SourceProperty = BindableProperty.Create("Source", typeof(ImageSource), typeof(Image), default(ImageSource), propertyChanging: OnSourcePropertyChanging,
- propertyChanged: OnSourcePropertyChanged);
+ public static readonly BindableProperty SourceProperty = BindableProperty.Create("Source", typeof(ImageSource), typeof(Image), default(ImageSource),
+ propertyChanging: OnSourcePropertyChanging, propertyChanged: OnSourcePropertyChanged);
public static readonly BindableProperty AspectProperty = BindableProperty.Create("Aspect", typeof(Aspect), typeof(Image), Aspect.AspectFit);
newvalue.SourceChanged += OnSourceChanged;
SetInheritedBindingContext(newvalue, BindingContext);
}
+
InvalidateMeasureInternal(InvalidationTrigger.MeasureChanged);
}
{
if (oldvalue == null)
return;
-
+
oldvalue.SourceChanged -= OnSourceChanged;
try
{
TaskCompletionSource<bool> _completionSource;
+ readonly WeakEventManager _weakEventManager = new WeakEventManager();
+
protected ImageSource()
{
}
protected void OnSourceChanged()
{
- EventHandler eh = SourceChanged;
- if (eh != null)
- eh(this, EventArgs.Empty);
+ _weakEventManager.HandleEvent(this, EventArgs.Empty, nameof(SourceChanged));
}
- internal event EventHandler SourceChanged;
+ internal event EventHandler SourceChanged
+ {
+ add { _weakEventManager.AddEventHandler(nameof(SourceChanged), value); }
+ remove { _weakEventManager.RemoveEventHandler(nameof(SourceChanged), value);}
+ }
}
}
\ No newline at end of file
--- /dev/null
+using System;
+using System.Collections.Generic;
+using System.Reflection;
+
+namespace Xamarin.Forms
+{
+ internal class WeakEventManager
+ {
+ readonly Dictionary<string, List<Subscription>> _eventHandlers =
+ new Dictionary<string, List<Subscription>>();
+
+ public void AddEventHandler<TEventArgs>(string eventName, EventHandler<TEventArgs> handler)
+ where TEventArgs : EventArgs
+ {
+ if (eventName == null)
+ {
+ throw new ArgumentNullException(nameof(eventName));
+ }
+
+ if (handler == null)
+ {
+ throw new ArgumentNullException(nameof(handler));
+ }
+
+ AddEventHandler(eventName, handler.Target, handler.GetMethodInfo());
+ }
+
+ public void AddEventHandler(string eventName, EventHandler handler)
+ {
+ if (eventName == null)
+ {
+ throw new ArgumentNullException(nameof(eventName));
+ }
+
+ if (handler == null)
+ {
+ throw new ArgumentNullException(nameof(handler));
+ }
+
+ AddEventHandler(eventName, handler.Target, handler.GetMethodInfo());
+ }
+
+ public void HandleEvent(object sender, object args, string eventName)
+ {
+ var toRaise = new List<Tuple<object, MethodInfo>>();
+
+ List<Subscription> target;
+ if (_eventHandlers.TryGetValue(eventName, out target))
+ {
+ foreach (Subscription subscription in target)
+ {
+ object subscriber = subscription.Subscriber.Target;
+
+ if (subscriber == null)
+ {
+ // The subscriber was collected, so there's no need to keep this subscription around
+ target.Remove(subscription);
+ }
+ else
+ {
+ toRaise.Add(Tuple.Create(subscriber, subscription.Handler));
+ }
+ }
+ }
+
+ foreach (Tuple<object, MethodInfo> tuple in toRaise)
+ {
+ tuple.Item2.Invoke(tuple.Item1, new[] { sender, args });
+ }
+ }
+
+ public void RemoveEventHandler<TEventArgs>(string eventName, EventHandler<TEventArgs> handler)
+ where TEventArgs : EventArgs
+ {
+ if (eventName == null)
+ {
+ throw new ArgumentNullException(nameof(eventName));
+ }
+
+ if (handler == null)
+ {
+ throw new ArgumentNullException(nameof(handler));
+ }
+
+ RemoveEventHandler(eventName, handler.Target, handler.GetMethodInfo());
+ }
+
+ public void RemoveEventHandler(string eventName, EventHandler handler)
+ {
+ if (eventName == null)
+ {
+ throw new ArgumentNullException(nameof(eventName));
+ }
+
+ if (handler == null)
+ {
+ throw new ArgumentNullException(nameof(handler));
+ }
+
+ RemoveEventHandler(eventName, handler.Target, handler.GetMethodInfo());
+ }
+
+ void AddEventHandler(string eventName, object handlerTarget, MethodInfo methodInfo)
+ {
+ List<Subscription> target;
+ if (!_eventHandlers.TryGetValue(eventName, out target))
+ {
+ target = new List<Subscription>();
+ _eventHandlers.Add(eventName, target);
+ }
+
+ target.Add(new Subscription(new WeakReference(handlerTarget), methodInfo));
+ }
+
+ void RemoveEventHandler(string eventName, object handlerTarget, MemberInfo methodInfo)
+ {
+ List<Subscription> subscriptions;
+ if (!_eventHandlers.TryGetValue(eventName, out subscriptions))
+ {
+ return;
+ }
+
+ for (int n = subscriptions.Count; n > 0; n--)
+ {
+ Subscription current = subscriptions[n - 1];
+
+ if (current.Subscriber != handlerTarget
+ || current.Handler.Name != methodInfo.Name)
+ {
+ continue;
+ }
+
+ subscriptions.Remove(current);
+ }
+ }
+
+ struct Subscription
+ {
+ public Subscription(WeakReference subscriber, MethodInfo handler)
+ {
+ if (subscriber == null)
+ {
+ throw new ArgumentNullException(nameof(subscriber));
+ }
+
+ if (handler == null)
+ {
+ throw new ArgumentNullException(nameof(handler));
+ }
+
+ Subscriber = subscriber;
+ Handler = handler;
+ }
+
+ public readonly WeakReference Subscriber;
+ public readonly MethodInfo Handler;
+ }
+ }
+}
\ No newline at end of file
<Compile Include="Internals\ToolbarTracker.cs" />
<Compile Include="ViewExtensions.cs" />
<Compile Include="ViewState.cs" />
+ <Compile Include="WeakEventManager.cs" />
<Compile Include="WeakReferenceExtensions.cs" />
<Compile Include="WebNavigatedEventArgs.cs" />
<Compile Include="WebNavigatingEventArgs.cs" />
if (_isDisposed)
return;
+ if (disposing)
+ {
+ _information.OrientationChanged -= OnOrientationChanged;
+ _information = null;
+ }
+
_isDisposed = true;
- _information.OrientationChanged -= OnOrientationChanged;
- _information = null;
base.Dispose(disposing);
}