在windows phone 中常在列表中会常包含比较丰富文字和图片混排数据信息. 针对列表数据中除了谈到listbox等控件自身数据虚拟化问题外.虽然wp硬件设备随着SDK 8.0 发布获得应用可以使用内存空间获得了很大扩展. 但为了保证WP 平台在低配置机型一样的应用操做用户体验. 性能调优则是没法避免的问题.css
早期在Windows phone 7 版本是受制于当时CE内核对硬件上限制.单个应用最高内存峰值是90M.当应用程序内存超过该峰值没有任何提示会自动退出.随着windows phone 8 采用NT内核.硬件设备获得必定扩展.在WP SDK 8.0中 关于内存上限随着设备不断演化而存在不一样的峰值.更高的内存上限也因设备的类型而有所不一样。一般,拥有 1 GB 以上内存的手机被视为高内存设备,可是这也需视设备而定。例如,若是手机拥有高分辨率的相机,就应用用途而言,该手机将被视为低内存设备。下表列出了这些类别中的默认内存上限和更高内存上限:html
固然咱们也能够经过获取DeviceExtendedProperties.GetValue(String) 方法检查 ApplicationWorkingSetLimit 值,借此检查应用可用的内存峰值.git
而相对Windows phone 7版本达到峰值后自动退出的作法. WP SDK 8.0 作了更多可选项. 而不是简单粗暴采用直接退出的方式.为了保证咱们应用可以覆盖更多wp终端.对于低端配置的的手机.能够请求更多的内存或是彻底放弃在低端适配.但愿应用对全部手机均可用(但这同时可能会因占用更多内存而影响其余手机任务),必须将 FunctionalCapability 条目添加到应用清单文件。要退出 低内存设备,必须将 Requirements 条目添加到清单.github
有了这样的选项.则能够很自由选择当前应用在内存使用对于机型的适配.固然对于最好的方法.仍是从应用程序角度优化咱们应用.达到低端机型也能适配的效果.windows
本篇来重点谈谈关于windows phone 应用程序中图片性能的优化.api
首先抛开xaml文件来讲所windows phone 图片经常使用支持的格式png和jpg. jpg相对于png 解码的速度要更快.单个图片显示并不明显.在大批量数据UI呈现上才能有必定体现.因此原则上来讲对于图片资源选择jpg格式要优先.但若是UI呈现相似Backgound Image背景图片有透明的要求.则只能选择png格式.jpg并不支持背景透明.缓存
把图片拿到xaml文件中来讲.默认状况下全部的图片的解码过程都是在UI线程同步进行,因此若是用以下方式显示图片将会将会在必定程度上阻塞UI线程:安全
1: <Image Source=”{Binding ImageUrl}”/>
xaml在ui显示时会对bingding的图片进行解码.解码完成后才会显示ui上来.而这个过程当中ui进程会一直阻塞到解码结束才会显示出来.因此通常状况咱们针对Image图片资源获取采用后台进程方式来加载:性能优化
1: <Image>
2: <Image.Source>
3: <BitmapImage UriSource="{Binding ImgUrl}" CreateOptions="BackgroundCreation"/>
4: </Image.Source>
5: </Image>
但即便这样设置咱们实际测试发现并无明显的出别. 但若是咱们加载一个比较大图片.加载的时间和UI阻塞上则会出现很明显的卡顿延迟. 其实说到这里咱们UI控制图片显示会分为两种形式 一种绑定另一种经过后台进行直接赋值的方式. 当咱们在后台代码中向一个Image控件直接赋值BitmapImage. 对于大图片一般会遇到OutOfMemory内存溢出的问题.以下代码加载一个大图片就会出现:网络
1: using (var isoFile = IsolatedStorageFile.GetUserStoreForApplication())
2: {
3: const string filePath = @"Shared\ShellContent\FlipBackImage.jpg";
4: var filename = "Image.png";
5: var stream = !isoFile.FileExists(filename) ? null : isoFile.OpenFile(filename, FileMode.Open, FileAccess.Read);
6: if (stream != null)
7: {
8: if (isoFile.FileExists(filePath))
9: isoFile.DeleteFile(filePath);
10:
11: Debug.WriteLine("currentMemory"+DeviceStatus.ApplicationCurrentMemoryUsage);
12: var bi = new BitmapImage();
13: bi.CreateOptions = BitmapCreateOptions.None;
14: bi.SetSource(stream); //out of memory exception getting here
15:
16: var wbm=new WriteableBitmap(bi);
17: using (var streamFront = isoFile.OpenFile(filePath, FileMode.Create))
18: {
19: wbm.SaveJpeg(streamFront, 691, 336, 0, 80);
20: }
21: Deployment.Current.Dispatcher.BeginInvoke(() => Utils.DisposeImage(wbm));
22: }
23: }
其实出现outofMemory内存溢出的问题若是仔细分析会发现.加入这段代码测试采用1500*1000宽高的高分辨率图片.每一个像素pixel占用内存空间为4KB.咱们来计算一下一张大图片在UI渲染显示后占用的内存空间为:1500 x 1100 x 4 = 6600000 bytes = > 6.5 MB 接近了6.5M左右的大小.受限于手机有限的屏幕分辨率.更大的图片应在低分辨率下取样后显示。若是图片大于2000*2000其显示会明显减慢。特别在低端设备更为明显.固然也有人针对windows phone 大图片的显示给出解决方案:
每次只显示图片的一部分。能够经过先将图片载入到一个T:System.Windows.Media.Imaging.WriteableBitmap中,而后使用LoadJpeg(WriteableBitmap, Stream)扩展方法来载入图片
这种能有效规避如上出现outofmemory内存泄露的异常.其实相对WriteableBitmap对象而言有一种更好的方式.当咱们载入图片资源后.其实内存很大一部分用在图片解码上消耗.若是咱们能够采用WriteableBitmap的DecodePixelWidth 和DecodePixelHeight 属性 来避免图片从新解码.获取器数据流Stream在Memory内存中操做对象.这样也就避免在显示时内存过多的开销.但这里指的注意的是DecodePixelWidth 和DecodePixelHeight在某些状况可能为空值既Null.须要额外处理一下.
另外若是你用过全景视图控件.默认background是黑色.大多的状况咱们会设置背景图片来填充.其实background是一个特殊的元素,被限定到2048X2048。超过此大小的会被剪裁,这就意味着使用一个长宽超过2048像素的的图片,该图片不会彻底显示。为了不这种状况的发生,WP7平台会自动的下降像素是图片去适应2048X2048。这一点不一样于桌面Silverlight应用,应为silverlight不存在上述限制.
这里指的一提的是.WriteableBitmap目前只在windows phone sdk提供了一个SaveJpeg方法既吧当前资源编码成一个JPEG流.但并无提供png格式的方法. 若是你须要经过WriteableBitmap编码输出成一个png格式的stream流.你能够参考以下解决方案:
能够采用writeablebitmapex 开源第三方库对WriteableBitmap添加一个扩展方法WritePNG扩展方法来实现png格式stream流的转换
其实本质就是基于WriteableBitmap对象添加了两个扩展方法.该扩展方法是基于ToolStackCRCLib和ToolStackPNGWriterLib库实现的.并移植了windows phone 版本,核心扩展类以下:
1: using System.IO;
2: using System.IO.IsolatedStorage;
3: using System.Windows.Shapes;
4: using System.Windows.Media;
5: using ToolStackCRCLib;
6: using ToolStackPNGWriterLib;
7:
8: namespace System.Windows.Media.Imaging
9: {
10: /// <summary>
11: /// WriteableBitmap Extensions for PNG Writing
12: /// </summary>
13: public static partial class WriteableBitmapExtensions
14: {
15: /// <summary>
16: /// Write and PNG file out to a file stream. Currently compression is not supported.
17: /// </summary>
18: /// <param name="image">The WriteableBitmap to work on.</param>
19: /// <param name="stream">The destination file stream.</param>
20: public static void WritePNG(this WriteableBitmap image, System.IO.Stream stream)
21: {
22: WritePNG(image, stream, -1);
23: }
24:
25: /// <summary>
26: /// Write and PNG file out to a file stream. Currently compression is not supported.
27: /// </summary>
28: /// <param name="image">The WriteableBitmap to work on.</param>
29: /// <param name="stream">The destination file stream.</param>
30: /// <param name="compression">Level of compression to use (-1=auto, 0=none, 1-100 is percentage).</param>
31: public static void WritePNG(this WriteableBitmap image, System.IO.Stream stream, int compression)
32: {
33: PNGWriter.DetectWBByteOrder();
34: PNGWriter.WritePNG(image, stream, compression);
35: }
36: }
37: }
只须要在调用WriteableBitmap 扩展方法中WritePng便可轻松实现把文件输出一个png格式stream流对象.关于该类库具体使用请参考官方的文档.有关图像的API中都是以图片自身的分辨率解码[除了Downsampling缩减像素采样].若是下载一批600X600的图片,但只以60X60那么小显示,那么还以原分辨率解码就会浪费应用程序的的内存资源.还好windows phone 平台上提供一个PictureDecoder的接口,可以自定义分辨率去解码图片 来节省更多的内存开销:
1: image.Source = PictureDecoder.DecodeJpeg(jpgStream, 60, 60);
刚才上面说到UI xaml文件咱们处理图片两种方式:
两种方式:
A: 经过后台代码对Xaml文件中Image控件直接赋值
B:采用数据绑定方式呈现Image.
其实若是采用后台赋值的方式.首先须要Xaml文件中定义一个Image控件.采用经过UriSource或是Source方式进行赋值操做便可.其实若是你仔细研究过这个问题会发现.即便清空了Source属性为null而且把Image从visual tree中移除掉,Image的内存仍是不会释放,查看内存占用并无少.且页面退出并无当即图片占用内容进行垃圾回收.本质的问题目前图片缓存依然还占用着内存的资源.这是实际上是一个预留的性能优化机制,实际上为了不一遍又一遍的加载和解码相同的图片。windows phone 中在内存中开辟了一个缓冲区,利用它方便快捷的再利用图片资源,减缓每次UI响应的时间.虽然图片缓存对于提升性能有很大的帮助,可是有时候也会增长没必要要的内存消耗。特别咱们打算回收那些不会再显示的图片并把他内存及时释放掉. 其实能够采用以下代码来操做xaml中image空间方式可以作到:
1: BitmapImage bitmapImage = image.Source as BitmapImage;
2: bitmapImage.UriSource = null;
3: image.Source = null;
在最上面xaml文件代码中咱们提到采用BitmapImage 对象的CreateOptions设置为BackgroundCreation. 来减小Ui线程在显示上阻塞.其实BitmapImage对象CreateOptions默认值为DelayCreation. 当你在页面这样设置时.页面一次加载成功后.固然尝试去访问这个图片大小时,却发现值为空Null.你有可能会问:为何当已经完成图片建立时仍不会返回图片的大小?这是由于CreateOptions 属性默认被设置成了“DelayCreation”,也就是说当BitmapImage被设置成一个Image的Source属性上或者是可视化树的ImageBrush上,这个对象只有在真正须要它的时候才会被建立.当这个真正被建立时xaml文件中Image控件会自动触发ImageOpened事件.能够在这里获取实际图片大小.
再回到BitmapImage 对象的CreateOptions属性.其实当页面加载时.能够直接不少的BitmapImage,却不消耗任何的资源(CPU或者是内存)直到程序须要特定的图片才会真正建立它们.固然若是你想图片在页面建立当即显示出来.也就是当即执行建立操做.你能够把CreateOptions设置成“None”便可.关于这个属性合理使用 后面还会提到.
如上提到不少关于图片在实际操做可能影响性能以及减小内存开销一些小细节.其实真正实际项目应用场景.咱们大多对于批量的图片是采用集合控件Listbox等数据绑定来显示的.不少人在都采用MVVM模式来绑定展示数据.如何在MVVM模式下来优化集合控件中图片性能? 下半段重点讲讲这个问题.
首先新建一个项目.命名为:PictureMemoryUsageDemo. 在主页面Xaml文件采用Listbox来采用MVVM形式来绑定图片显示,Xaml UI 布局以下:
1: <!--ContentPanel - place additional content here-->
2: <Grid x:Name="ContentPanel" Grid.Row="1" Margin="24,0,12,0">
3: <ListBox x:Name="ControlMemory_LB" ItemsSource="{Binding TripPictureCol}" SelectionChanged="ControlMemory_LB_SelectionChanged">
4: <ListBox.ItemsPanel>
5: <ItemsPanelTemplate>
6: <tool:WrapPanel Orientation="Horizontal"></tool:WrapPanel>
7: </ItemsPanelTemplate>
8: </ListBox.ItemsPanel>
9: <ListBox.ItemTemplate>
10: <DataTemplate>
11: <StackPanel Margin="10,0,0,0">
12: <Image Source="{Binding TripPictureUrl}" Width="100" Height="100"></Image>
13: </StackPanel>
14: </DataTemplate>
15: </ListBox.ItemTemplate>
16: </ListBox>
17: </Grid>
一个listBox很单一就是简单绑定一个Image 进行横向布局. 后台代码绑定一个标准的ViewModel 绑定形式以下:
1: void MainPage_Loaded(object sender, RoutedEventArgs e)
2: {
3: if (_tripPicViewModel == null)
4: _tripPicViewModel = new TripPictureViewModel();
5: this.DataContext = _tripPicViewModel;
6: }
z在ViewModel 模拟一个图片数据的ObserverCollection<T>集合进行绑定 ViewModel 代码以下:
1: public class TripPictureViewModel:INotifyPropertyChanged
2: {
3: #region Property
4: public event PropertyChangedEventHandler PropertyChanged;
5: private ObservableCollection<TripPictureInfo> _tripPictureCol = new ObservableCollection<TripPictureInfo>();
6: public ObservableCollection<TripPictureInfo> TripPictureCol
7: {
8: get { return _tripPictureCol; }
9: set
10: {
11: _tripPictureCol = value;
12: BindProertyChangedEventHandler("TripPictureCol");
13: }
14: }
15: #endregion
16:
17: public TripPictureViewModel()
18: {
19: LoadTripAddressPictureData();
20: }
21:
22: #region Action
23:
24: public void BindProertyChangedEventHandler(string propertyName)
25: {
26: if (string.IsNullOrEmpty(propertyName))
27: return;
28:
29: PropertyChangedEventHandler eventHandler = this.PropertyChanged;
30: if (eventHandler != null)
31: eventHandler(this, new PropertyChangedEventArgs(propertyName));
32: }
33:
34: public void LoadTripAddressPictureData()
35: {
36: string pictureHealderStr = "/Images/670(";
37: string pictureFooterStr = ").jpg";
38: for (int count = 0; count < 20; count++)
39: _tripPictureCol.Add(new TripPictureInfo() { CityName="乌克兰", StatusCode=(count+1).ToString(), TripPictureUrl=pictureHealderStr+(count+1).ToString()+pictureFooterStr});
40: }
41: #endregion
42:
43: }
一个标准的ViewModel绑定就这样结束了.咱们直接打开页面能够发现UI呈如今UI图片横向的排列效果以下:
通常状况下咱们在页面绑定一个ListBox 集合来呈现简单图片显示.这个时候咱们不作任何关于图片的处理.在这个功能基础对当前应用在使用过程内存使用状况进行记录.在单独添加一个内存使用记录显示页面命名为MemoryLogView.xaml .能够经过DeviceStatus.ApplicationCurrentMemoryUsage 属性来获取当前应用内存使用状况.那咱们应该在哪里添加日志记录呢?
首先来看看第一次进入MainPage 时整个PhoneApplicationPage的生命周期.调用流程.首先经过构造方法MainPage().执行完InitializeComponent() 后再加载Loaded()事件.如今构造方法和Load事件开始结束位置分别添加当前内容监控日志:
1: public MainPage()
2: {
3: LogRecordHelper.AddLogRecord("MainPage Init Before:", DeviceStatus.ApplicationCurrentMemoryUsage.ToString()+" B");
4: InitializeComponent();
5: this.Loaded += MainPage_Loaded;
6: LogRecordHelper.AddLogRecord("MainPage Init After:", DeviceStatus.ApplicationCurrentMemoryUsage.ToString() + " B");
7: }
Loaded方法 添加内存使用状况日志监控:
1: void MainPage_Loaded(object sender, RoutedEventArgs e)
2: {
3: LogRecordHelper.AddLogRecord("MainPage Load Before:", DeviceStatus.ApplicationCurrentMemoryUsage.ToString() + " B");
4: if (_tripPicViewModel == null)
5: _tripPicViewModel = new TripPictureViewModel();
6: this.DataContext = _tripPicViewModel;
7: LogRecordHelper.AddLogRecord("MainPage Load After:", DeviceStatus.ApplicationCurrentMemoryUsage.ToString() + " B");
8: }
这时咱们打开应用.日志会自动记录当前内存使用状况. 第一次进入应用内存使用状况以下:
可见当第一次进入应用时. 在MainPage 构造函数并无消耗更多的内存.只是在当数据成功载入时Load Before 和Load After变化偏大.在从日志界面backup 到MainPage 反复操做屡次发现内存使用记录以下:
咱们能够很明显的发现.一样的数据.在进过页面跳转后当前内存逐步增长. 而这个过程当中并无增长ui数据.这是为什么? 针对这个问题 咱们在页面添加MainPage 析构函数.并执行函数时获取当前内存使用记录:
1: ~MainPage()
2: {
3: LogRecordHelper.AddLogRecord("MainPage Destructor Excuted:", DeviceStatus.ApplicationCurrentMemoryUsage.ToString() + " B");
4: }
咱们再重复执行刚才的操做.来看当前页面析构函数是否执行?.MainPage页面 是否在退出时正常释放了该页面数据内存,通过屡次测试发现析构函数成功执行了,并记录当前当前内存使用记录:
虽然析构函数成功执行了.并非每次离开MainPage页面才执行的. 首先须要说明析构函数对整个PhoneApplicationPage生命周期的意义.析构函数和构造函数的做用偏偏相反.当对象脱离其做用域时(例如对象所在的函数已调用完毕),系统自动执行析构函数来释放资源.其实实际的状况是代码中咱们没法控制什么时候调用析构函数,由于这由垃圾回收器决定。垃圾回收器检查是否存在应用程序再也不使用的对象。若是垃圾回收器认为某个对象符合析构条件,则调用析构函数(若是有的话),回收该对象的内存。程序退出时一样也会调用析构函数.
回到刚才的操做.虽然咱们MainPage页面成功执行了析构函数. 这里有几个疑问是? 执行析构函数的并不是是在离开MainPage页面时执行的. 另一个问题是虽然咱们成功执行析构函数但咱们发现实际内存使用的状况并无减小. 那么如何在采用数据绑定MVVM摸下释放页面占用的内存空间?
若是咱们离开MainPage时发现析构函数没有被执行.这时咱们就要采起策略. 通常状况下咱们采用DataContent=ViewModel进行数据绑定. 在页面离开由于View的DataContent 属性于ViewModel之间存在引用关系依赖. 导致View在离开得不到销毁. 咱们能够在OnRemovedFromJournal()方法中剔除掉这份引用关系. 并作强制作GC处理:
1: protected override void OnRemovedFromJournal(JournalEntryRemovedEventArgs e)
2: {
3: this.DataContext = null;
4: GC.Collect();
5: GC.WaitForPendingFinalizers();
6: base.OnRemovedFromJournal(e);
7: }
d能够在OnRemovedFromJournal方法中解除View和ViewModel依赖关系.这样View就能安全的释放.这里指的一提的是OnRemovedFromJournal方法执行时间点.这个方法事实上是view被弹出栈顶时调用的方法的.也在OnNavigatedFrom方法以后执行该操做. 这样就足够吗? 事实上咱们UI上图片缓存的数据依然还占用着内存空间.也就是在离开页面以前咱们需简要清除掉图片缓存数据.
在上文Xaml文件咱们ListBox绑定的实体设置Image Source针对的只是一个图片Url地址 实体定义以下:
1: public class TripPictureInfo
2: {
3: public string CityName { get; set; }
4: public string StatusCode { get; set; }
5: public string TripPictureUrl { get; set; }
6: }
这时咱们若是想在离开页面时状况图片缓存数据须要改造这个实体 新添加一个 属性TirpPictureSource:
1: public class TripPictureInfo
2: {
3: public string CityName { get; set; }
4: public string StatusCode { get; set; }
5: public string TripPictureUrl { get; set; }
6:
7: private BitmapImage _tirpPictureSource = new BitmapImage() { CreateOptions = BitmapCreateOptions.BackgroundCreation
8: | BitmapCreateOptions.IgnoreImageCache
9: | BitmapCreateOptions.DelayCreation };
10: public BitmapImage TirpPictureSource
11: {
12: get
13: {
15: _tirpPictureSource.UriSource = new Uri(TripPictureUrl,UriKind.RelativeOrAbsolute);
16: return _tirpPictureSource;
17: }
18: }
19: }
并设置该BitmapImage的CreateOptions属性为BackgroundCreation、IgnoreImageCache 、DelayCreation .在UI上ListBox数据末班中修改Image Source绑定的实体属性为TirpPictureSource.
1: <!--ContentPanel - place additional content here-->
2: <Grid x:Name="ContentPanel" Grid.Row="1" Margin="24,0,12,0">
3: <ListBox x:Name="ControlMemory_LB" ItemsSource="{Binding TripPictureCol}" SelectionChanged="ControlMemory_LB_SelectionChanged">
4: <ListBox.ItemsPanel>
5: <ItemsPanelTemplate>
6: <tool:WrapPanel Orientation="Horizontal"></tool:WrapPanel>
7: </ItemsPanelTemplate>
8: </ListBox.ItemsPanel>
9: <ListBox.ItemTemplate>
10: <DataTemplate>
11: <StackPanel Margin="10,0,0,0">
12: <Image Source="{Binding TirpPictureSource}" Width="100" Height="100"></Image>
13: </StackPanel>
14: </DataTemplate>
15: </ListBox.ItemTemplate>
16: </ListBox>
17: </Grid>
当页面离开是在OnNavigatedFrom方法中清除全部的图片缓存 其实本质其实就是设置BitmapImage 的UrlSource为空:
1: private void ClearImageCache()
2: {
3: if (_tripPicViewModel.TripPictureCol.Count > 0)
4: {
5: foreach(TripPictureInfo queryInfo in _tripPicViewModel.TripPictureCol)
6: queryInfo.TirpPictureSource.UriSource=null;
7: }
8: }
当页面只有返回上一个页面才会执行该操做.同时并清空ViewModel中ObserverCollection<T>集合中数据 当页面离开添加当前内存使用状况的记录.查看当页面离开是页面缓存图片数据内存是否被回收 代码以下:
1: protected override void OnNavigatedFrom(NavigationEventArgs e)
2: {
3: LogRecordHelper.AddLogRecord("Clear Before:", DeviceStatus.ApplicationCurrentMemoryUsage.ToString() + " B");
4: if (e.NavigationMode == NavigationMode.Back)
5: {
6: ClearImageCache();
7: _tripPicViewModel.TripPictureCol.Clear();
8: }
9: LogRecordHelper.AddLogRecord("Clear After:", DeviceStatus.ApplicationCurrentMemoryUsage.ToString() + " B");
10: }
这样一来咱们在进行一样的操做经过内存使用日志来判断当前页面离开是否清空当前图片缓存,内存日志以下:
咱们能够看到Clear Before 和Clear After之间当前内存使用状况的对比,Clear Before 内存是66637824 [B] Clear After 内存使用是 15417344 [B].来计算当页面离开时清除图片缓存数据实际大小为:[(Clear After-Clear Before)/1024/1024]=[(66637824-15417344)/1024/1024]=48.84765625 M. 也便是在页面离开时清除图片占用缓存大小为48.8M.
能够看到真正影响每次页面离开后.内存占用主要问题是缓存图片资源内存得不到释放. 咱们只须要在页面退出时清空缓存图片资源便可达到内存释放.当疑惑的是发如今页面离开后析构函数依然没有执行.一般.NET Framework 垃圾回收器会隐式地管理对象的内存分配和释放。但当应用程序封装窗口、文件和网络链接这类非托管资源时,应使用析构函数释放这些资源。当对象符合析构时,垃圾回收器将运行对象的 Finalize 方法。虽然垃圾回收器能够跟踪封装非托管资源的对象的生存期,但它不了解具体如何清理这些资源。常见的非托管源有:ApplicationContext、Brush、Component、ComponentDesigner、Container、Context、Cursor、FileStream、Font、Icon、Image、Matrix、Object、OdbcDataReader、OleDBDataReader、Pen、Regex、Socket、StreamWriter、Timer、Tooltip 等.
若是你UI使用Brush相似这些非托管资源.会致使每次进入页面UI会自动重绘.增长了内存的开销.但目前咱们当前并无如上这些对象,为什么析构函数在退出后一段时间内依然没有执行?其实问题存在主要由于在Code behind代码中针对ListBox 添加了一个ControlMemory_LB_SelectionChanged 事件用来查看单张照片:
1: private void ControlMemory_LB_SelectionChanged(object sender, SelectionChangedEventArgs e)
2: {
3: if (e.AddedItems.Count > 0)
4: {
5: TripPictureInfo tripPicInfo = this.ControlMemory_LB.SelectedItem as TripPictureInfo;
6: if (tripPicInfo == null)
7: return;
8:
9: this.ControlMemory_LB.SelectedIndex = -1;
10: this.NavigationService.Navigate(new Uri("/SinglePictureView.xaml?Url="+tripPicInfo.TripPictureUrl,UriKind.RelativeOrAbsolute));
11: }
12: }
ListBox对象做为由于该事件存在订阅的引用关系. 同时ListBox做为Mainpage子对象. 从而致使Mainpage页面在离开得不到销毁.因此这里很是值得一提的是.在离开页面.若是页面对象订阅了后台事件.必定取消该事件的订阅.才能保证View可以退出时正常的销毁,添加一个方法来取消ListBox事件订阅:
1: private void ClearRegisterUIElementEvent()
2: {
3: this.ControlMemory_LB.SelectionChanged -= ControlMemory_LB_SelectionChanged;
4: }
同时在OnRemovedFromJournal方法调用清除事件订阅方法:
1: protected override void OnRemovedFromJournal(JournalEntryRemovedEventArgs e)
2: {
3: ClearRegisterUIElementEvent();
4: this.DataContext = null;
5: GC.Collect();
6: GC.WaitForPendingFinalizers();
7: base.OnRemovedFromJournal(e);
8: }
这样一来延续上面重复的操做.来看看内存使用和析构函数的执行状况:
能够看到当第一次进入页面离开是成功清除了图片的缓存.当再次进入MainPage时会当即执行析构函数.这样一来再次建立新的MainPage对象时内存中原来第一次建立MainPage对象View就会被当即的销毁.这样就能及时销毁原来View. 在测试过程当中.发现Page析构函数被调用,但内存并无下降. 说明析构函数只是gc回收View,不表明其内部申请的资源都释放了.因此针对图片缓存的数据进行单独的处理,固然若是你的UI比较复杂. 包含一些非托管资源.则须要你在析构函数中手动释放资源内存的占用.
若是使用IOC+MVVM开发模式.须要在OnRemovedFromJournal函数中添加以下代码:
1: Messenger.Default.Unregister<bool>(this, MessageToken.MessageListChanged);
Messenger.Default.Unregister<bool>(this, MessageToken.MessageListChanged);这个语句很重要,若是vm在init时在Messenger中注册了观察者,系统默认不会将这个vm关联的view销毁,因此咱们能够在这里对他进行销毁。固然这样的Messenger销毁咱们通常也能够直接写在vm里以保持生命周期的统一性.虽然MVVM模式可以富文本模式采用DataBinding方式提升咱们了开发效率.但同时也增长咱们处理windows phone 内存泄露的难度.同时要参考整个PhoneApplicationPage生命周期来合理处理内存释放.
源码下载:[https://github.com/chenkai/ReduceMemoryUseageDemo]
Contact Me: [@chenkaihome]
参考资料:
Clear image cache from Grid background
OutOfMemoryException occure on WriteableBitmap
How to pass BitmapImage reference to BackgroundWorker.DoWork