diff options
9 files changed, 360 insertions, 9 deletions
diff --git a/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Bugzilla42069.cs b/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Bugzilla42069.cs new file mode 100644 index 00000000..7abfa5d3 --- /dev/null +++ b/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Bugzilla42069.cs @@ -0,0 +1,74 @@ +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 diff --git a/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Bugzilla42069_Page.xaml b/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Bugzilla42069_Page.xaml new file mode 100644 index 00000000..5f98e198 --- /dev/null +++ b/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Bugzilla42069_Page.xaml @@ -0,0 +1,59 @@ +<?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 diff --git a/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Bugzilla42069_Page.xaml.cs b/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Bugzilla42069_Page.xaml.cs new file mode 100644 index 00000000..2031e56c --- /dev/null +++ b/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Bugzilla42069_Page.xaml.cs @@ -0,0 +1,36 @@ +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 diff --git a/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Xamarin.Forms.Controls.Issues.Shared.projitems b/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Xamarin.Forms.Controls.Issues.Shared.projitems index 6531b47d..a98b97dc 100644 --- a/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Xamarin.Forms.Controls.Issues.Shared.projitems +++ b/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Xamarin.Forms.Controls.Issues.Shared.projitems @@ -110,6 +110,11 @@ <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" /> @@ -544,4 +549,10 @@ <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 diff --git a/Xamarin.Forms.Core/Image.cs b/Xamarin.Forms.Core/Image.cs index 4292f952..dd2692ad 100644 --- a/Xamarin.Forms.Core/Image.cs +++ b/Xamarin.Forms.Core/Image.cs @@ -1,4 +1,6 @@ using System; +using System.Collections.Generic; +using System.Reflection; using Xamarin.Forms.Internals; using Xamarin.Forms.Platform; @@ -7,8 +9,8 @@ namespace Xamarin.Forms [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); @@ -126,6 +128,7 @@ namespace Xamarin.Forms newvalue.SourceChanged += OnSourceChanged; SetInheritedBindingContext(newvalue, BindingContext); } + InvalidateMeasureInternal(InvalidationTrigger.MeasureChanged); } @@ -138,7 +141,7 @@ namespace Xamarin.Forms { if (oldvalue == null) return; - + oldvalue.SourceChanged -= OnSourceChanged; try { diff --git a/Xamarin.Forms.Core/ImageSource.cs b/Xamarin.Forms.Core/ImageSource.cs index f65d0610..3b59f7fa 100644 --- a/Xamarin.Forms.Core/ImageSource.cs +++ b/Xamarin.Forms.Core/ImageSource.cs @@ -14,6 +14,8 @@ namespace Xamarin.Forms TaskCompletionSource<bool> _completionSource; + readonly WeakEventManager _weakEventManager = new WeakEventManager(); + protected ImageSource() { } @@ -132,11 +134,13 @@ namespace Xamarin.Forms 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 diff --git a/Xamarin.Forms.Core/WeakEventManager.cs b/Xamarin.Forms.Core/WeakEventManager.cs new file mode 100644 index 00000000..226f329a --- /dev/null +++ b/Xamarin.Forms.Core/WeakEventManager.cs @@ -0,0 +1,159 @@ +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 diff --git a/Xamarin.Forms.Core/Xamarin.Forms.Core.csproj b/Xamarin.Forms.Core/Xamarin.Forms.Core.csproj index d0cb293c..0541673f 100644 --- a/Xamarin.Forms.Core/Xamarin.Forms.Core.csproj +++ b/Xamarin.Forms.Core/Xamarin.Forms.Core.csproj @@ -331,6 +331,7 @@ <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" /> diff --git a/Xamarin.Forms.Platform.WinRT/WindowsDeviceInfo.cs b/Xamarin.Forms.Platform.WinRT/WindowsDeviceInfo.cs index f3d6d100..235fe2db 100644 --- a/Xamarin.Forms.Platform.WinRT/WindowsDeviceInfo.cs +++ b/Xamarin.Forms.Platform.WinRT/WindowsDeviceInfo.cs @@ -77,9 +77,13 @@ namespace Xamarin.Forms.Platform.WinRT if (_isDisposed) return; + if (disposing) + { + _information.OrientationChanged -= OnOrientationChanged; + _information = null; + } + _isDisposed = true; - _information.OrientationChanged -= OnOrientationChanged; - _information = null; base.Dispose(disposing); } |