本篇借鑒了同事翔哥的勞動成果,在巨人的肩膀上把稿子又念了一遍。
內存泄漏的概念我這里就不說了,之前《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方法來釋放非托管資源。