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