了解拖慢移動(dòng)應(yīng)用的這三大原因,才好解決問(wèn)題
譯文【51CTO.com快譯】一般而言,軟件應(yīng)用服務(wù)的緩慢主要表現(xiàn)在兩個(gè)方面:一個(gè)是加載頁(yè)面的滯留、或長(zhǎng)時(shí)間等待UI(用戶(hù)界面)的建立;另一方面則與UX(用戶(hù)體驗(yàn))有關(guān),包括界面按鈕無(wú)法按照預(yù)期做出響應(yīng),或是用戶(hù)發(fā)現(xiàn)各種默認(rèn)的手勢(shì)、動(dòng)作及動(dòng)畫(huà)無(wú)法生效。顯然,這兩者都會(huì)影響到用戶(hù)的使用體驗(yàn)。在本文中,我將和您深入探討導(dǎo)致應(yīng)用變慢的主要原因,以及如何通過(guò)跨平臺(tái)的應(yīng)用框架來(lái)予以解決。
跨平臺(tái)vs.原生
在日常研發(fā)過(guò)程中,人們經(jīng)常會(huì)對(duì)跨平臺(tái)技術(shù)抱有成見(jiàn)。他們認(rèn)為:由于并非原生,而是基于網(wǎng)絡(luò),因此其整體效率會(huì)較為緩慢,當(dāng)然也就注定會(huì)出錯(cuò)。
但是,他們殊不知:如今,那些原生架構(gòu)能夠?qū)崿F(xiàn)的功能,跨平臺(tái)的應(yīng)用框架基本都能實(shí)現(xiàn),它們之間的運(yùn)行效率通常取決于用戶(hù)是如何實(shí)現(xiàn)其程序代碼的。在實(shí)際項(xiàng)目中,我們經(jīng)常會(huì)看到有的團(tuán)隊(duì)使用Swift/Java,開(kāi)發(fā)出了速度緩慢且時(shí)常崩潰的原生應(yīng)用;而其他團(tuán)隊(duì)則能夠使用Titanium之類(lèi)的跨平臺(tái)技術(shù),開(kāi)發(fā)出了流暢的產(chǎn)品。
另外,無(wú)需用到混合式跨平臺(tái)工具,跨平臺(tái)的框架本身就能夠生成真正意義上的原生UI。因此,作為原生應(yīng)用的開(kāi)發(fā)人員,您過(guò)去遇到過(guò)的諸如內(nèi)存泄漏等問(wèn)題,在如今大多數(shù)跨平臺(tái)框架中已得到了***解決,而它們能夠協(xié)助開(kāi)發(fā)人員實(shí)現(xiàn)了大部分的重量級(jí)加載任務(wù)。
可見(jiàn),只有理解了您所使用的框架局限性,才是避免應(yīng)用程序出現(xiàn)緩慢狀況的關(guān)鍵所在。下面,我將試著和您分析跨平臺(tái)應(yīng)用在移動(dòng)設(shè)備上運(yùn)行緩慢、甚至無(wú)法響應(yīng)的原因,并且?guī)椭业礁鞣N加速的辦法。作為代碼示例,我選用的是Titanium作為框架。當(dāng)然,這些改進(jìn)技巧也適用于其他類(lèi)型的框架。
第1類(lèi)原因:設(shè)計(jì)
通常情況下,在開(kāi)發(fā)跨平臺(tái)的應(yīng)用程序時(shí),開(kāi)發(fā)團(tuán)隊(duì)往往趨向于將兩大平臺(tái)的版本合二為一。而這種設(shè)計(jì)理念恰恰是導(dǎo)致應(yīng)用程序在長(zhǎng)期運(yùn)行后,變得緩慢的根本原因之一。
首先,您需要了解的是:不同平臺(tái)所對(duì)應(yīng)的默認(rèn)UI與UX(更為重要)是截然不同的。例如,iOS為每一個(gè)窗體“棧(stack)”都配置了打開(kāi)/關(guān)閉動(dòng)畫(huà)、以及幻燈片手勢(shì)。這些雖然看似微不足道,但是動(dòng)畫(huà)的觸發(fā),能夠給用戶(hù)帶來(lái)交互式使用的體驗(yàn)。這種“讓用戶(hù)通過(guò)在手機(jī)屏幕上滑動(dòng)手指,在窗體棧中切換應(yīng)用內(nèi)與應(yīng)用間不同頁(yè)面”的想法,緩沖了跳轉(zhuǎn)的時(shí)間。用戶(hù)不會(huì)直觀地體會(huì)到點(diǎn)擊后退按鈕所可能碰到的緩慢問(wèn)題。
因此,在為某個(gè)窗體設(shè)計(jì)不同的外觀時(shí),開(kāi)發(fā)人員應(yīng)酌情考慮是否采用原生的顯示效果。如果想自定義的話(huà),那么開(kāi)發(fā)人員就需要通過(guò)一個(gè)事件偵聽(tīng)器,來(lái)捕捉用戶(hù)是否在屏幕上滑動(dòng)了手指,進(jìn)而關(guān)閉相應(yīng)的窗體。否則,動(dòng)畫(huà)手勢(shì)的突然消失,會(huì)讓用戶(hù)生硬地感覺(jué)到整個(gè)UX的下降,進(jìn)而產(chǎn)生應(yīng)用變慢的感受。
除了上述動(dòng)畫(huà)效果與UI上的不同之外,開(kāi)發(fā)人員還應(yīng)當(dāng)注意跨平臺(tái)框架的垃圾回收問(wèn)題,以防止出現(xiàn)自定義UI在運(yùn)行過(guò)程中出現(xiàn)內(nèi)存泄漏的漏洞。
同時(shí),隨著用戶(hù)的廣泛使用、以及移動(dòng)設(shè)備硬件性能的提升,他們會(huì)設(shè)置更多的自定義手勢(shì)與快捷方式,而您的移動(dòng)應(yīng)用需要捕捉、跟蹤與計(jì)算更多的手指軌跡。因此,您會(huì)發(fā)現(xiàn)數(shù)據(jù)的加載與計(jì)算的阻塞會(huì)相互影響,并形成惡性循環(huán)。以至于某些應(yīng)用在上線(xiàn)一年之后,就失去了可維護(hù)性與可使用性,而開(kāi)發(fā)團(tuán)隊(duì)則不得不對(duì)程序進(jìn)行重寫(xiě)與重構(gòu)。
解決方案
首先,在應(yīng)用的設(shè)計(jì)階段,我們應(yīng)當(dāng)充分了解與接受平臺(tái)之間的差異性。通過(guò)針對(duì)兩套系統(tǒng)的產(chǎn)品設(shè)計(jì),來(lái)發(fā)現(xiàn)不同平臺(tái)的用戶(hù)對(duì)于界面與效果的期待。在公司內(nèi)部,您可以邀請(qǐng)長(zhǎng)期使用Android手機(jī)和iPhone的兩類(lèi)人,進(jìn)行各種使用效果的實(shí)測(cè)。
其次,利用內(nèi)置的UX/UI特征,嘗試著從原生平臺(tái)的角度,實(shí)現(xiàn)應(yīng)用界面上的各項(xiàng)功能。例如:在大多數(shù)iOS應(yīng)用中,按鈕一般會(huì)被放置在標(biāo)題的左右兩側(cè);而Android版本則通常會(huì)把按鈕放置在右邊,或者直接隱藏到菜單圖標(biāo)的里面。另外,Android應(yīng)用會(huì)內(nèi)置有后退按鈕,而iOS則會(huì)有一個(gè)返回的手勢(shì)。通過(guò)支持這些基于不同移動(dòng)平臺(tái)的特征,用戶(hù)就能夠直觀地理解您的應(yīng)用,并產(chǎn)生一定的認(rèn)同感。
第2類(lèi)原因:加載太多
應(yīng)用程序變慢的另一個(gè)主要原因是:在UI中一次性加載的元素太多。畢竟,移動(dòng)設(shè)備的性能不及用戶(hù)電腦,很難同時(shí)處理多項(xiàng)任務(wù)需求。而且,不是所有的人都能使用***版本的iPhone、以及高端Android設(shè)備。其中仍在使用Android 4.4的用戶(hù)也不在少數(shù)。因此,要想在各類(lèi)應(yīng)用商店里脫穎而出,您的移動(dòng)產(chǎn)品就應(yīng)該具備在低端配置和老舊版本的設(shè)備上仍有不俗性能的能力。
例如,在iOS的應(yīng)用商店內(nèi),如果您點(diǎn)開(kāi)并選中“Today”標(biāo)簽的話(huà),它會(huì)迅速地載入五款應(yīng)用標(biāo)簽。接著,您在向下滑動(dòng)時(shí),請(qǐng)注意滾動(dòng)條的位置和大小。您會(huì)不難發(fā)現(xiàn):滾動(dòng)條會(huì)以原來(lái)的大小滑向屏幕的底部,當(dāng)接近屏底的20%處時(shí),它會(huì)迅速縮小、甚至彈回到上方的某個(gè)位置。而屏幕上則開(kāi)始顯示“第二頁(yè)”的數(shù)據(jù)。
可見(jiàn),該應(yīng)用商店并非一次性加載了所有數(shù)據(jù)。因此,在實(shí)際應(yīng)用的設(shè)計(jì)與構(gòu)建中,我們應(yīng)該捫心自問(wèn):我們?cè)谧層脩?hù)***看到推送內(nèi)容時(shí),是否一并加載了后續(xù)內(nèi)容?您是否針對(duì)圖片進(jìn)行了延遲加載?在預(yù)加載的數(shù)據(jù)中,有多少應(yīng)該在ListVIEW中就顯示出來(lái)?100項(xiàng)還是200項(xiàng)?在應(yīng)用啟動(dòng)之處,您需要調(diào)用多少個(gè)API?我在TiSlack論壇里,有看過(guò)“在APP的啟動(dòng)時(shí),如何最有效地調(diào)用50個(gè)API”的帖子,以及“如何在ListVIEW中有效加載10000條信息”的留言。
在此,我的答案是:“不要這樣做!”沒(méi)有人能夠一次性查看這么多條信息。如果您希望用戶(hù)能夠滾動(dòng)列表的話(huà),那么請(qǐng)使用延遲加載(lazy-loading)以及分頁(yè),哪怕數(shù)據(jù)已存放在設(shè)備的本地空間里。另外,如果想讓用戶(hù)進(jìn)行搜索的話(huà)?那么也請(qǐng)您在服務(wù)端本地搜索完畢之后,再推送到用戶(hù)設(shè)備上。
解決方案
從上面蘋(píng)果應(yīng)用商店的例子,我們可以了解到:在應(yīng)用被***打開(kāi)之后,數(shù)據(jù)內(nèi)容才被進(jìn)行加載。例如在默認(rèn)的TabGroup中:
...
另外,我在首頁(yè)選項(xiàng)卡的窗體中添加了一個(gè)postlayout事件偵聽(tīng)器。該函數(shù)的作用是調(diào)用窗體的初始化器,在為選項(xiàng)卡獲取相關(guān)數(shù)據(jù)之后,再進(jìn)行加載。通過(guò)等待postlayout事件,您可以確保用戶(hù)看到的不是加載頁(yè)面,而是正常的應(yīng)用內(nèi)容。下面便是用來(lái)滯后進(jìn)行初始化內(nèi)容的簡(jiǎn)單函數(shù)--handlePostlayout:
- function handlePostlayout() { $.todayWindow.add( Alloy.createController('todayContent').getView() ); }
如您所見(jiàn),在Today標(biāo)簽中,真實(shí)請(qǐng)求的內(nèi)容、以及相關(guān)依賴(lài)項(xiàng),并沒(méi)有馬上被初始化或加載,此舉無(wú)疑加快了應(yīng)用程序的整體性能。
同時(shí),對(duì)于其他選項(xiàng)卡而言,我們可以監(jiān)控屏幕窗體的“焦點(diǎn)(focus)”事件,只在窗體被聚焦時(shí)進(jìn)行加載。同理,您也可以將該事件用在當(dāng)前窗體被再次聚焦時(shí),及時(shí)刷新數(shù)據(jù);以及運(yùn)用到Firebase Analytics(Android集成埋點(diǎn)分析)中。
記住:相對(duì)于那些用分頁(yè)或延遲加載的方式,來(lái)重新初始化整個(gè)頁(yè)面而言,僅下載數(shù)據(jù)的方式在量級(jí)上會(huì)更“輕”、速度會(huì)更快、也更節(jié)約CPU的計(jì)算力。
第3類(lèi)原因:橋
此處的“橋”是一個(gè)源自Titanium和React Native之類(lèi)跨平臺(tái)框架的概念。它表示:JavaScript代碼和原生代碼之間的每一次交互,都會(huì)產(chǎn)生開(kāi)銷(xiāo)。例如:您在服務(wù)器進(jìn)行API調(diào)用的時(shí)候,顯然,相對(duì)于花費(fèi)100次調(diào)用API,而每次僅取回1條數(shù)據(jù)而言,我們更愿意僅調(diào)用1次API,并一次性地取回100條數(shù)據(jù)。
那么何為“過(guò)橋”呢?其實(shí),我們?cè)谔砑覷I元素、更新UI元素、以及觸發(fā)動(dòng)畫(huà)時(shí)都會(huì)產(chǎn)生調(diào)用。例如:在Titanium中的Ti.App.fireEvent流就需要用到過(guò)橋的概念。該類(lèi)事件通常被用于在應(yīng)用程序內(nèi)觸發(fā)某些操作,進(jìn)而實(shí)現(xiàn)初始化。另外,此類(lèi)事件也會(huì)觸發(fā)UI的更新操作。因此,一個(gè)事件可能會(huì)觸發(fā)兩次過(guò)橋。
那么當(dāng)所有的事情都同時(shí)發(fā)生,尤其處于循環(huán)觸發(fā)的狀態(tài)時(shí),過(guò)橋進(jìn)程就會(huì)變得“擁擠不堪”。而如果橋的帶寬容量又比較“狹窄”的話(huà),您的應(yīng)用就會(huì)產(chǎn)生大面積的延遲,甚至可能發(fā)生中斷事故,進(jìn)而直接影響了用戶(hù)的使用體驗(yàn)。
解決此類(lèi)問(wèn)題其實(shí)非常簡(jiǎn)單。您只需要將UI的批處理組合在一起便可。因此,當(dāng)需要修改ListTimes的整張表時(shí),我們可以采用ListTeal.RePateTimeSt,來(lái)輕松地一次性重新插入整個(gè)數(shù)據(jù)集。而當(dāng)您需要更改某個(gè)UI元素的一組屬性時(shí),則完全可以使用applyProperties,而無(wú)需更改每個(gè)屬性的具體順序。
同時(shí),您可以使用Backbone Events,來(lái)觸發(fā)整個(gè)應(yīng)用程序中的各種事件,而不必采取過(guò)橋的方式。而且,我們很容易將當(dāng)前的各種Ti.App事件遷移到Backbone Events上。
如下所示,我們首先將Backbone Events包括到alloy.js中。
- Alloy.Globals.events = _.clone(Backbone.Events);
然后,您將應(yīng)用里任何曾經(jīng)用到了Ti.App.fireEvent()的地方,替換為如下函數(shù):
- Alloy.Globals.events.trigger();
接著,以同樣的方式,您可以繼續(xù)將Ti.App.addEventListener()替換為:
- Alloy.Globals.events.on()
您甚至可以對(duì)自己的項(xiàng)目進(jìn)行全局搜索與替換,以獲得立竿見(jiàn)影的性能提高效果。
結(jié)論
綜上所述,拖慢移動(dòng)應(yīng)用的原因有許多種,其中大部分與低效的代碼實(shí)現(xiàn)方式有關(guān)。因此,我們應(yīng)該盡量采用原生的UI組件,盡可能少地加載數(shù)據(jù),同時(shí)減少過(guò)橋的數(shù)量。***,我再次重申:跨平臺(tái)框架并不會(huì)注定比原生應(yīng)用要慢。事實(shí)上,在各種應(yīng)用商店中,您會(huì)發(fā)現(xiàn)有95%到99%的應(yīng)用(不包括游戲應(yīng)用),都是采用Titanium之類(lèi)的框架所構(gòu)建的。
原文標(biāo)題:3 Reasons Mobile Apps Can Be Slow,作者:Rene Pot
【51CTO譯稿,合作站點(diǎn)轉(zhuǎn)載請(qǐng)注明原文譯者和出處為51CTO.com】