创建高性能移动 web 站点
如果你的网站3秒钟没有响应,人们就会失去兴趣了。为了满足响应快这个愿望,需要一个不同的方法在手机上进行分析,设计和测试。
这篇文章将会对Johan Johansson在2013年4月提出" 怎样让你的网站在手机上也很快"的这种理念进行扩展。我们将提出论证方法来确认人们在手机上与网站的交互方式和以前是不一样的,特别是设计也是基于此理念的。我们的目标不仅仅是提高网站性能,而且也要增加客户收入的。
我们将关注手机两个特性,这两个特性短期内也不会有变化:电池容量小,屏幕小。
电量小
手机的通讯要用无线电,但手机的电池很小,所以要非常谨慎的用电以防止把电用光。这样,如果无线电不用的时候就会迅速关掉,这样就增加了网页出现的时间。2G和3G无线技术需要2秒钟来建立HTTP链接。如果我接受“用户会在3秒后失去兴趣”的观点的话,那我们的网站只有1秒来响应了。想想这“黄金般的一秒”吧。
最大化利用这“黄金一秒”
小屏幕
在物理世界中,广告牌和杂志的内容都是根据媒介的大小和观看距离来定制的。在数字世界中,一个典型的中档智能手机拥有几乎6平方英寸大小的屏幕。15英寸的MacBook Pro电脑屏幕拥有超过100平方英寸的大小。这样,我们不仅可以通过减少发送到手机端的内容优化网站性能,而且可以优化业务流程来提高网站所有者的投资回报。
本文的代码示例是由.NET提供。我已经在companion article文章中展示了用PHP, Java, C 和Python达到同样的效果。我在这篇文章的结尾会解释为什么选用.NET。
最大化利用 “黄金一秒”
网站设计者和开发者们常常想当然的认为用户应该用高带宽Wi-Fi和固网来连接。响应式网站设计(RWD)强制在不同设备上(不论其性能好坏)显示相同的内容、导航和业务流程,限制了创新。
确保我们能够容易的进行性能测量,进行用户行为监控的基于不同设备特性的解决方案以及低带宽设备网页访问优化都需要最大限度的利用这“黄金一秒”。
模拟现实网络
现实移动带宽模拟测试是一个必不可少的移动Web性能测试。很多100美元以下的廉价无线路由都提供了限制带宽功能,测试仅仅只涉及到了局域网内的客户端的上行和下行带宽限制功能。如果路由不支持这个功能话,那么试试用 DD-WRT(DD-WRT是一个开源升级固件,可以替代目前主流路由的默认操作系统)来限制带宽。
我用DD-WRT升级了Linksys E3000路由。路由升级的过程非常简单,DD-WRT官网上提供了完整的说明。
安装好DD-WRT后去到QoS菜单,启用带宽限制。设置上行和下行带宽的值,我习惯将下行带宽设置为256kbps,上行带宽设置为28kbps来模拟移动网络的平均带宽。
在“Quality of Service”选项中限定带宽
现在无论是以Wi-Fi或网线连接到路由器的设备的带宽都被人为的限制了。我们可以监视带宽实际的使用情况。
用DD-WRT监视带宽使用
虽然这种测试方法并没有包括随机的掉线、可变带宽条件和由信号强弱引起的延迟等情况,但是比起你在快速、低延迟带宽下做的其他测试效果要好。在网站开发初期,这是一个在开发过程中对Web性能进行非正式测试的简单的方法,能够确保你在正式测试过程中不出现任何讨厌的问题。
你不能管理所不能测量的事
管理顾问 Peter Drucker 曾经说过一句名言:“如果你无法测量某件事,你就无法管理它。”
平均屏幕尺寸随时间的推移的增长情况
持续根据设备特性(比如无线支持或屏幕大小)对用户查看的内容进行监控,或多或少将会有助于你识别手机上流行的内容和服务。也许你将看不到任何区别,但是除非你测量过,否则无法确定。
例子:“现在我要吃”站点
一个全球化的快餐特许经营店需要创建针对移动终端的大屏幕互联网站点的优化版本。在创建第一个针对移动终端优化的互联网站点之前,执行分析以确定大屏幕互联网站点的哪些项是小屏幕设备的用户可以访问的。主菜单、特卖品和分店查找是最受欢迎的,因此创建针对移动设备优化的互联网站点就集中在这些方面。
工作不能停留在此。接着的分析显示出分店查找是最受欢迎的。因此再次修改移动设备的主页以关注分店查找。继续的监视显示出多少访问者选择其他选项,然后依此不断改善地这个互联网站点,以确保用最简单可行的方法实现最受欢迎的栏目。
好的日志
Google Analytics 提供了一些关于设备模型的信息,但它缺乏我们需要基于屏幕尺寸和输入方法作出明智决定的细节。幸运的是,一个全面的设备检测库(DDR)可以将此信息添加到现有日志文件中。下面的代码片段可以添加到 .NET网站中,参考51degrees.mobi(可通过 NuGet ) 获取屏幕的物理尺寸和输出到一个简单的CSV文件中。
1
2
3
4
5
6
7
8
9
10
11
12
|
// Write a log file containing the current time, and the screen // size of the requesting device in inches. File.AppendAllText( Path.Combine( AppDomain.CurrentDomain.BaseDirectory, String.Format( "App_Data\\Simple_Log_{0:yyyyMMdd}.csv" , DateTime.UtcNow)), String.Format( "{0:s},{1},{2},{3}\r\n" , DateTime.UtcNow, Request.Path, Request.Browser[ "ScreenInchesWidth" ], Request.Browser[ "ScreenInchesHeight" ])); |
第一行是处理请求的日期和时间。第二行是请求的页面。最后两行是设备屏幕的宽度和高度。抓取足够多的数据和平均屏幕的尺寸大小绘制出了下面的图表:
比较设备屏幕的平均大小超过20个月
分析可以缩小到具体的页面。有关设备的特性,操作系统和浏览器也可以被添加到列中。
类似的代码可以使用PHP、Java、Python和其他环境语言。
已有的日志文件
有时,已有的Web页面不能按照上面的方式修改。在这样的情况下,DDR可以用来执行含有用户代理的日志日文的离线分析了。下面的.NET代码是一个实用的命令行程序,它解析空格分隔的日志文件,然后计算出日志所表示的请求以平方英尺为单位的平均屏幕尺寸。第一个参数是日志文件的位置,第二个参数是日志文件里用户代理所在列的索引。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
|
using System; using FiftyOne.Foundation.Mobile.Detection.Binary; using System.IO; namespace ConsoleApplication { class Program { static void Main( string [] args) { // The number of devices read from the log file. int count = 0; // The column in the input file the user agent is held in. int column = int .Parse(args[1]); // Screen dimension variables. double total = 0, width, height, squareInches; // Create a provider to determine the device capabilities. var provider = Reader.Create( "51Degrees.mobi.dat" ); // Read each line of the log file provided in argument 0. // Assume the value at column 8 is the UserAgent string. using (var reader = File.OpenText(args[0])) { while (reader.EndOfStream == false ) { var values = reader.ReadLine().Split( new [] { ' ' }); if (values.Length >= column) { // Get the device information based on the UserAgent. var device = provider.GetDeviceInfo( values[column - 1].Replace( "+" , " " )); if (device != null ) { // Determine the screen dimensions in inches. double .TryParse( device.GetFirstPropertyValue( "ScreenInchesWidth" ), out width); double .TryParse( device.GetFirstPropertyValue( "ScreenInchesHeight" ), out height); squareInches = width * height; // If valid values are available (not a desktop/laptop) // then add the values to the results. if (squareInches > 0) { total += squareInches; count++; } } } } } Console.WriteLine( "Average screen size '{0:#.00}' square inches from '{1}' devices" , total / count, count); Console.ReadKey(); } } } |
分析日志文件很不准确,因为除了用户代理外的其他HTTP头都影响着检测结果。对Opera Mini和Opera 移动浏览器来说尤其是这样的。在这两个浏览器里,第二个HTTP头,也就是名字为Device-Stock-UA的头常常用来提供标准用户代理里没有的有关物理硬件的信息。
为什么监控?
监控使得我们能够将不受欢迎的内容从主页中删除,以此提升更重要的内容或相关的内容的性能。删除的内容应该仍可以通过二级页面访问到——只是不放在首页,不然的话它们会消耗宝贵的带宽并降低性能体验。
那么,我们怎样来创建一个独立的性能优化的移动网站呢?
分而治之
我能理解为什么RWD(响应web设计)从用户界面设计的角度来说很有意义。对于6平方英寸屏幕和10平方英寸屏幕,以及仅仅是需要进行改动的布局来说,在内容,导航以及业务流程需求方面可以完全一致,这实在是太棒了。
平均设备屏幕尺寸。
但是,在上述条件不为真或者对性能要求严格的时候有一个独立的移动网站 具有特别的意义。
独立的移动网站常常表现出一种不良的用户体验。通过给网站惩罚赋以较低的搜索引擎等级,Google现在投射出一缕曙光 到这些普通的问题上。问题包括了将每个桌面页发送到单独的移动主页,重定向到应用下载页,阻止用户访问大屏的网站,对所有带特定操作系统的设备以相同的方式处理。
这些糟糕的实现让人对这些概念有一个坏的印象。这里是一些简单又正确的做法。
下面的 .NETweb.config片段将把来自智能手机的第一个请求,重定向到网站上“Smartphone”部分指定的等价页面。 重要的是,查询字符串与页面名字在重定向的过程中一直保持着。
1
2
3
4
5
6
7
8
9
10
11
12
|
< redirect firstRequestOnly = "true" mobileHomePageUrl = "~/Mobile/Default.aspx" timeout = "20" devicesFile = "~/App_Data/Devices.dat" mobilePagesRegex = "/(Mobile|Smartphone)/" > < locations > <!--Send smartphones to an equivalent version of the original page, preserving the page name and query string.--> < location name = "smartphone" url = "~/Smartphone/{0}" matchExpression = "(?<=^\w+://.+/).+" > < add property = "IsSmartphone" matchExpression = "true" /> </ location > </ locations > </ redirect > |
在大多数情形,当重定向到替代页面时,如果愿意的话用户应当可以返回原始的页面;或许他们对网站的大屏幕版本更熟悉呢。firstRequestOnly属性保证了只有来自设备的第一次请求才被重定向。devicesFile属性是用来对不支持cookies的设备进行跟踪。timeout属性控制了在多长时间内该设备被记忆(为了重定向的目的)。
重定向系统还必须知道哪个页面是针对哪种设备设计的。mobilePagesRegex属性被应用到请求URLs。如果存在匹配,页面将不适用重定向。这阻止了无穷重定向的情况。
locations元素允许配置定义不同的地址,以及相关的规则。这个例子将Smartphone目录插入到原始的URL。查询字符串和其他的URL信息在重定向过程中一直保持。所有影响到请求上下文的信息必须被传送,以便用户获得他们期望的内容。
这个简单的方法使得一个搜索引擎友好的,兼容Google的,移动手机优化的网站,在传送的过程中有良好的用户体验和优异的性能。这个过程的基础是DDR,它快速的,一致的,精确的提供了设备的信息。对于改变了移动手机浏览器设置到桌面模式的用户,重定向将不会发生。
警惕云
云服务是给网站迅速增加特性的流行方法。但是它们跨越Internet的请求对性能带来损耗。如果忽略处理时间,我们观察到由Amazon Web Service提供的云服务的数据传输有平均200毫秒的延时。
200毫秒是一个黄金秒的20%。因此,仔细考虑一下你使用的云服务在哪里,确保它们是异步调用的,以便在等待响应的过程中其他处理能继续下去。它们应该避免关键路径上的活动,例如判别请求设备的信息。
压缩内容
紧随视频、图像之后,CSS和HTML占据了大量的Web流量。我们需要优化所有这一切的方法。视频本身就是就可以写一篇文章,所以要等以后再说。
图像
流行的解决方案是同一张图像提供三个版本,而且当浏览器渲染页面的时候,使用JavaScript或者CSS选择最适合请求设备的那张图象。这是一个好的开始,不过管理同一图像的不同版本却是很痛苦的;图像从来都不是完美优化的,而且这种方法给有限CPU和电池电量的移动设备增加了进行图像大小调整的负担。
有一个更好的处理方法是使用图像优化器。可以通过Viusal Studio的集成开发环境把52Degrees.mobi的图像优化器增加到ASP.NET站点。下面的配置将自动增加到web.config里。
1
2
3
|
< handlers > < add name = "Image" verb = "GET" path = "P.axd" type = "FiftyOne.Framework.Image.ImageHandler, FiftyOne.Framework" /> </ handlers > |
上面的处理器告诉互联网信息服务(IIS)图像处理器应该处理资源P.axd的任何GET请求。
一旦web.config里启用这项,下面的ASP.NET代码将使用图像优化器从三种可能的资源-也就是分别为240,480和640像素宽的图像中确定一个图像。
1
2
3
4
5
|
< mob:Image runat = "server" ID = "ImageBanner" CalculateSizeMode = "ClientWidth" Style = "clear: both; width: 100%" > < mob:AltImage ImageUrl = "~/Images/Landscape240.png" /> < mob:AltImage ImageUrl = "~/Images/Landscape480.png" /> < mob:AltImage ImageUrl = "~/Images/Landscape640.png" /> </ mob:Image > |
当初始化显示图像的时候,服务器将发送一个白色的1x1像素的GIF显示在图像所在位置。下面就是生成的HTML:
1
|
< img id = "B" src = "P.axd?i=E.gif&i=1" /> |
一旦页面装载完成,JavaScript用来算出最终显示图像所需要的真正的尺寸,然后向服务器请求一个大小明确的图像。经过JavaScript处理后,上面的HTML转换为:
1
|
< img id = "B" src = "P.axd?i=1&w=500" /> |
web.config中引用的图像处理器把i查询字符串关联到图像源,因此服务器上最适合的图像将用作调整的最初图像。w查询字符串参数指定了所请求图像的宽度。因此不需要提供多个图像;一个单独的图像几乎就可以了。 这种方法易于实现,而且结果是大小明确的图像。这样做不但减少了带宽,而且还减少了移动电话CPU的运行周期和电量。
HTML
整个牛津大辞典包括171476个单词。如果一个电脑用独有的二进制数字代表一个单词,而不是一个字母表的字母,大概需要用18比特(向上舍入大概3字节)。这表明压缩算法是十分有效的。
然而,HTML不是很有效,因为它充满了用字符代表的元素、IDs,类,styles和Javascript,没有考虑到是否是人可读的。压缩可以减少这些,但还是要有开销的。这就是为什么流行的库都有人们读不懂的压缩版本。
在服务器发送给浏览器之前,一些与标记相关的单词还可以最小化,而且不会丢失任何这些单词的意义。看一个上面所示的图像的例子,ASP.NET里标准的HTML的图像元素的ID属性是ImageBanner。
1
|
< mob:Image runat = "server" ID = "ImageBanner" CalculateSizeMode = "ClientWidth" Style = "clear: both; width: 100%" > |
然而发送给浏览器的代码只使用B。对一个单独的元素来说。这样的性能改进是微不足道的,然而,对一个含有数百个元素的复杂页面来说,传送这样的页面会更快,而且浏览器将能更快地处理这一切。
Includes
图像例子生成的HTML看起来比较奇怪:
1
|
< img id = "B" src = "P.axd?i=1&w=500" /> |
这段ASP.NET代码没有样式而且也没有为img元素设置CLASS属性。那么它的样式是怎么来的呢?
服务器端最小化过程中将会确定样式信息,并且为网页创建一个CSS文件,从而减小了HTML的大小。当HTML变化时,样式已经被缓存在浏览器中,不需要重新下载。CSS片段看起来就像这样:
1
|
#B{ clear : both ; width : 100% ;} |
如果许多元素共享同一个样式,那么就要给这个CSS增加ID属性,这样它们就可以共享相同的信息。
还可以通过使用服务器端样式元素实现在多个元素和页面上共享样式信息。下面的代码扩展了前面图像例子,以此来说明共享样式元素。
1
2
3
4
5
|
< mob:Style runat = "server" ID = "StyleBanner" > < mob:Filter Style = "clear: both; width: 100%" /> </ mob:Style > < mob:Image runat = "server" ID = "ImageBanner" CalculateSizeMode = "ClientWidth" StyleID = "StyleBanner" > |
根据设备的能力,还可以对这个元素进行进一步扩展,使得可以应用其他样式,并且可在多个页面上优化样式表。 这种技术总是确保只传输必须的CSS,因此在对同一个页面的后续请求方面提高了性能,尤其是对HTML内容只有稍稍不同的页面。
为什么选择.NET?
上面所示的图像优化和动态最小化HTML和CSS内容的技术和代码例子取决于页面渲染之后且服务器传送给浏览器之前更改的内容。这样的预处理技术在诸如ASP.NET的Web表格这样的结构里实现起来相当容易。
然而在基于脚本的比如PHP这样的架构里实现它们就非常复杂。正是由于这个原因,并且为了保持一致性,这篇文章中的例子都是在.NET上的。我们已经能够把这种技术应用到其他语言上,例子代码可在朋友博客里看到。
例子
公共卫生基金会的企业实现了本文中提到的技术,并在第一周成功提高了23%的性能。
其他比较注重性能的网站包括 24.com(媒体),ServiceTick(分析),LettingWeb(财产),AdSupply(广告)和Kitsap Credit Union(金融)都使用了本文提到的部分或是全部的技术来优化他们的移动站点。
总结
为了真正地优化性能,我们需要考虑网站所有人的投资回报。监测设备特征的不同是根本起始点。
然后我们才能部署诸如利用分散的移动网站来分离或改变内容焦点的解决方案。我们还能通过收紧缩小图像和HTML,移除jQuery,问询何时单独使用响应式网站设计(RWD),以及其它技术等使性能达到最高。当然,现有的技术也是至关重要的,例如配置缓存路径和压缩内容。
微调我们的开发环境,来模拟真实世界的情况,这样也可以在整个开发过程中获得对性能的更好的理解。
现在就优化
为了能让你更多地考虑性能,我已经设定好了一个寻找世界上最重型的网站的竞赛。寻找一个网页,它在移动电话上运行得很糟糕,并将其提交给竞赛。我们会比较页面的分量,以及它是否是最重型的,你将因此而赢得1000美元。与此同时,实现本文以及其他Smashing Magazine中的牛文中提到的技术,以确保你的网站在被我们掂量其性能的时候不会出现在榜单之首!
从未有过比这更能提高你的网站性能的机会了。