UWP開發入門(十六)——常見的內存泄漏的原因


  本篇借鑒了同事翔哥的勞動成果,在巨人的肩膀上把稿子又念了一遍。

  內存泄漏的概念我這里就不說了,之前《UWP開發入門(十三)——Diagnostic Tool檢查內存泄漏》中提到過,即使有垃圾回收機制,寫C#還是有可能發生內存泄漏。

  一般來說,以下兩種情況會導致內存泄漏:

  1. 對象用完了但是沒有釋放資源
  1. 對象本身是做了清理內存的操作,但是對象內部的子對象沒有成功釋放資源

  下面就UWP開發中具體的實例來說明需要避免的寫法

  • static/global的對象上注冊了事件
FakeService.Instance.ShowMeTheMoneyEvent += Instance_ShowMeTheMoneyEvent;

  比如我們有一個底層的FakeService,提供整個APP生命周期的數據和網絡的訪問。假設某個頁面+=了這個FackServiceEvent,在離開頁面時沒有-=掉。那么該頁面就無法被垃圾回收。

合理的做法是在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本身的LoadedIsEnabledChanged等事件等並不會造成內存泄漏,反注冊這些事件是為了避免事件的重復觸發。而DispatcherTimer比較特殊,我理解它會把自己加到一個專門維護計時器的隊列中,然后不停的觸發Tick事件,如果沒有Stop-=,就等於Timer一直引用了外部的對象,從而導致頁面本身也無法回收。

  • Data Binding Memory Leak

  這一條在很多的文檔上有所提及,很遺憾我沒法通過Diagnostic Tools監測出來具體的泄漏,我猜測可能是很小規模的內存泄漏。但是避免的方式非常容易,只要平時寫XAML注意一下就可以了。

  會出現問題的寫法是以下兩種:

  1. 未實現INotifyPropertyChanged的對象,而你又想監測Property變化
  2. 未實現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方法來釋放非托管資源。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM