您的位置:首页 > 其它

wp7开发系列:性能优化实例分析

2013-09-15 22:22 549 查看
前言

功能开发完善的应用程序,如果性能和体验太差,只能算是一款糟糕的软件。只有流畅的体验和一流的性能才能确保一款优秀的App能够走得更远。最理想的情况是在开发过程中就能够保证App的高质量,一般情况下还是需要后期做有针对性的优化的。

抛开我们无法改变的如网络速度,硬件环境,系统SDK等因素,我们可以优化的地方实在太多,而且前者往往并不是性能瓶颈,绝大部分导致性能糟糕的原因都是我们自己的开发方式造成的,都是可以解决的或者绕开的,本文并没有系统性的讲解的性能优化的一些方法,只是以实例的形式介绍了典型了几种性能优化的方法。

一、Cases

★动画启动延迟/阻塞优化

★场景切换速度优化

★界面流畅度优化

★UI阻塞优化

二、动画启动延迟/阻塞优化

2.1、LongListSelector性能问题

在使用toolkit里面的LongListSelector控件时,我们发现当打开和关闭索引界面时,其中的延迟令人抓狂,至少能感觉出来整个界面停顿了0.5s以上。如果我们再给27个索引字母加上翻转的动画(类似系统首页的动画效果),启动时甚至连动画都看不到,这种体验实在太差了。(可以体验下Lumia800手机上的微信和系统索引的速度对比)





图2-1-1 LongListSelector控件 图2-1-2 索引界面

2.2、优化方法

为什么和系统首页使用的控件性能差别那么大呢,这只有分析源码了,幸运的是toolkit的源码可以自由修改。

分析源码,首先找到点击List触发的OnSelectionChanged事件,当确认点击的是索引字母时,会调用DisplayGroupView方法,如果设置了GroupItemTemplate,就会调用OpenPopup方法,可以看到OpenPopup里面的实现如下。

Code

private void OpenPopup()
{
SaveSystemState(false, false);
BuildPopup();
AttachToPageEvents();
_groupSelectorPopup.IsOpen = true;

// This has to happen eventually anyway, and this forces the ItemsControl
//to  expand it's template, populate it's items etc.
UpdateLayout();
}


SaveSystemState用于保存系统的一些状态以便还原(AppBar的Visible属性),因为打开索引界面时会隐藏掉Appbar。

AttachToPageEvents用于监听Page的事件,BackKeyPressed和OrientationChanged。

关键就是BuildPopup方法,在这个方法里创建了整个的索引界面,不得不说这是一件超级耗时的事情,导致界面的停顿0.5s左右就一点也不奇怪了。与之对应的就是ClosePopup方法,这里面对所有对象都进行了析构,所以每次打开和关闭索引界面都是动态创建的,速度当然是超级慢了。优化方法很简单,将BuildPopup放在构造方法里,也就是预加载的方式,打开和关闭索引界面只需要将Popup打开和关闭即可,完全省略掉构造的时间,这样可以确保流畅的动画效果和无停滞的UI。

至此我们已经提升了至少80%的性能,但是仍然无法像系统首页那样完美的流畅,剩下的20%可能的原因是什么呢?请先看下一章。

三、场景切换速度优化

3.1、详情/大图切换性能问题

在开发比较复杂的单个页面时,经常需要在多种状态之间切换,这时就可能遇到切换速度较慢的性能问题,一般有2种情况。

1、类似于上一节所遇到的问题,新界面是动态创建的。

2、预加载的,一般使用VisualStateGroup管理,新界面需要显示时设置Visibility属性为Visible,不需要显示时设置为Collapsed。

详情页打开和关闭大图模式就属于第二种情况。





图3-1-1 宝贝详情 图3-1-2 大图模式(同一个Page)

3.2、优化方法

这里我们提供了第二种情况的优化方法,以详情页面打开和隐藏大图模式为例。

Code

<Grid x:Name="LayoutRoot" >
<tbcontrols:TBPivot
Visibility="{Binding IsShowLargeImage,Converter={StaticResource falseToVisibilityConverter}}">
<local:LargeImageModeBrowseControl
Visibility="{Binding IsShowLargeImage,Converter={StaticResource trueToVisibilityConverter}}">
</local:LargeImageModeBrowseControl>
</Grid>


详情页的布局大致是这样的,第一层内容是一个Pivot,大图模式在第二层,当IsShowLargeimage为ture时显示大图模式。问题是,当在2种状态之间来回切换时,中间有一个很明显的UI阻塞现象,约有0.3s。时间消耗主要在Recreate上,因为当元素的Visibility属性设置为Collapsed时将会被释放掉,重新设置为Visible则会重新创建和布局。所以我们考虑用缓存的方法消除这部分时间。

Code

<Grid x:Name="LayoutRoot">
<tbcontrols:TBPivot
Opacity="{Binding IsShowLargeImage,Converter={StaticResource    falseToOpacityConverter}}"
IsHitTestVisible="{Binding    IsShowLargeImage},Converter={StaticResource tbNegativeConverter}">

<local:LargeImageModeBrowseControl
IsHitTestVisible="{Binding IsShowLargeImage}"
CacheMode="BitmapCache"
Opacity="{Binding IsShowLargeImage,Converter={StaticResource    trueToOpacityConverter}}">
</local:LargeImageModeBrowseControl>


修改后的布局如上所示,当需要显示大图模式时,将Pivot透明度设为0(同时禁止用户输入事件),并将大图模式控件透明度设置为1,反之类似。由于大图模式控件使用了BitmapCache,完全没有构造和布局的时间消耗,呈现速度就如同展现Bitmap图像一样迅速。

上一章提到的剩余20%性能优化即与此有关,Popup的打开和关闭应该是和设置Visibility属性类似,如果对LonglistSelector做进一步优化相信能够达到系统级别的流畅度。

四、界面流畅度优化

3.1、Panorama左右滑动性能问题

这一类的优化的原因很大一部分就是靠感觉了,尽管动画有较高的帧率,尽管使用了各种缓存,使用了GPU动画,然而界面左右滑动切换就是有一种不流畅的感觉,总会“钝”那么一下。

在首页使用的panorama控件中,其中有2页内容是以列表的形式展示的,此外其他页面还有一些按钮。问题是当我们在左右滑动时,当手指刚刚触碰到按钮或者列表项,就会触发Tilt动画(使用toolkit的TiltEffect),这会导致整个大内容区块BitmapCache缓存的更新,滑动切换就没有那么流畅了,总会感觉“钝”了一下,其实就是gpu超负荷跳帧了。





图3-1-1 Panorama其中=中一页图3-1-2 Panorama的另一页

3.2、优化方法

优化的方法也比较简单,当检测到滑动手势时就不要触发Tilt动画,但是滑动手势的检测需要一小段时间间隔,而且sdk也没用提供任何相关接口和事件,我们得自己实现了。首先得实现我们自己的点击效果,提供2个方法beginTouchDownAnimation和beginTouchUpAnimation,分别是按下和恢复的动画效果。正常情况下在ManipulationStarted事件发生时会执行动画,但是我们加了一个60ms的延时,如果在30ms之内发现有ManipulationDelta事件发生(滑动)或者ManipulationCompleted事件发生(手指离开屏幕),则取消执行动画。

Code

static void effectElement_ManipulationStarted(object sender, ManipulationStartedEventArgs e)
{
FrameworkElement element = sender as FrameworkElement;
e.ManipulationContainer = element;
Point origin = e.ManipulationOrigin;
element.CaptureMouse();

isSingleTap = true;
isTapEnded = false;
if (myAction != null)
{
TBDelayExecuter.CancelPerform(myAction);
}

myAction = new Action(() =>
{
if (isSingleTap && !isTapEnded)
{
beginTouchDownAnimation(element, origin);
}
});
TBDelayExecuter.Perform(myAction, 30);
}

static void effectElement_ManipulationDelta(object sender, ManipulationDeltaEventArgs e)
{
isSingleTap = false;
}

static void effectElement_ManipulationCompleted(object sender, ManipulationCompletedEventArgs e)
{
if (!isTapEnded)
{
FrameworkElement element = sender as FrameworkElement;
element.ReleaseMouseCapture();
beginTouchUpAnimation(element);
isTapEnded = true;
}
}


这样在左右滑动屏幕时没有Tilt动画发生,变得流畅起来,在配合BitmapCache缓存,滑动内容复杂的panorama就相当于滑动内容仅为Bitmap的页面一样流畅。

五、UI阻塞优化

3.1、图片更新性能问题



图5-1-1 瀑布流图片墙

在一小段时间内,UI线程执行任务太多会导致UI阻塞现象,这时需要进行优化。比如首页的瀑布流分页加载时,每一页有16张图片需要下载,虽然我们使用了后台线程去下载图片,但是返回的情况是不确定的。最常见的情况,使用wifi时有可能16张图片在零点几秒的时间里面全部下载成功了,这是全部通知到UI,必然造成UI阻塞,表现为短时间里(也是零点几秒)几乎不响应拖动事件了。

3.2、优化方法

我们的采用的优化策略是,分散UI线程工作,控制图片下载后集中通知UI刷新的时机。

使用TBImage控件绑定一个图片的Url地址,后台实际使用我们的QueueDownloadImageProxy代理图片的下载。这个类的功能就是控制图片下载成功后依次延时通知到UI,保持一个最近一张图片通知到UI的时间,后续图片以100ms的间隔依次通知到UI。

Code

static Object lockObj= new Object();

static void QueueNotify(TBQueueDownloadImageProxy proxy, TBImage tbImage, object context)
{
lock (lockObj)
{
if (tbImage == null)
return;
DateTime now = DateTime.Now;
TimeSpan timeSpan;
timeSpan = now.Subtract(PreNotifyImageTime);
if (timeSpan.Milliseconds < minNotifyImageInterval)
{
PreNotifyImageTime = now +   TimeSpan.FromMilliseconds(minNotifyImageInterval - timeSpan.Milliseconds);
Thread.Sleep(minNotifyImageInterval - timeSpan.Milliseconds);
}
else
{
PreNotifyImageTime = now;
}
}

Deployment.Current.Dispatcher.BeginInvoke(delegate()
{
if (proxy.DownloadImageSuccessed != null)
{
proxy.DownloadImageSuccessed(proxy, new TBQueueDownloadImageProxyEventArgs(tbImage.GetImage(), context));
}
});
}


使用此方法可以使图片加载以随机并且依次的顺序显示出来,不影响浏览体验,再配合一个图片淡出的动画效果就很cool了。

Xaml

<tbcontrols:TBImage
Width="130"
Background="{StaticResource imageBackgroundColor}"
Stretch="Fill"
CacheType="PERSIST_FILE"
ImageUrl="{Binding ImageUrl}">
<i:Interaction.Behaviors>              <tbBehavior:TBImageFadeInOnImageDownloadedBehavior
DurationInMillisecond="{StaticResource imageFadeInDurationInMillisecond}"/>
</i:Interaction.Behaviors>
</tbcontrols:TBImage>


六、优化策略总结

不难发现,我们采用性能优化策略有如下几种,分别是前面每一节用到的:

1、预加载

2、使用缓存

3、避免缓存的更新,避免重绘的发生

4、分散UI线程工作

七、结束语

在wp7手机优秀而强大的系统硬件配置下,只要在开发过程中,适当注意和使用合理的策略进行开发或者优化,保证流畅顺爽的客户端应用程序体验是不是很简单轻松?

2012.6
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: