創(chuàng)建高性能移動Web站點(diǎn)
如果你的網(wǎng)站3秒鐘沒有響應(yīng),人們就會失去興趣了。為了滿足響應(yīng)快這個愿望,需要一個不同的方法在手機(jī)上進(jìn)行分析,設(shè)計(jì)和測試。
這篇文章將會對Johan Johansson在2013年4月提出" 怎樣讓你的網(wǎng)站在手機(jī)上也很快"的這種理念進(jìn)行擴(kuò)展。我們將提出論證方法來確認(rèn)人們在手機(jī)上與網(wǎng)站的交互方式和以前是不一樣的,特別是設(shè)計(jì)也是基于此理念的。我們的目標(biāo)不僅僅是提高網(wǎng)站性能,而且也要增加客戶收入的。
我們將關(guān)注手機(jī)兩個特性,這兩個特性短期內(nèi)也不會有變化:電池容量小,屏幕小。
電量小
手機(jī)的通訊要用無線電,但手機(jī)的電池很小,所以要非常謹(jǐn)慎的用電以防止把電用光。這樣,如果無線電不用的時候就會迅速關(guān)掉,這樣就增加了網(wǎng)頁出現(xiàn)的時間。 2G和3G無線技術(shù)需要2秒鐘來建立HTTP鏈接。如果我接受“用戶會在3秒后失去興趣”的觀點(diǎn)的話,那我們的網(wǎng)站只有1秒來響應(yīng)了。想想這“黃金般的一秒”吧。
最大化利用這“黃金一秒”
小屏幕
在物理世界中,廣告牌和雜志的內(nèi)容都是根據(jù)媒介的大小和觀看距離來定制的。在數(shù)字世界中,一個典型的中檔智能手機(jī)擁有幾乎6平方英寸大小的屏幕。15英寸的MacBook Pro電腦屏幕擁有超過100平方英寸的大小。這樣,我們不僅可以通過減少發(fā)送到手機(jī)端的內(nèi)容優(yōu)化網(wǎng)站性能,而且可以優(yōu)化業(yè)務(wù)流程來提高網(wǎng)站所有者的投資回報(bào)。
最大化利用 “黃金一秒”
網(wǎng)站設(shè)計(jì)者和開發(fā)者們常常想當(dāng)然的認(rèn)為用戶應(yīng)該用高帶寬Wi-Fi和固網(wǎng)來連接。響應(yīng)式網(wǎng)站設(shè)計(jì)(RWD)強(qiáng)制在不同設(shè)備上(不論其性能好壞)顯示相同的內(nèi)容、導(dǎo)航和業(yè)務(wù)流程,限制了創(chuàng)新。
確保我們能夠容易的進(jìn)行性能測量,進(jìn)行用戶行為監(jiān)控的基于不同設(shè)備特性的解決方案以及低帶寬設(shè)備網(wǎng)頁訪問優(yōu)化都需要最大限度的利用這“黃金一秒”。
模擬現(xiàn)實(shí)網(wǎng)絡(luò)
現(xiàn)實(shí)移動帶寬模擬測試是一個必不可少的移動Web性能測試。很多100美元以下的廉價無線路由都提供了限制帶寬功能,測試僅僅只涉及到了局域網(wǎng)內(nèi)的客戶端的上行和下行帶寬限制功能。如果路由不支持這個功能話,那么試試用 DD-WRT(DD-WRT是一個開源升級固件,可以替代目前主流路由的默認(rèn)操作系統(tǒng))來限制帶寬。
我用DD-WRT升級了Linksys E3000路由。路由升級的過程非常簡單,DD-WRT官網(wǎng)上提供了完整的說明。
安裝好DD-WRT后去到QoS菜單,啟用帶寬限制。設(shè)置上行和下行帶寬的值,我習(xí)慣將下行帶寬設(shè)置為256kbps,上行帶寬設(shè)置為28kbps來模擬移動網(wǎng)絡(luò)的平均帶寬。
在“Quality of Service”選項(xiàng)中限定帶寬
現(xiàn)在無論是以Wi-Fi或網(wǎng)線連接到路由器的設(shè)備的帶寬都被人為的限制了。我們可以監(jiān)視帶寬實(shí)際的使用情況。
雖然這種測試方法并沒有包括隨機(jī)的掉線、可變帶寬條件和由信號強(qiáng)弱引起的延遲等情況,但是比起你在快速、低延遲帶寬下做的其他測試效果要好。在網(wǎng)站開發(fā)初期,這是一個在開發(fā)過程中對Web性能進(jìn)行非正式測試的簡單的方法,能夠確保你在正式測試過程中不出現(xiàn)任何討厭的問題。
#p#
你不能管理所不能測量的事
管理顧問 Peter Drucker 曾經(jīng)說過一句名言:“如果你無法測量某件事,你就無法管理它。”
持續(xù)根據(jù)設(shè)備特性(比如無線支持或屏幕大?。τ脩舨榭吹膬?nèi)容進(jìn)行監(jiān)控,或多或少將會有助于你識別手機(jī)上流行的內(nèi)容和服務(wù)。也許你將看不到任何區(qū)別,但是除非你測量過,否則無法確定。
例子:“現(xiàn)在我要吃”站點(diǎn)
一個全球化的快餐特許經(jīng)營店需要創(chuàng)建針對移動終端的大屏幕互聯(lián)網(wǎng)站點(diǎn)的優(yōu)化版本。在創(chuàng)建第一個針對移動終端優(yōu)化的互聯(lián)網(wǎng)站點(diǎn)之前,執(zhí)行分析以確定大屏幕互聯(lián)網(wǎng)站點(diǎn)的哪些項(xiàng)是小屏幕設(shè)備的用戶可以訪問的。主菜單、特賣品和分店查找是最受歡迎的,因此創(chuàng)建針對移動設(shè)備優(yōu)化的互聯(lián)網(wǎng)站點(diǎn)就集中在這些方面。
工作不能停留在此。接著的分析顯示出分店查找是最受歡迎的。因此再次修改移動設(shè)備的主頁以關(guān)注分店查找。繼續(xù)的監(jiān)視顯示出多少訪問者選擇其他選項(xiàng),然后依此不斷改善地這個互聯(lián)網(wǎng)站點(diǎn),以確保用最簡單可行的方法實(shí)現(xiàn)最受歡迎的欄目。
好的日志
Google Analytics 提供了一些關(guān)于設(shè)備模型的信息,但它缺乏我們需要基于屏幕尺寸和輸入方法作出明智決定的細(xì)節(jié)。幸運(yùn)的是,一個全面的設(shè)備檢測庫(DDR)可以將此信息添加到現(xiàn)有日志文件中。下面的代碼片段可以添加到 .NET網(wǎng)站中,參考51degrees.mobi(可通過 NuGet ) 獲取屏幕的物理尺寸和輸出到一個簡單的CSV文件中。
- // 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"]));
第一行是處理請求的日期和時間。第二行是請求的頁面。最后兩行是設(shè)備屏幕的寬度和高度。抓取足夠多的數(shù)據(jù)和平均屏幕的尺寸大小繪制出了下面的圖表:
比較設(shè)備屏幕的平均大小超過20個月
分析可以縮小到具體的頁面。有關(guān)設(shè)備的特性,操作系統(tǒng)和瀏覽器也可以被添加到列中。
類似的代碼可以使用PHP、Java、Python和其他環(huán)境語言。
已有的日志文件
有時,已有的Web頁面不能按照上面的方式修改。在這樣的情況下,DDR可以用來執(zhí)行含有用戶代理的日志日文的離線分析了。下面的.NET代碼是一個實(shí)用的命令行程序,它解析空格分隔的日志文件,然后計(jì)算出日志所表示的請求以平方英尺為單位的平均屏幕尺寸。第一個參數(shù)是日志文件的位置,第二個參數(shù)是日志文件里用戶代理所在列的索引。
- 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();
- }
- }
- }
分析日志文件很不準(zhǔn)確,因?yàn)槌擞脩舸硗獾钠渌鸋TTP頭都影響著檢測結(jié)果。對Opera Mini和Opera 移動瀏覽器來說尤其是這樣的。在這兩個瀏覽器里,第二個HTTP頭,也就是名字為Device-Stock-UA的頭常常用來提供標(biāo)準(zhǔn)用戶代理里沒有的有關(guān)物理硬件的信息。
#p#
為什么監(jiān)控?
監(jiān)控使得我們能夠?qū)⒉皇軞g迎的內(nèi)容從主頁中刪除,以此提升更重要的內(nèi)容或相關(guān)的內(nèi)容的性能。刪除的內(nèi)容應(yīng)該仍可以通過二級頁面訪問到——只是不放在首頁,不然的話它們會消耗寶貴的帶寬并降低性能體驗(yàn)。
那么,我們怎樣來創(chuàng)建一個獨(dú)立的性能優(yōu)化的移動網(wǎng)站呢?
分而治之
我能理解為什么RWD(響應(yīng)web設(shè)計(jì))從用戶界面設(shè)計(jì)的角度來說很有意義。對于6平方英寸屏幕和10平方英寸屏幕,以及僅僅是需要進(jìn)行改動的布局來說,在內(nèi)容,導(dǎo)航以及業(yè)務(wù)流程需求方面可以完全一致,這實(shí)在是太棒了。
平均設(shè)備屏幕尺寸。
但是,在上述條件不為真或者對性能要求嚴(yán)格的時候有一個獨(dú)立的移動網(wǎng)站 具有特別的意義。
獨(dú)立的移動網(wǎng)站常常表現(xiàn)出一種不良的用戶體驗(yàn)。通過給網(wǎng)站懲罰賦以較低的搜索引擎等級,Google現(xiàn)在投射出一縷曙光 到這些普通的問題上。問題包括了將每個桌面頁發(fā)送到單獨(dú)的移動主頁,重定向到應(yīng)用下載頁,阻止用戶訪問大屏的網(wǎng)站,對所有帶特定操作系統(tǒng)的設(shè)備以相同的方式處理。
這些糟糕的實(shí)現(xiàn)讓人對這些概念有一個壞的印象。這里是一些簡單又正確的做法。
下面的 .NETweb.config片段將把來自智能手機(jī)的第一個請求,重定向到網(wǎng)站上“Smartphone”部分指定的等價頁面。 重要的是,查詢字符串與頁面名字在重定向的過程中一直保持著。
- <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>
在大多數(shù)情形,當(dāng)重定向到替代頁面時,如果愿意的話用戶應(yīng)當(dāng)可以返回原始的頁面;或許他們對網(wǎng)站的大屏幕版本更熟悉呢。firstRequestOnly屬性保證了只有來自設(shè)備的第一次請求才被重定向。devicesFile屬性是用來對不支持cookies的設(shè)備進(jìn)行跟蹤。timeout屬性控制了在多長時間內(nèi)該設(shè)備被記憶(為了重定向的目的)。
重定向系統(tǒng)還必須知道哪個頁面是針對哪種設(shè)備設(shè)計(jì)的。mobilePagesRegex屬性被應(yīng)用到請求URLs。如果存在匹配,頁面將不適用重定向。這阻止了無窮重定向的情況。
locations元素允許配置定義不同的地址,以及相關(guān)的規(guī)則。這個例子將Smartphone目錄插入到原始的URL。查詢字符串和其他的URL信息在重定向過程中一直保持。所有影響到請求上下文的信息必須被傳送,以便用戶獲得他們期望的內(nèi)容。
這個簡單的方法使得一個搜索引擎友好的,兼容Google的,移動手機(jī)優(yōu)化的網(wǎng)站,在傳送的過程中有良好的用戶體驗(yàn)和優(yōu)異的性能。這個過程的基礎(chǔ)是DDR,它快速的,一致的,精確的提供了設(shè)備的信息。對于改變了移動手機(jī)瀏覽器設(shè)置到桌面模式的用戶,重定向?qū)⒉粫l(fā)生。
警惕云
云服務(wù)是給網(wǎng)站迅速增加特性的流行方法。但是它們跨越Internet的請求對性能帶來損耗。如果忽略處理時間,我們觀察到由Amazon Web Service提供的云服務(wù)的數(shù)據(jù)傳輸有平均200毫秒的延時。
200毫秒是一個黃金秒的20%。因此,仔細(xì)考慮一下你使用的云服務(wù)在哪里,確保它們是異步調(diào)用的,以便在等待響應(yīng)的過程中其他處理能繼續(xù)下去。它們應(yīng)該避免關(guān)鍵路徑上的活動,例如判別請求設(shè)備的信息。
壓縮內(nèi)容
緊隨視頻、圖像之后,CSS和HTML占據(jù)了大量的Web流量。我們需要優(yōu)化所有這一切的方法。視頻本身就是就可以寫一篇文章,所以要等以后再說。
圖像
流行的解決方案是同一張圖像提供三個版本,而且當(dāng)瀏覽器渲染頁面的時候,使用JavaScript或者CSS選擇最適合請求設(shè)備的那張圖象。這是一個好的開始,不過管理同一圖像的不同版本卻是很痛苦的;圖像從來都不是完美優(yōu)化的,而且這種方法給有限CPU和電池電量的移動設(shè)備增加了進(jìn)行圖像大小調(diào)整的負(fù)擔(dān)。
有一個更好的處理方法是使用圖像優(yōu)化器??梢酝ㄟ^Viusal Studio的集成開發(fā)環(huán)境把52Degrees.mobi的圖像優(yōu)化器增加到ASP.NET站點(diǎn)。下面的配置將自動增加到web.config里。
- <handlers>
- <add name="Image" verb="GET" path="P.axd" type="FiftyOne.Framework.Image.ImageHandler, FiftyOne.Framework" />
- </handlers>
上面的處理器告訴互聯(lián)網(wǎng)信息服務(wù)(IIS)圖像處理器應(yīng)該處理資源P.axd的任何GET請求。
一旦web.config里啟用這項(xiàng),下面的ASP.NET代碼將使用圖像優(yōu)化器從三種可能的資源-也就是分別為240,480和640像素寬的圖像中確定一個圖像。
- <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>
當(dāng)初始化顯示圖像的時候,服務(wù)器將發(fā)送一個白色的1x1像素的GIF顯示在圖像所在位置。下面就是生成的HTML:
- <img id="B" src="P.axd?i=E.gif&i=1"/>
一旦頁面裝載完成,JavaScript用來算出最終顯示圖像所需要的真正的尺寸,然后向服務(wù)器請求一個大小明確的圖像。經(jīng)過JavaScript處理后,上面的HTML轉(zhuǎn)換為:
- <img id="B" src="P.axd?i=1&w=500"/>
web.config中引用的圖像處理器把i查詢字符串關(guān)聯(lián)到圖像源,因此服務(wù)器上最適合的圖像將用作調(diào)整的最初圖像。w查詢字符串參數(shù)指定了所請求圖像的寬度。因此不需要提供多個圖像;一個單獨(dú)的圖像幾乎就可以了。 這種方法易于實(shí)現(xiàn),而且結(jié)果是大小明確的圖像。這樣做不但減少了帶寬,而且還減少了移動電話CPU的運(yùn)行周期和電量。
HTML
整個牛津大辭典包括171476個單詞。如果一個電腦用獨(dú)有的二進(jìn)制數(shù)字代表一個單詞,而不是一個字母表的字母,大概需要用18比特(向上舍入大概3字節(jié))。這表明壓縮算法是十分有效的。
然而,HTML不是很有效,因?yàn)樗錆M了用字符代表的元素、IDs,類,styles和Javascript,沒有考慮到是否是人可讀的。壓縮可以減少這些,但還是要有開銷的。這就是為什么流行的庫都有人們讀不懂的壓縮版本。
在服務(wù)器發(fā)送給瀏覽器之前,一些與標(biāo)記相關(guān)的單詞還可以最小化,而且不會丟失任何這些單詞的意義??匆粋€上面所示的圖像的例子,ASP.NET里標(biāo)準(zhǔn)的HTML的圖像元素的ID屬性是ImageBanner。
- <mob:Image runat="server" ID="ImageBanner" CalculateSizeMode="ClientWidth" Style="clear: both; width: 100%">
然而發(fā)送給瀏覽器的代碼只使用B。對一個單獨(dú)的元素來說。這樣的性能改進(jìn)是微不足道的,然而,對一個含有數(shù)百個元素的復(fù)雜頁面來說,傳送這樣的頁面會更快,而且瀏覽器將能更快地處理這一切。
Includes
圖像例子生成的HTML看起來比較奇怪:
- <img id="B" src="P.axd?i=1&w=500"/>
這段ASP.NET代碼沒有樣式而且也沒有為img元素設(shè)置CLASS屬性。那么它的樣式是怎么來的呢?
服務(wù)器端最小化過程中將會確定樣式信息,并且為網(wǎng)頁創(chuàng)建一個CSS文件,從而減小了HTML的大小。當(dāng)HTML變化時,樣式已經(jīng)被緩存在瀏覽器中,不需要重新下載。CSS片段看起來就像這樣:
- #B{clear:both;width:100%;}
如果許多元素共享同一個樣式,那么就要給這個CSS增加ID屬性,這樣它們就可以共享相同的信息。
還可以通過使用服務(wù)器端樣式元素實(shí)現(xiàn)在多個元素和頁面上共享樣式信息。下面的代碼擴(kuò)展了前面圖像例子,以此來說明共享樣式元素。
- <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">
根據(jù)設(shè)備的能力,還可以對這個元素進(jìn)行進(jìn)一步擴(kuò)展,使得可以應(yīng)用其他樣式,并且可在多個頁面上優(yōu)化樣式表。 這種技術(shù)總是確保只傳輸必須的CSS,因此在對同一個頁面的后續(xù)請求方面提高了性能,尤其是對HTML內(nèi)容只有稍稍不同的頁面。
#p#
為什么選擇.NET?
上面所示的圖像優(yōu)化和動態(tài)最小化HTML和CSS內(nèi)容的技術(shù)和代碼例子取決于頁面渲染之后且服務(wù)器傳送給瀏覽器之前更改的內(nèi)容。這樣的預(yù)處理技術(shù)在諸如ASP.NET的Web表格這樣的結(jié)構(gòu)里實(shí)現(xiàn)起來相當(dāng)容易。
然而在基于腳本的比如PHP這樣的架構(gòu)里實(shí)現(xiàn)它們就非常復(fù)雜。正是由于這個原因,并且為了保持一致性,這篇文章中的例子都是在.NET上的。我們已經(jīng)能夠把這種技術(shù)應(yīng)用到其他語言上,例子代碼可在朋友博客里看到。
例子
公共衛(wèi)生基金會的企業(yè)實(shí)現(xiàn)了本文中提到的技術(shù),并在第一周成功提高了23%的性能。
其他比較注重性能的網(wǎng)站包括 24.com(媒體),ServiceTick(分析),LettingWeb(財(cái)產(chǎn)),AdSupply(廣告)和Kitsap Credit Union(金融)都使用了本文提到的部分或是全部的技術(shù)來優(yōu)化他們的移動站點(diǎn)。
總結(jié)
為了真正地優(yōu)化性能,我們需要考慮網(wǎng)站所有人的投資回報(bào)。監(jiān)測設(shè)備特征的不同是根本起始點(diǎn)。
然后我們才能部署諸如利用分散的移動網(wǎng)站來分離或改變內(nèi)容焦點(diǎn)的解決方案。我們還能通過收緊縮小圖像和HTML,移除jQuery,問詢何時單獨(dú)使用響應(yīng)式網(wǎng)站設(shè)計(jì)(RWD),以及其它技術(shù)等使性能達(dá)到最高。當(dāng)然,現(xiàn)有的技術(shù)也是至關(guān)重要的,例如配置緩存路徑和壓縮內(nèi)容。
微調(diào)我們的開發(fā)環(huán)境,來模擬真實(shí)世界的情況,這樣也可以在整個開發(fā)過程中獲得對性能的更好的理解。
現(xiàn)在就優(yōu)化
為了能讓你更多地考慮性能,我已經(jīng)設(shè)定好了一個尋找世界上最重型的網(wǎng)站的競賽。尋找一個網(wǎng)頁,它在移動電話上運(yùn)行得很糟糕,并將其提交給競賽。我們會比較頁面的分量,以及它是否是最重型的,你將因此而贏得1000美元。與此同時,實(shí)現(xiàn)本文以及其他Smashing Magazine中的牛文中提到的技術(shù),以確保你的網(wǎng)站在被我們掂量其性能的時候不會出現(xiàn)在榜單之首!
從未有過比這更能提高你的網(wǎng)站性能的機(jī)會了。
本文的代碼示例是由.NET提供。我已經(jīng)在companion article文章中展示了用PHP, Java, C 和Python達(dá)到同樣的效果。我在這篇文章的結(jié)尾會解釋為什么選用.NET。