圖源:unsplash
我在巴西一家P2P網(wǎng)絡(luò)借貸金融科技公司Mutual工作,具體的工作任務(wù)是幫助尋求公平利率的借款人與尋求高于市場回報的貸方聯(lián)系起來。我們的ReactNative IOS和Android應(yīng)用程序被廣泛應(yīng)用,在巴西國內(nèi)的用戶量很大,可以通過各種設(shè)備下載。
然而,我們可以通過Facebook的設(shè)備-年份-類庫了解到:在給定設(shè)備型號的情況下,該庫會顯示出在哪一年該設(shè)備被視為高端設(shè)備。我們的大多數(shù)用戶使用的是低端設(shè)備。
例如,最受用戶歡迎的手機是三星Galaxy A10,雖然它在2019年3月推出,但在2013年被認為是一款高端手機。縱觀所有用戶的設(shè)備,其中85%在2015年或之前是高端產(chǎn)品。
因此,我們打算把重點放在優(yōu)化應(yīng)用程序上,這樣即使設(shè)備配置不怎么高端,我們的用戶也能獲得良好的體驗。

每年旗艦產(chǎn)品設(shè)備的百分比
我們最近仔細研究了一下應(yīng)用程序的大小,在Android上的應(yīng)用程序大小是26.8MB。雖然這個數(shù)字并不是很大,但絕對超過了同行的中位數(shù),google Play控制臺報告的中位數(shù)是16.3MB。
對于那些由于數(shù)據(jù)計劃有限,可用磁盤空間很少甚至根本沒有,不得不選擇保留或卸載哪些應(yīng)用程序的用戶來說,程序大小可能是一個決定因素。
這一點對于Mutual尤為重要,因為借款人必須通過該應(yīng)用程序支付每月的分期款項。當借款人卸載該應(yīng)用程序時,他們按時還款的機率會急劇下降,直接影響投資者在公司市場上的回報。

Mutual的應(yīng)用程序(26.8 MB)比同類應(yīng)用程序要大得多
應(yīng)用程序的大小不僅影響卸載率,而且對安裝激活轉(zhuǎn)化率也有很大的影響。APK的大小每增加6 MB,安裝激活轉(zhuǎn)換率就會降低1%。
Google Play的一篇文章很深入地談到了這個問題(
https://medium.com/googleplaydev/shrinking-apks-growing-installs-5d3fcba23ce2)。在低端設(shè)備使用比例較大的發(fā)展中國家,這種影響甚至更大:在新興市場,如果應(yīng)用程序的APK刪減10 MB,安裝激活轉(zhuǎn)換率就能增加約2.5%。

每個國家的APK大小每減少10MB,安裝激活轉(zhuǎn)換率的增加
因此,必須在不降低用戶體驗的前提下,盡可能地縮小應(yīng)用程序大小。我們要做的第一步是查看Android開發(fā)人員可用的官方資源。

Android App Bundle
這一部分表明縮小應(yīng)用程序大小的最簡單方法是嘗試新的Android App Bundle(AAB)分發(fā)方法。在此之前,我們一直在編譯一個可以在大多數(shù)Android設(shè)備中運行的舊Android Package(APK)文件,并將其上傳到Google Play控制臺來分發(fā)應(yīng)用程序。
然而,AAB包只包含編譯過的代碼和資源。因此,上傳時,通過了解其規(guī)格和CPU架構(gòu),Google Play本身就會為每種設(shè)備類型生成一個優(yōu)化的APK。

圖源:unsplash
所以只要對構(gòu)建管道做一個簡單的改變,就可以免費獲得縮減程序大小的好處!
在瀏覽了這篇文章之后,我們修改其React Native Gradle構(gòu)建腳本去運行bundleRelease,而不是當前的assembleRelease。就這樣,我們有了AAB文件。
在對FastlaneConfig的supply進行了一些更深一步的修改之后,就可以直接自動上傳到Play Store了,新版本將會出現(xiàn)在Google Play控制臺上。通過這一改變,我們將已交付的APK大小縮減了9.1MB~12.4MB!

舊的APK為26.8 MB,而新的AAB為14.4至17.7 MB
但是要小心:如果在Hermes中使用React Native,那么可能要根據(jù)這個問題更新soloader依賴,否則就有可能讓用戶使用到一個有嚴重錯誤的應(yīng)用程序。
好在我們能夠通過在alpha發(fā)布軌道中的測試捕捉到這個問題。但它可能很容易通過,因為它不會在本地或建立一個APK時出現(xiàn)。

使用Android Size Analyzer優(yōu)化資產(chǎn)
下一個建議是Android Size Analyzer。這是一個命令行工具,可以分析一個Android應(yīng)用程序,指出可以對尺寸進行多少縮減。
在使用命令size-analyzercheck-bundle[BUNDLE]運行它之后,會收到一個可以優(yōu)化的大型資產(chǎn)和映像列表,還包括配置ProGuard。

size-analyzer命令的輸出
Proguard
Proguard是一個壓縮、模糊和優(yōu)化JAVA字節(jié)碼的工具。因為已經(jīng)了解到可能與其他Android庫不兼容,所以我們還沒有探索這一途徑。我們正在尋找快速而容易的縮減的方法,未來可再進行優(yōu)化。
大型資產(chǎn)
使用-d標志再次運行該命令,將得到按大小排序的每個資產(chǎn)的列表。由于size-analyzer工具不知道應(yīng)用程序的用戶流,它讓我們決定哪些可以刪除或動態(tài)捆綁。

按大小排序的大型資產(chǎn)列表
第一個也是最大的項目是React Native JavaScript包。目前還不可能拆分和動態(tài)加載,但是將在稍后看到如何縮小它。沿著建議列表進一步向下,我們看到許多大型字體(TTF)和圖像(JPG和PNG)資產(chǎn)。
不需要的圖片
內(nèi)部Storybook工具中使用的四張巨大的JPG圖片吸引了我們的注意力。他們給生產(chǎn)APK增加了額外的2MB垃圾!
在軟件工程這個復(fù)雜的世界里,人人都會犯錯誤。我相信與同行分享這些經(jīng)驗時,大家都可以從這里學(xué)習(xí)到很多。如果不跟蹤應(yīng)用程序不斷增長的規(guī)模,很可能也會犯這些失誤。
字體
在迅速擺脫這些大圖之后,我們不斷地查看列表的其余部分。很明顯,有大量的字體被捆綁。在與設(shè)計團隊交談后,他們告訴我們許多舊組件沒有嚴格遵循排版指南。

圖源:unsplash
所以我們確定了哪些組件可以被移除,哪些可以使用類似的更新字體。通過此法,我們把字體的使用從六個減少到四個。
另一個問題是字體資產(chǎn)大小巨大無比!每個字體大小幾乎達到了670KB。這意味著四種字體占了未壓縮包高達2.7MB的大小。
有一個名為FontForge的工具,可以更深入地查看和修改這些字體文件。打開后可以看到大部分的資產(chǎn)規(guī)模可以用擴展的西里爾文字和其他不需要的字形來解釋。這些都可以被刪除,因為應(yīng)用程序完全是葡萄牙語。
隨著這一變化,每個字體大小從670KB縮小到70KB,減少了90%!

字體中包含的一些字形的示例
刪除不需要的字體并優(yōu)化剩余的字體總共減少了3.8 MB,這意味著最終APK的大小減少了2 MB。


刪除兩個字體并優(yōu)化剩余字體前后對比
優(yōu)化圖像
在剩下的圖像中有一些是相當大的。我們通過圖像優(yōu)化工具(tinyjpg)優(yōu)化了其中的幾個,大小縮減了很多。之后,我們決定優(yōu)化應(yīng)用程序內(nèi)使用的所有41個JPG和PNG資產(chǎn)。

優(yōu)化的圖像的前后
這使圖像資產(chǎn)從2.5MB減少到756 KB,減少了70%。但是圖像本身沒有優(yōu)化,在生成最終APK的過程中已經(jīng)被壓縮了。因此,實際上只為最終用戶削減了500 KB。
在這之后,我們意識到已經(jīng)耗盡了所有容易的hanging fruit optimizations。所有進一步的資產(chǎn)優(yōu)化要么需要更多的努力,要么只會帶來很小的改進。

優(yōu)化React Native JavaScript bundle
已經(jīng)看完了native資產(chǎn),現(xiàn)在是時候分析JavaScript包了。這是一件特別值得優(yōu)化的事情,原因有三:
· 首先,它減小了成品APK的bundle大小;
· 其次,由于JS虛擬機解析更少的代碼,所以能進行更快的應(yīng)用程序啟動;
· 最后,也是最重要的一點是,它加快了每周通過CodePush多次發(fā)布的空中(OTA)更新的速度。
Bundle analyzer
要決定將如何減少bundle的大小,首先,需要能夠看到什么占用了最多的空間。為此,我們將依靠另一個優(yōu)秀的開源工具:
react-native-bundle-visualizer。在項目運行時,得到了應(yīng)用程序的每個文件夾和依賴項以及它們各自大小的可視化。

Mutual前端代碼庫的庫和文件夾的展示及其大小
應(yīng)用程序包總共有5.49MB,其中57.8%來自node_modules依賴項,27.5%來自應(yīng)用程序代碼,其余部分是工具無法映射的。捆綁過程已經(jīng)移除了未使用的代碼路徑,在這里看到的是應(yīng)用程序?qū)嶋H使用的代碼。即便如此,總還有改進的空間。
最大的依賴項是math.js,顧名思義,它實現(xiàn)了許多數(shù)學(xué)運算。我們應(yīng)該不需要這種依賴項,因為在服務(wù)器中執(zhí)行所有敏感的計算,然后只需將結(jié)果發(fā)送給應(yīng)用程序。
仔細查看前端代碼,可以發(fā)現(xiàn)庫用于一些簡單的操作。很有可能是一個同樣從事過后端代碼開發(fā)者出于習(xí)慣而使用它。我們迅速地從庫中提取了這些方法,并將其納入代碼庫,完全消除了此依賴項。這將捆綁包的大小降至4.64MB。移除一個lib之后,大小縮減了15.5%!

圖源:unsplash
如前所述,我們使用Storybook獨立地開發(fā)和測試組件。但是,它應(yīng)該只在本地和臨時環(huán)境中可用。任何最終用戶都不能看到它。
正因為如此,我們使用一個環(huán)境變量來控制是否啟用App的這一部分。雖然這對于限制訪問有效,但捆綁程序無法知道該變量的值。由于這個限制,所有的Storybook代碼最終都要放入到production bundle中去。
為了解決這個問題,將此部分的導(dǎo)入隔離到單個文件中。然后創(chuàng)建了該文件的兩個版本:一個包含Storybook,另一個用于生產(chǎn),只有一個虛擬組件。為了在面向生產(chǎn)時在這些文件之間切換,編寫了一個腳本,該腳本在交換兩個文件的綁定步驟之前運行。
通過這種方法,我們能夠從生產(chǎn)中完全刪除Storybook代碼路徑,消除node_modules依賴項以及內(nèi)部配置的所有代碼。

更新之后帶有兩個版本索引文件的storybook文件
通過這兩項更改,能夠?qū)undle大小從5.49MB縮減到4.2MB。這意味著用戶將擁有更快的應(yīng)用程序啟動速度和更新下載速度。

bundle最終大小為4.2MB
在所有這些改進之后,我們再次將應(yīng)用程序上傳到了Play商店。最終的APK大小將在10.5到13.7MB之間,從最初的26.8MB減少了60%!按照Google Play團隊的文章,這可以將安裝激活轉(zhuǎn)化率提高3.75%。

最初的APK和經(jīng)過所有改進的最終AAB版本之間的比較
作為面向業(yè)務(wù)的軟件工程師,我們知道對公司來說最好的決定就是積累技術(shù)債務(wù)來更快地更新迭代產(chǎn)品。對于像Mutual這樣試圖找到產(chǎn)品與市場契合點的初創(chuàng)公司來說,情況尤其如此。
但是如果不監(jiān)控這個債務(wù),可能會犯一些大錯誤,比如捆綁2MB的測試圖片和使用一個不必要的巨大的庫。這也是常見的隧道視野,快速和容易的機會優(yōu)化已有的。

圖源:unsplash
所以需要定期后退一步。確保不會錯過對應(yīng)用程序的大小、速度或任何方面的快速改進。我們只花了兩天的時間來分析、規(guī)劃和執(zhí)行上述所有的改進,這些改進使應(yīng)用程序的大小縮減了60%。
這么小的努力,解決困擾已久的難題,帶來這么多實實在在的效果,這真的很驚人。