本篇借鉴了同事翔哥的劳动成果,在巨人的肩膀上把稿子又念了一遍。
内存泄漏的概念我这里就不说了,之前《UWP开发入门(十三)——用Diagnostic Tool检查内存泄漏》中提到过,即使有垃圾回收机制,写C#还是有可能发生内存泄漏。
一般来说,以下两种情况会导致内存泄漏:
- 对象用完了但是没有释放资源
- 对象本身是做了清理内存的操作,但是对象内部的子对象没有成功释放资源
下面就UWP开发中具体的实例来说明需要避免的写法
- 从static/global的对象上注册了事件
FakeService.Instance.ShowMeTheMoneyEvent += Instance_ShowMeTheMoneyEvent;
比如我们有一个底层的FakeService,提供整个APP生命周期的数据和网络的访问。假设某个页面+=了这个FackService的Event,在离开页面时没有-=掉。那么该页面就无法被垃圾回收。
合理的做法是在OnNavigatedFrom方法里,把事件反注册掉。
protected override void OnNavigatedFrom(NavigationEventArgs e) { base.OnNavigatedFrom(e); FakeService.Instance.ShowMeTheMoneyEvent -= Instance_ShowMeTheMoneyEvent; }
- DispatcherTimer事件未关闭
这种情况就属于对象内部的属性未能被释放,假设页面内部存在Timer对象:
public sealed partial class TimerPage : Page { private DispatcherTimer Timer { get; set; } = new DispatcherTimer(); public ArrayList arrayList { get; set; } public TimerPage() { this.InitializeComponent(); arrayList = new ArrayList(10000000); Timer.Tick += Timer_Tick; Timer.Interval = TimeSpan.FromSeconds(1); Timer.Start(); } private void Timer_Tick(object sender, object e) { int count = 0; int.TryParse(TextBoxTimer.Text, out count); count += 1; TextBoxTimer.Text = count.ToString(); } private void Button_Click(object sender, RoutedEventArgs e) { this.Frame.GoBack(); } protected override void OnNavigatedFrom(NavigationEventArgs e) { base.OnNavigatedFrom(e); Timer.Stop(); } }
如果在离开页面之前,未调用Timer对象的Stop方法,也未-=Tick事件(这里Stop方法会自动-=Tick事件)。该页面就不能正常的回收。
这里并不是说所有的Event都需要在OnNavigatedFrom方法中-=,例如Control本身的Loaded、IsEnabledChanged等事件等并不会造成内存泄漏,反注册这些事件是为了避免事件的重复触发。而DispatcherTimer比较特殊,我理解它会把自己加到一个专门维护计时器的队列中,然后不停的触发Tick事件,如果没有Stop或-=,就等于Timer一直引用了外部的对象,从而导致页面本身也无法回收。
- Data Binding Memory Leak
这一条在很多的文档上有所提及,很遗憾我没法通过Diagnostic Tools监测出来具体的泄漏,我猜测可能是很小规模的内存泄漏。但是避免的方式非常容易,只要平时写XAML注意一下就可以了。
会出现问题的写法是以下两种:
- 未实现INotifyPropertyChanged的对象,而你又想监测Property变化
- 未实现INotifyCollectionChanged 接口的集合,而你又想监测Collection变化
其实很好处理。如果想监测变化,就老老实实继承对应的接口。如果使用了普通的Property和集合,并且不想监测变化,一定记得Mode = OneTime。
当然如果属性本身是dependency property,就不存在内存泄漏的情况了。
<!--内存泄漏,因为Children集合没有实现INotifyPropertyChanged来通知Count属性变化--> <TextBlock Text="{Binding ElementName=layoutRoot, Path=Children.Count}" /> <!--不会内存泄漏,因为ActualWidth是依赖属性--> <TextBlock Text="{Binding ElementName=layoutRoot, Path=ActualWidth}" /> <!--不会内存泄漏,因为Mode = OneTime--> <TextBlock Text="{Binding ElementName=layoutRoot, Path=Children.Count, Mode = OneTime}" />
- 非托管资源的释放
这个都非常熟悉,不多说了。主要是通过using语句,或者在try { … } finally { … }中调用Dispose或者Close方法来释放非托管资源。