HTML5的性能比原生差很多,比如切頁時白屏、列表滾動不流暢、下拉刷新和上拉翻頁卡頓。
在低端Android手機上,很多原生App常用的功能和體驗效果都很難使用HTML5技術模擬。
我們首先來看個問題,如何避免切頁白屏。
瀏覽器的頁面在切換時,由于其頁面加載機制,在跳轉到下一個頁面時,先要請求聯網、載入頁面代碼、構建dom、渲染,后才顯示出來。
在終結果渲染完畢前,會出現幾十毫秒甚至數秒的白屏。原生App是沒有這個問題的。
雖然使用SPA單頁應用模型,即ajax+div切換也可以避免白屏,但把所有頁面寫在一個SPA頁面里,頁面多了手機上也跑不起來,初始化非常慢,而且工程大了代碼那個亂。。。被坑過的人自然知道。
標準HTML5無法解決,我們就使用擴展的手段。
HTML5+ 是一套增強HTML5的規范,它可以用JS調用幾十萬原生API。
想要解決切頁白屏這個問題,需要使用plus.webview類來做MPA多頁應用。
plus.webview類是對原生的webview對象的js化封裝,使用js可以操作webview。
解決白屏的原理是:把每個頁面當作一個webview,但用js來控制它就像控制div一樣。
因為webview可以隱式創建,后臺載入內容,并且在載入完畢時有js事件通知,我們可以在新頁面載入完成后再把它通過動畫移入屏幕,從而避免白屏。
同時webview之間相互獨立,不會出現SPA下不同頁面js和css沖突的問題。
通過操作webview來避免切頁白屏,有幾種常見的做法:
一種是稱之為預載,即后臺預載新頁面的HTML文件及資源,使用時直接調出這個已經創建好的webview;
另一種稱之為現載,即點擊前頁的鏈接開始走waiting轉圈,同時后臺開始加載完整的新頁面,加載完再用js控制顯示到前臺。
還有一種稱之為分開載入,隨后會細講。
所謂預載,即后臺預載新頁面的HTML文件及資源,使用時直接調出這個已經創建好的webview。
Hello mui、csdn、36kr等項目源碼,都使用了預載思路。
以新聞類app為例,啟動首先載入資訊列表list頁面,然后后臺創建了一個隱藏的webview,加載了一個內容模板show頁面。
在點擊list頁面的一個新聞item時,調用webview的窗體控制動畫,把show頁面側滑進屏幕。
但show頁面僅僅是一個模板而沒有數據,在show頁面剛側滑進屏幕時,在show頁面有一個“加載中”的提示。
緊接著show頁面開始執行ajax請求,聯網加載數據并顯示出來。
我們可以在list頁面的item點擊里,一邊移動窗體,一邊通知新頁面執行ajax。webview間相互傳遞消息使用webview的evalJS方法。
這種做法,相當于用戶是在新的show頁面來等待聯網數據。
示例代碼如下:
var webviewShow;document.addEventListener('plusready', function(){ //擴展的js對象在plusready后方可使用 webviewShow = plus.webview.create("show.html");});function clicklist (id) { //list點擊item后的事件 webviewShow.show("slide-in-right",150);}
預加載,由于不顯示出來,并不會過多增加資源占用。(同時顯示在屏幕上的webview不要超過3個,隱藏在后臺的webview不要超過20個)
如果是list轉到content,不同的item點擊只是一個頁面,完全可以使用預載。
但如果頁面不同且較多,后臺預載太多webview會有點慢。這里的慢不是會造成手機慢,而是預載是耗時間的。
Hello H5+這個示例使用了預加載技術,有一些網頁在啟動時就被預載了。點擊到這些網頁時,切換會非常快。
之所以不預載所有的網頁,不是內存不夠,是時間不夠。
預載這些網頁時手機CPU消耗比較高,此時如果滑動列表,會發現滑動不流暢。
為了避免這個問題,Hello H5+的首頁是延遲進入的,等待預載的幾個頁面都完成后才調用plus.navigator.closeSplashscreen()關閉啟動封面圖片。
如果預載太多頁面,等待全部預載結束后再進入首頁,會導致App啟動非常慢。
所謂現載,就是用戶點擊時后臺創建webview加載新頁面完整內容,渲染后再顯示到前臺
有時我們無法使用預載,為了避免白屏,就要等待新webview完成后再通過動畫把它移進來。
當點擊list頁面的item時,首先通過plus.nativeUI.waiting()來彈出一個等待框。
緊接著在后臺create一個webview,載入show頁面。
show頁面在后臺聯網獲取數據。
show頁面在數據解析渲染后,有兩種方式通知list頁面。
一種是在list頁面注冊show頁面的webview的loaded回調,在新webview載入完成后系統會自動觸發loaded事件。
還有一種是在show頁面合適的js位置通過evalJS方法通知list頁面關閉等待框,并執行窗體切換把show頁面顯示出來。
當然show頁面也可以不通知list頁面,而是自己直接關閉等待框,并調用窗體切換動畫把自己顯示出來。
示例代碼如下:
function clicklist (id) { //list點擊item后的事件 var nwaiting = plus.nativeUI.showWaiting();//顯示原生等待框 webviewShow = plus.webview.create("show.html");//后臺創建webview并打開show.html webviewShow.addEventListener("loaded", function() { //注冊新webview的載入完成事件 nwaiting.close(); //新webview的載入完畢后關閉等待框 webviewShow.show("slide-in-right",150); //把新webview窗體顯示出來,顯示動畫效果為速度150毫秒的右側移入動畫 }, false);}
需要注意的是,為了減少窗體切換的等待,一般不在點擊后使用webview的create方法,因為創建過程也有時間消耗。比較好的方法是提前創建一個webview,需要載入頁面時使用webview的loadURL方法來載入。
如果中間還是出現白屏,可以通過延遲顯示新webview來解決。
目前官方演示demo里,把新webview移入進來是在新webview的loaded回調中做的,這個時機略晚于新頁面的DOMContentLoaded時機,即dom tree完成但頁面未必渲染完畢。
此時可以settimeout延遲新webview的顯示動畫,也可以干脆不在上級頁面操作顯示動畫,而是在下級頁面的onload或你認為頁面已經渲染完畢的時間來操作把下級頁面用動畫顯示出來。
了解以上兩種方式后,我們可以玩些更復雜的。
一般窗體都是2部分組成,頂部的標題欄(帶返回按鈕)和中間的內容區,或稱body區。
我們把它分成2個webview,一個叫webviewHead,一個叫webviewBody。(名字是隨便起的,方便下面引用)
我們可以在首頁先預載webviewHead,載入一個head.html的頁面,這個HTML里上面是title內容+back按鈕或其他菜單按鈕,其中間body區只有一行字“加載中...”
然后再預載一個webviewBody,先放著不用。
在點擊切換窗體時,首先用窗體動畫直接把這個已經預載的webviewHead移入窗體內,此時用戶會看到一個新界面,上面顯示加載中字樣。
同時js代碼操作webviewBody的loadURL,給它載入你需要載入的頁面,等待這個webview的loaded觸發后,再把它append到webviewBody里。
這樣,可以做到不預載,也能讓用戶盡可能的感受到窗體的流暢切換。
Hello mui的窗體切換,采用了這種方式。大家可以去看它的實現代碼。
使用這種方式是要注意:head.html的body區域的背景色和webviewBody里載入的頁面的背景色應該統一,這樣在把webviewBody append到webviewHead時才不會突兀。
另外,head和body分開載入,對于body的顯示會略有延遲。如果body是靜態內容,在iOS上由于性能很好,HTML的頁面切換比較快,其實head和body合并為一個頁面直接載入會更快點。
預加載可以避免白屏的發生,但窗體動畫有時還不如預期流暢,有些新窗體移入過程中,還在不停聯網獲取數據,不停重繪界面,導致窗體進入過程感覺卡頓,此時還有一個高級技巧是截圖動畫。
5+ runtime提供了一個plus.nativeObj.Bitmap的對象,同時webview對象提供了一個截圖方法,可以把webview顯示區域保存到bitmap對象中。此外webview的動畫方法中支持傳bitmap,這樣給開發者提供了一個性能調優的手段。
我們可以預載一個webview,然后把這個webview預先截圖下來,然后在窗體移入時在動畫參數里傳入保存這個截圖的bitmap對象,這樣窗體移動時,移動的就不是webview,而是移動的圖片,這樣能讓窗體動畫流暢許多。
流暢度:飄圖>飄webview>飄div。
一般場景下飄webview就夠了,在特別追求極致的環境下,可以飄圖,流暢度杠杠的。
從HBuilder7.2起,nativeObj下擴展了view對象,可以寫字、貼圖,還提供點擊事件。
舉個實例:從商品列表到商品詳情界面,為了讓點擊響應速度更快,我們完全可以把商品詳情里的非數據部分預截圖,從列表點擊時先popin動畫飄一張提前截好的圖進來,同時在圖上面把商品名稱寫上去,同時ajax聯網加載商品的json數據并渲染,然后在關閉假圖。
大家搞明白5+提供的這些api的原理后,其實可以靈活的根據自己的需求應用webview和nativeobj。
但我們還是歸納一下之前提到的3種切換方式適用的場景,當然我們也會提到SPA的適用場景。
1. 如果是類似于新聞資訊或公文列表等應用,每個列表item點擊后打開的是同一個頁面。應該使用預載的方式,直接preload,然后每次在這個show頁面里面通過ajax請求服務器來更新數據。當然退出這個界面時記得要把ajax請求后渲染的內容hide掉,不然下次點另一條新聞會先閃一下上一篇新聞。
2. 如果是類似九宮格導航,或每個列表item去往不同的頁面,如果頁面數量低于9個,可以在啟動時直接預載這9個webview。
如果頁面太多,無法全部預載,建議把導航拆成二級導航。
比如帶tab或segment的九宮,或者二級導航列表,在展開二級導航時做預載。
如果業務場景合適,還可以做智能預載,判斷用戶空閑時間和接下來可能要點擊的頁面來預載。當然高手才能搞定這么復雜的代碼。
3. 如果頁面因為各種條件無法預載,至少把能預載的預載一部分,剩余的按如下方式處理:
- 3.1 如果都是本地頁面不聯網的簡單頁面。
比如很多軟件的設置界面,其內部的二級頁面跳轉,內容都很簡單,也不涉及區域滾動卡的問題,此時SPA比較好。確實其實SPA也不是一無是處,它適用的場景下用它是比較好的。當然這么做雖然體驗好,但一個App里一會MPA、一會SPA,一般開發者可能會頭暈。那些追求極致體驗的高級開發者可以通過混合MPA和SPA,博采眾家之長來達到佳用戶體驗。
而對于普通開發者,其實統一使用head和body分離的多webview方式其效果也完全可以商用。
在Hello mui里,下方有setting模板,就是一個spa的樣例。在iOS上,這個樣例的效果可以完全達到原生效果,頂部標題欄的漸變非常酷。
- 3.2 如果是本地頁面不涉及聯網但有下拉刷新或超長復雜圖文列表,那還是head和body分開載入的方式比較好。
因為SPA的復雜div滾動和下拉刷新在低端Android手機很卡。
比如一些列表頁面,本地有緩存的內容,從websql等本地數據庫里加載內容,通過下拉刷新的方式更新數據,這類頁面本身設計就應該是雙webview的。
- 3.3 如果是聯網獲取內容的頁面,使用head和body分開載比較合適
如果切一個頁面,要等待新頁面載入,新頁面載入后還要等待聯網載入數據,連續2個等待框就會讓用戶反感。
反正也是要等待聯網,干脆就和等待webview加載一起等了。
先把預載的webviewHead移入屏幕,然后等待webviewBody載入HTML及聯網獲取數據,一并載入完畢后把webviewBody拍到主webview上。
mui框架為了簡化窗體管理的工作,把一些常用的窗體模型做了簡化封裝。
但對于復雜的窗體切換,仍需開發者搞明白上面提到的窗體切換原理。
mui的init方法,通過參數封裝了preload和subpage,這樣就可以方便的預載webview,對于head和body分離的雙webivew界面,也可以方便的通過subpage參數來控制webviewBody。
mui的openWindow方法,封裝了顯示waiting,載入新頁面,處理動畫,關閉waiting等工作。
mui的back樣式控制,自動封裝了窗體的隱藏和關閉。
Hello H5+和Hello mui都是簡單的本地靜態頁面,但頁面數量非常多,導致無法全部預載。
所以其實很多實際的App可以通過預載實現比這2個示例App更好的窗體切換效果。
Hello H5+的轉場策略是:
1. 預載一些常見的,比如主列表前2個webview,以及窗體切換和下拉刷新的webview。
2. 其他的窗體采用了“現載”的方式,即點擊item后立即彈waiting,然后后臺加載webview,載入完畢后移入屏幕。
Hello mui的轉場策略是:
整體采用head和body分開載入的方式。
從直觀的感受看2個示例,在iOS上,Hello H5+效果更好。在Android上,各有千秋。
但靜態示例的設計和實際業務場景不同,由于實際業務大量存在聯網內容,所以其實head和body分開載入更實用。
mui封裝了head和body分開載入的模板設計,而Hello H5+只是api演示,不會做封裝,所以造成2個示例的轉場方式不同。
首頁是沒有預加載的概念的。
啟動封面的圖片關閉觸發條件,默認是在首頁的webview的loaded事件發生后關閉。
如果首頁內容較大或聯網后、框架載入后重繪屏幕,即在首頁HTML的DOMContentLoaded后無法立即渲染界面,會出現啟動封面圖片消失后,頁面還沒渲染好的情況。
此時需要手動控制封面圖片消失。
首先在工程下manifest.json里找到plus、splashscreen、autoclose節點,設置為false,即手動控制封面圖片的消失。
然后在首頁合適的位置,一般在plus的ready事件后,調用js關閉封面圖片,plus.navigator.closeSplashscreen();
這樣就能防止個頁面的白屏。
為了節約系統資源,在webview不可見時,我們的引擎默認會回收掉它的渲染資源。
如果頁面復雜、渲染的慢,在返回時可能會因為來不及渲染而造成先模糊后清晰的問題。
此時或者優化頁面寫法,加快渲染。或者使用我們提供的api,使得webview在不可見時一樣不移除渲染資源。
另外從HBuilder7開始,pop-in、slide-in-right、fade-in等動畫經過特殊處理,在返回時不會虛一下。
5+runtime默認是開啟Android硬件加速的,但Android的一些非官方rom的硬件加速有bug。尤其是Android5.0初期的一些rom。
關于這方面的處理方案,單獨起了一篇文章,參考http://ask.dcloud.net.cn/article/55
plus.webview提供了很多切換動畫,上下左右平移、淡入淡出、縮放、擠壓......但比較常用的動畫是右移slide-in-right和擠壓pop-in。
一般在iOS上,強烈推薦使用pop-in,更接近原生體驗。
在Android上,其實也是pop-in效果更好,但為了達到更好的效果,也需要開發者編碼時注意一些寫法,如果寫不好,效果還不如slide-in-right
這里是pop-in動畫使用注意:http://ask.dcloud.net.cn/article/225
不管使用哪種方法,都要注意一點,手機App的HTML頁面必須本身性能足夠高。
頁面體積要小、加載和渲染要快。
互聯網上有很多提升HTML、JS、CSS性能的方案,此處不再羅列。
但注意一點,如非必要,不要使用框架。
pc上web框架的盛行,也是后來pc瀏覽器性能足夠高之后的事情,互聯網發展初期的開發者并不像如今這般依賴框架。
手機,尤其是低端Android機的性能也很差,如果照著寫pc web的思路寫頁面,終的用戶體驗必然會非常差。
首先,AMD框架盡量不用,包括angularjs在內,js動態解析標簽再替換渲染是很慢的。
其次,jquery、zepto也盡量不要使用。document.getElementById("") 、document.querySelectorAll("")、$(""),這三者性能依次下降,尤其是在低端Android上遍歷dom時,當你辛辛苦苦減少白屏和用戶等待時間時,你會非常憤怒這些js框架拖了你的后腿。
并且HBuilder提供了很多代碼塊來快速完成代碼,比如敲dg就可以出document.getElementById(""),比敲$("#")要快多了。
當然個別頁面為了使用一些現成的jquery插件而引用了框架,倒也不會對app整體產生太大影響,這需要開發者自己根據產品對性能追求的極致程度來把握了。
還有一點就是聯網數據量要小,把app做出c/s方式,界面在本地,從服務器取json,此時的網絡消耗要比b/s方式下頁面和數據都從服務器加載要小的多,窗體切換也更快。
本站文章版權歸原作者及原出處所有 。內容為作者個人觀點, 并不代表本站贊同其觀點和對其真實性負責,本站只提供參考并不構成任何投資及應用建議。本站是一個個人學習交流的平臺,網站上部分文章為轉載,并不用于任何商業目的,我們已經盡可能的對作者和來源進行了通告,但是能力有限或疏忽,造成漏登,請及時聯系我們,我們將根據著作權人的要求,立即更正或者刪除有關內容。本站擁有對此聲明的最終解釋權。