天貓11.11:手機淘寶 521 性能優化項目揭祕

又是一年雙十一,億萬用戶都會在這一天打開手機淘寶,高興地在會場頁面不斷瀏覽,面對琳琅滿目的商品圖片,搶着添加購物車,下單付款。爲了讓用戶 更順暢更方便地實現這一切,作到「如絲般順滑」,雙十一前夕手機淘寶成立了「521」(我愛你)性能優化項目,在平常優化基礎之上進行三個方面的專項優化 攻關,分別是1)H5頁面的一秒法則;2)啓動時間和頁面幀率提高20%;3)Android內存佔用下降50%。優化過程當中遇到的困難,思考後找尋的方 案,實施後提取的經驗都會在下面詳細地介紹給讀者。前端

第一章 一秒法則的實現

「1S法則」是面向Web側,H5鏈路上加載性能和體驗方向上的一個指標,具體指:1)「強網」(4G/WIFI)下,1秒徹底完成頁面加載,包括首屏資源,可看亦可用;2)3G下1秒完成首包的返回;3)2G下1秒完成建連。ios

在移動網絡環境下,http請求和資源加載與有線網絡或者PC時代相比有着本質區別,尤爲是在2G/3G網絡下,每每一個資源請求建連的時間都會 是整個Request-Response流程裏面的大頭,一些小資源上拖累效應尤爲明顯。例如一個1k的圖片,即便在10k/s 的極慢網速下,理論上0.1秒可下載完畢,但因爲創建鏈接的巨大消耗,這樣一個請求會要耗上好幾秒。web

僅僅「建連」這一個點,就能說明移動時代的Web側性能優化和PC時代目標和方式都相去甚遠,要求咱們必須從更底層,更細緻的去抓,才能取得看起來相對有效的結果。數據庫

15年初的性能狀況數組

平均LoadTime-WIFI瀏覽器

平均LoadTime - 4G緩存

平均LoadTime - 2G安全

3.35s性能優化

3.84s服務器

14.34s

能夠看到優化前,平均時間很難接近1秒。爲了實現優化目標,在技術和實施抓手層面,由底層往上,作了四方面事情:

  1. 網絡節點:HttpDNS優化
  2. 建連複用:SSL化,SPDY建連高複用
  3. 容器層面:離線化和預加載方案
  4. 前端組件:請求控制,域名收斂,圖片庫,前端性能CheckList

網絡節點:HttpDNS優化

DNS解析想必你們都知道,在傳統PC時代DNS Lookup基本在幾十ms內。而咱們經過大量的數據採集和真實網絡抓包分析(存在DNS解析的請求),DNS的消耗至關可觀,2G網絡大量5-10s,3G網絡平均也要3-5s。

針對這種狀況,手淘開發了一套HttpDNS-面向無線端的域名解析服務,與傳統走UDP協議的DNS不一樣,HttpDNS基於HTTP協議。基於HTTP的域名解析,減小域名解析部分的時間並解決DNS劫持的問題。

手淘HttpDNS服務在啓動的時候就會對白名單的域名進行域名解析,返回對應服務的最近IP(各運營商),端口號,協議類型,心跳等信息。

優勢

1)防止域名劫持

傳統DNS由Local DNS解析域名,不一樣運營商的Local DNS有不一樣的策略,某些Local DNS可能會劫持特定的域名。採用HttpDNS可以繞過Local DNS,避免被劫持;另外,HttpDNS的解析結果包含HMAC校驗,也可以防止解析結果被中間網絡設備篡改。

2)更精準的調度

對域名解析而言,尤爲是CDN域名,解析獲得的IP應該更靠近客戶端的地區和運營商,這樣纔能有更快的網絡訪問速度。然而,因爲運營商策略的多樣 性,其推送的Local DNS可能和客戶端不在同一個地區,這時獲得的解析結果可能不是最優的。HttpDNS可以獲得客戶端的出口網關IP,從而可以更準確地判斷客戶端的地區 和運營商,獲得更精準的解析結果。

3)更小的解析延遲和波動

在2G/3G這種移動網絡下,DNS解析的延遲和波動都比較大。就單次解析請求而言,HttpDNS不會比傳統的DNS更快,但經過 HttpDNS客戶端SDK的配合,整體而言,可以顯著下降解析延遲和波動。HttpDNS客戶端SDK有幾個特性:預解析、多域名解析、TTL緩存和異 步請求。

4)額外的域名相關信息

傳統DNS的解析結果只有ip,HttpDNS的解析結果採用JSON格式,除了ip外,還支持其它域名相關的信息,好比端口、spdy協議等。利用這些額外的信息,APP能夠啓用或中止某個功能,甚至利用HttpDNS來作灰度發佈,經過HttpDNS控制灰度的比例。

建連複用:SSL化,SPDY建連高複用

出於安全目的,淘寶實現了全站SSL化。自己和H5鏈路性能優化沒有直接的關係,可是從數據層面看,SSL化以後的資源加載耗時都會略優於普通的Http鏈接。

有讀者會有疑惑,SSL化以後每一個域名首次請求會額外增長一個「SSL握手」的時間,DNS建連也會比http的狀態下要長,這是不可避免的,可是爲何一次完整的RequestRespone 流程耗時會比http狀態下短呢?

合理的解釋是:SSL化以後,SPDY能夠默認開啓,SPDY協議下的傳輸效率和建連複用效益將最大化。SPDY協議下,資源併發請求數將再也不受瀏覽器webview的併發請求數量限制,併發100+都是可能的。

同時,在保證了域名收斂以後,一樣域名下的資源請求將能夠徹底複用第一次的DNS建連和SSL握手,因此,僅在第一次消耗的時間徹底能夠被 SPDY後續帶來的資源傳輸效率,併發能力,以及鏈接複用度帶來的收益補回來。甚至理論上,越複雜的頁面,資源越多的狀況,SSL化+SPDY以後在性能 上帶來的收益越大。

容器層面:離線化和預加載方案

收益最明顯,實現中遇到困難最多的就是離線化或者說資源預加載的方案。預加載方案是爲了在 用戶訪問H5以前,將頁面靜態資源(HTML/JS/CSS/IMG...)打包預加載到客戶端;用戶訪問H5時,將網絡IO攔截並替換爲本地文件IO;從而實現H5加載性能的大幅度提高。

天貓11.11:手機淘寶 521 性能優化項目揭祕

手淘實現要比上面的通用示意圖複雜:由於Android和iOS安裝包已經很大,因此預加載Zip包(如下簡稱「包」)都是從服務器端下載到客戶端;本地 須要記錄總體包狀態,並在合適的時機與服務器通訊並交換狀態信息。在包發佈更新的過程當中要注意,本地版本和服務端最新包之間的差量同步,必要的網絡判 斷,WiFi下才下載等。

面對億級UV,而且在服務器資源頗有限的狀況下搞定這個流程,須要藉助CDN來扛住壓力,實際上CDN扛住了約98%的流量。

須要注意的是預加載實際上也是一種緩存,更新比H5稍慢一些,主要受幾個因素影響:推送到達率(用戶是否在線,用戶所在網絡質量),總控,服務端策略等,因此須要經過推拉結合的觸發策略並優化下載包的體積(增量包)來提高到達率。

除了優化到達率,手淘還作了url解CDN Combo後再映射的優化工做,若 URL 是 Combo URL,那麼會對 URL 解 Combo,解析出其中包含的資源。而後嘗試從本地讀取包含的資源,若是全部資源都在本地存在,那麼將本地文件內容拼裝爲一份完整文件並返回;不然 URL 直接走線上,不作任何操做。

提高到達率和解CDN Combo再映射,這兩個容器側對於離線化方案的優化對於本次H5鏈路上總體性能的提高有着相當重要的意義。

前端組件:請求控制,域名收斂,圖片庫,前端性能CheckList

嚴格執行性能方面的CheckList,主要有三個點:

1)圖片資源域名所有收斂到gw.alicdn.com;

2)前端圖片庫根據強弱網和設備分辨率作適配;

3)首屏數據合併請求爲一個。

在執行中,性能的檢查和校驗必定要歸入到發佈階段,不然就不是一個合理的流程。性能的工具和校驗必定應該是工程化,研發流程裏面的一部分,纔可以保障性能自動化,低成本,不退化。

經過以上優化方案,H5頁面的平均Loadtime在Wifi,4G下均如期進入1秒,3G和2G也有80%多達成1s法則的目標。

第二章 啓動時間和頁面幀率20%的提高

不少App都會遇到如下幾個常見的性能問題:啓動速度慢;界面跳轉慢;事件響應慢;滑動和動畫卡頓。

手機淘寶也不例外。咱們分爲兩部分來作,第一部分是啓動階段優化,目的解決啓動任務繁多,缺少管控的問題,減小啓動和首頁響應時間。第二部分是針 對各個界面作優化,提高界面跳轉時間和滑動幀率,解決卡頓問題。雙十一性能優化目標之一就是將啓動時間和頁面幀率在原有基礎上繼續優化提高20%,接下來 就從這兩部分的優化過程來作一一介紹。

一.啓動階段的優化

手機淘寶做爲阿里無線的航母,接入的業務Bundle超過100個,啓動初始化任務超過30個,這些任務缺乏管控和性能監控。

那麼首要任務就是:

創建任務管理機制

全部的初始化任務能夠用兩個維度來區分:

1)任務必要性:有些任務是應用啓動所必需的,好比網絡、主容器;有些任務則不是必需的,僅僅實現單個業務功能,甚至是爲了業務自身體驗和性能而考慮在啓動階段提早執行,其合理性值得推敲。

2)任務獨立性:將應用的架構簡單分紅基礎庫、中間件、業務三層,這三層中業務層最爲龐大,其初始化任務也最多。對於中間件來講,其初始化可能依賴於另一箇中間件。但對於一個獨立的業務模塊來講,其初始化任務應該也具備獨立性,不存在跟其餘業務模塊依賴關係。

啓動階段任務管理機制包含了以下幾方面的內容

1)任務可並行

既然不少初始化任務是獨立的,那麼並行執行能夠提升啓動效率。

2)任務可串行

雖然咱們指望全部初始化任務都相互獨立,可是在實際中不可避免會存在相互依賴的初始化任務。爲了支持這種狀況,咱們設計任務的異步串行機制,這裏主要借鑑了前端的Promise思想實現。

3)任務可插拔

面對這麼多不一樣優先級的初始化任務,任何一個出現異常都會致使應用不能啓動,給穩定性帶來嚴重挑戰。所以咱們設計了可插拔機制,當某一項初始化任 務出現問題時可以跳過該任務,從而不影響整個應用的啓動使用。這裏咱們根據初始化任務的必要性作了區分,只有非必要的初始化任務纔會應用可插拔的特性,這 也是爲了防止出現不執行一個必要的初始化任務致使應用啓動使用出現問題。

4)任務可配置

在ios上經過plist指定每一項啓動任務, 其中字段optional表示該項是不是必需的,當以前運行出現crash或者異常時,若值爲YES則能夠不執行該項。

有了任務管理機制,並引入懶加載的理念,能夠持續地合理有效管控啓動階段的各項初始化任務,是大型app必不可少的環節。

檢測超時方法,優化主線程

性能優化前,初始化代碼都在主線程中執行,爲了啓動性能已將部分初始化任務放入後臺線程或者異步執行。可是隨着手淘業務發展和人員變動,仍是出現 了在主線程中執行很重的初始化任務。爲此,在ios實現了一套應用運行時方法耗時檢測機制,可以對應用中全部類的方法調用作耗時統計。方便的找到超時的方 法調用以後,就能夠有針對性的作出修改,或刪除或異步化。這種方法調用耗時檢測機制一樣適用於APP運行過程當中,從而找到致使應用卡頓的根本緣由,最後作 出對應修改。

多線程治理

分析各個模塊的線程數量,檢查線程池的合理性。經過去掉沒必要要的線程和線程池,再控制線程池的併發數和優先級。進一步經過框架層的線程池來接管業務方的線程使用,以減小線程太多的問題。

減小IO讀寫

從自身業務出發,去除若干初始化階段沒必要要的文件操做,以及將若干非實時性要求的文件操做延後處理。Android上對於頻繁讀寫數據庫和 SharedPreference以及文件的模塊,經過增長緩存和下降採樣率等手段減小對IO的讀寫。對於SharedPreference進行了專門的 優化,減小單個文件的大小,將毫無聯繫的存儲鍵值分開到不一樣文件中,而且防止將大數據塊存儲到SharedPreference中,這樣既不利於性能也不 利於內存,由於SharedPreference會有額外的一份緩存長期存在。

降級部分功能

例如搖一搖功能,測試發現應用場景不頻密,但業務使用了高頻率的遊戲模式,會耗電及佔用主線程時間。對該功能作了降級處理,下降檢測頻率。同理,對於其餘非必須使用但又佔據較多資源的模塊也都作了適當的降級處理。

熱啓動時間的縮短

在安卓手機上咱們把啓動分爲兩類進行檢測和優化:冷啓動和熱啓動。冷啓動是程序進程不存在的狀況下啓動,熱啓動是指用戶將程序切換到後臺或者不斷按Back鍵退出程序,實際進程還存在的狀況下點擊圖標運行。

以前安卓手淘在按Back鍵退出時整個首頁Activity銷燬了,熱啓動會通過一個比較長的過程。優化後首頁在退出的時候並不銷燬 Activity,可是會釋放圖片等主要資源,在下次熱啓動時就能更快的進入。另外,將手淘歡迎頁的界面從其它bundle轉移到首頁的模塊,在進入歡迎 頁時就開始初始化首頁資源,作到更快展現。

在通過一系列的優化後,啓動方面已經有了明顯的改善,在進入首頁的時候不會卡頓,GC次數也減小了一半以上。

二.各個界面的優化

各界面優化咱們也是圍繞着提升幀率和加快展示而展開的,手淘的幾個主鏈路界面,都是相對比較複雜的,既使用多圖,也使用了動態模板的技術。功能越 複雜,也越容易產生性能問題,因此常遇到佈局複雜、過渡繪製多、Activity主要函數耗時、內容展現慢、界面從新佈局(Layout)、GC次數多等 問題。

優化GPU的過渡繪製

經過開發者選項的GPU過渡繪製選項檢查界面的過渡繪製狀況。該優化並不複雜,經過去掉層疊佈局中多餘的背景設置、圖片控件有前景內容的時候不顯 示背景、界面背景定義到Activity的主題中、減小Drawable的複雜Shape使用等手段就能夠基本消除過渡繪製,減小對GPU和CPU的浪 費。

優化層級和佈局

層級越多,測量和佈局的時間就會相應增長,建立硬件列表的時間也會相應增長。有時咱們會嵌套不少佈局來實現本來只要簡單佈局就能夠實現的功能,有 時還會添加一些測試階段纔會使用的佈局。經過刪除無用的層級,使用Merge標籤或者ViewStub標籤來優化整個佈局性能。好比一些顯示錯誤界面、加 載提示框界面等,不是必須顯示的這些佈局可使用ViewStub標籤來提高性能。

另外要靈活使用佈局,並非層級越多就會性能越差,有時候1層的RelativeLayout會比3層嵌套的LinearLayout實現的性能更糟糕。

除了靈活使用佈局,另外咱們還經過提早inflate以及在線程中作一些必要的inflate等來提早初始化佈局,減小實際顯示時候的耗時。對於一些複雜的佈局,咱們還會本身作複用池,減小inflate帶來的性能損耗,特別是在列表中。

加快界面顯示

1)能夠經過TraceView工具找出主線程的耗時操做和其餘耗時的線程並做優化。另外減小主線程的GC停頓,由於即便並行GC,也會對 heap加鎖,若是主線程請求分配內存的話,也會被掛起,因此儘可能避免在主線程分配較多對象和較大的對象,特別是在onDraw等函數中,以減小被掛起的 時間。另外能夠經過去掉ListView ,ScrollView等控件的EdgeEffect效果,來減小內存分配和加快控件的建立時間。

2)利用本地緩存,主要界面緩存上次的數據,而且配合增量的更新和刪除,能夠作到數據和服務端同步,這樣能夠直接展現本地數據,不用等到網絡返回數據。

3)減小沒必要要的數據協議字段,減小名字長度等,並做壓縮。還能夠經過分頁加載數據來加快傳輸解析時間。由於JSON越大,傳輸和解析時間也會越久,引起的內存對象分配也會越多。

4)注意線程的優先級,對於佔用CPU較多時間的函數,也要判斷線程的優先級。

優化動畫細節

經過TraceView工具發現,一些Banner輪播廣告和文字動畫在移出可視區域後,仍然存在定時刷新,不只耗電也影響幀率。優化措施是在移出可視區域後中止動畫輪播。

阻斷多餘requestLayout

在ListView滑動,廣告動畫變化等過程當中,圖片和文字有變化,常常會發現整個界面被從新佈局,影響了性能。尤爲佈局複雜時,測量過程很費時 致使明顯卡頓。對於大小基本固定的控件和佈局例如TextView,ImageView來講,這是多餘的損耗。咱們能夠用自定義控件來阻斷,重寫方法 requestLayout、onSizeChanged,若是大小沒有變化就阻斷此次請求。對於ViewPager等廣告條,能夠設置緩存子view的 數量爲廣告的數量。

優化中間件

中間件的代碼被上層業務方調用的比較頻繁,容易有較多的高頻率函數,也容易產生細節上的問題。除了頻繁分配對象外,例如類初始化性能,同步鎖的額外開銷,接口的調用時間,枚舉的使用等等都是不能忽視的問題。

減小GC次數

安卓上的GC會引發性能卡頓,必須重點優化。除了第三章會詳細介紹對於圖片內存引發GC的優化,咱們還作了以下工做:

1)減小對象分配,找出沒必要要的對象分配,如可使用非包裝類型的時候,使用了包裝類型;字符串的+號和擴容;Handler.post(Runnable r)等頻繁使用。

2)對象的複用,對於頻繁分配的對象須要使用複用池。

3)儘早釋放無用對象的引用,特別是大對象和集合對象,經過置爲NULL,及時回收。

4)防止泄露,除了最基本的文件、流、數據庫、網絡訪問等都要記得關閉以及unRegister本身註冊的一些事件外,還要儘可能少的使用靜態變量和單例。

5)控制finalize方法的使用,在高頻率函數中使用重寫了finalize的類,會加劇GC負擔,使得性能上有幾倍的差異。

6)合理選擇容器,在性能上優先考慮數組,即便咱們如今習慣了使用容器,也要注意頻繁使用容器在性能上的隱患點:首先是擴容開銷, HashMap擴容時從新Hash的開銷較大。其次是內存開銷,HashMap須要額外的Map.Entry對象分配 ,須要額外內存,也容易產生更多的內存碎片。SparseArray和ArrayList等在內存方面更有優點。再次是遍歷,對於實現了 RandomAccess接口的容器如ArryList的遍歷,不該該使用foreach循環。

7)用工具監控和精雕細琢:在頁面滑動過程當中,經過Memory Monitor查看內存波動和GC狀況,還可經過AlloCation Tracker工具觀察內存的分配,發現不少小對象的分配問題。

8)利用Trace For OpenGL工具找出界面上致使硬件加速耗時的點,例如一些圓角圖片的處理等。

經過多種工具和手段配合,手淘各個界面性能上有了較大的提升,平均幀率提升了20%,那麼內存節省50%又是如何實現的哩,請看下文。

第三章 Android手機內存節省50%

Android上應用出現卡頓的核心緣由之一是主線程完成繪製的週期過長引發丟幀。而影響主線程完成繪製時間的主要有兩方面,一方面是主線程處於運行狀態 時須要作的任務太多但CPU資源有限,另一方面是GC時Suspend時直接掛起了全部線程包括主線程。GC對整體性能的影響在4.x的系統上尤其突 出,一部分是單次GC pause總時長,一部分是用戶操做過程當中GC發生的次數。而決定這兩部分的因素就是Dalvik內存分配。那麼在手淘這樣的大型應用中究竟是誰佔用了 內存大頭 呢?

誰佔用了內存

基於雙11前的手淘Android版本,咱們在魅藍note1(4.4 OS)上滑動完首頁後,dump出其Dalvik Heap,總體內存佔用的分佈狀況以下圖。能夠看出,byte數組(a)佔用空間最大,絕大多數是用來存放Bitmap的 像素數據(Pixel Data) 。另外(c)與(d)一塊兒佔用了18.4%, byte數組加上Bitmap、BitmapDrawable總共佔用了64.4%,成爲內存佔用的主體。這也從側面說明了手淘是以圖片爲瀏覽主體內容的 大型應用。而每每圖片須要較大的內存塊,在分配時引發GC的可能性也每每最大。那咱們能不能將圖片這部分須要的內存移走而不在Dalvik Heap分配呢?若是能,那麼不單GC會明顯減小,同時Dalvik Heap總大小也會降低50%左右,對總體性能會有顯著的提高。

(點擊放大圖像)

天貓11.11:手機淘寶 521 性能優化項目揭祕

何處安放的Pixel Data

Ashmem即匿名共享內存,使用的核心過程是建立一個/dev/ashmem設備文件,控制反轉設置文件的名字和大小,最終把設備符交給 mmap就獲得了共享內存。在Android系統中Binder進程間通訊的實現就是依賴Ashmem完成不一樣進程間的內存共享。但此處並不利用其共享特 性,而是使用它在Native Heap完成內存分配。圖片空間如何才能使用Ashmem,答案在Facebook推出的Fresco中已有說起,那就是解碼時的purgeable標 記,這樣在系統底層解碼位圖時會走Ashmem空間分配,而非Dalvik Heap空間。這樣就解決了像素數據存放由Dalvik到Native的問題了嗎?

1 BitmapFactory.Options options = new BitmapFactory.Options();
2   /*
3    * inPurgeable can help avoid big Dalvik heap allocations (from API level 11 onward)
4    */
5   options.inPurgeable = true;
6   Bitmap bitmap = BitmapFactory.decodeByteArray(inputByteArray, 0, inputLength, options);

 

當心Bitmap空包彈

事實並不是那麼簡單,最後實際解出來Bitmap沒有像素數據(沒有到Ashmem分配任何空間),根本沒有去完成jpeg或者png解碼。此時的 Bitmap是個空包彈!它所作的只是把輸入的解碼前數據拷貝到了native內存,若是把這個Bitmap交給ImageView渲染就糟了,在 View.draw()時Bitmap會在主線程進行圖片解碼。

並且不要天真的覺得Bitmap解碼一次以後再屢次使用都不會引發二次解碼,在系統內存緊張時底層可能回收Ashmem裏這部份內存。回收後該Bitmap再次渲染時又將在主線程完成一次解碼。若是就這樣直接使用該機制,性能上無疑雪上加霜。

那麼怎樣才能避免這個隱形炸彈呢?還好SDK預留了一個C層方法AndroidBitmap_lockPixels。而lockPixels底層 完成的工做大體以下圖所示。第一步是prepareBitmap完成真正的數據解碼,在工做線程調用AndroidBitmap_lockPixels避 免了在主線程進行數據解碼;第二步是完成對分配出來的Ashmem空間的鎖定,這樣即便在系統內存緊張時,也不會回收Bitmap像素數據,避免屢次解 碼。

(點擊放大圖像)

天貓11.11:手機淘寶 521 性能優化項目揭祕

貌似解決了Bitmap渲染的全部問題,但在手淘中則否則。爲了兼容低版本系統以及提高webp解碼性能,咱們使用了本身的解碼庫libwebp.so,怎樣把它解碼出來的數據也存放到Ashmem呢?

libwebp借雞生蛋

若是自有解碼庫libwebp.so要解碼到Ashmem,經過SkBitmap、ashmem_create_region實現一套相似的機制 是不太現實的。一方面Skia庫的源碼編譯兼容會存在很大問題,另外一方面不少系統層面的核心接口並無對外。因此實現這點的關鍵仍是要藉助系統已經提供的 purgeable到Ashmem的機制,借雞生蛋,穩定性和成本上都能獲得保證:

  1. 依據圖片寬高生成空JPEG。
  2. 走系統解碼接口完成Ashmem Bitmap生成。
  3. 覆寫Pixel Data地址在libwebp

完成解碼。

更進一步,遷移解碼前數據

上面談到的內存遷移都是針對Decoded像素數據的,而Encoded圖像數據在解碼時會在Dalvik Heap保存一份,解碼完成後再釋放;Ashmem方式解碼時在底層又會拷貝一份到Native內存,這份數據直到整個Bitmap回收時才釋放。那可否 直接將網絡下載的Encoded數據存放到Native內存,省去Dalvik Heap上的開銷以及解碼時的內存拷貝呢?的確能夠,將網絡流數據直接轉移到 MemoryFile可實現,但遺憾的是真機測試中發現,小米及其餘國產「神機」(自改ROM),多線程使用MemoryFile獲取fd到BitmapFactory解碼, 會出現系統死機,懷疑是在併發狀況下系統代碼級別的死鎖形成。手機淘寶放棄了這種方案,改用ByteArrayPool複用池技術來減小Dalvik Heap針對Encoded Image的內存分配,效果也不錯。若是應用能接受單線程解碼,仍是MemoryFile方案更具優點。

是放手的時候了

上文提到Bitmap像素數據存放到Ashmem,有讀者可能擔憂數據回收問題,其實仍是由GC來觸發Ashmem內存的回收。在Dalvik層若是一個Bitmap已經不被任何地方引用,那麼在下一次GC時該Bitmap就會從Ashmem中回收,大體流程示意以下圖。

(點擊放大圖像)

天貓11.11:手機淘寶 521 性能優化項目揭祕

再看內存佔用

咱們再次在魅藍note1中dump出首頁滑動後的內存,以下圖能夠看出,原來byte數組(k)大量佔用已經不存在了,Bitmap(c)與BitmapDrawable(已不在前14名當中)的佔用也急驟降低。應用的整體內存降低近60%。

(點擊放大圖像)

天貓11.11:手機淘寶 521 性能優化項目揭祕

在雙11版本上,針對一些熱門機型在搜索結果頁不斷滾動使用,進行了不一樣版本的內存佔用對比分析,以下圖。能夠看出,除華爲3c和vivo這類系統內存偏小使用上一直受到控制、內存較爲緊張的外,大部分機型內存的降低幅度都達到45%以上。

(點擊放大圖像)

天貓11.11:手機淘寶 521 性能優化項目揭祕

撓走GC之癢

內存降低不是最終目的,最終要將GC對性能的影響降到最低。仍然以魅藍note1打開首頁後滑動到底的內存堆積圖來作對比。能夠看到舊版本內存佔 用上升趨勢至關明顯,一路帶有各式「毛刺」直奔70MB,每造成一個毛刺就意味一次GC。而雙11版本中,內存只在初期有上升,然後很快降低到21MB左 右,後期也顯得平滑得多,沒有那麼多的「毛刺」,就意味着GC發生的次數在明顯減小。

(點擊放大圖像)

天貓11.11:手機淘寶 521 性能優化項目揭祕

舊版本

(點擊放大圖像)

天貓11.11:手機淘寶 521 性能優化項目揭祕

雙11版本

同時使用一些熱門機型,針對雙十一版本在首頁不斷滑動,進行先後版本的GC_FOR_ALLOC次數對比。熱門機型GC次數降低了4~8倍,效果很是明顯。

天貓11.11:手機淘寶 521 性能優化項目揭祕

經過上文描述的各個優化方案,手機淘寶於雙十一前在大部分機型上達到了521目標-Android手機內存節省50%,啓動時間和頁面幀率提高20%,H5頁面實現1s法則。

從持續不斷的優化中,咱們也獲得了一套優化的經驗閉環,由觀察問題現象到分析緣由,創建監控,定下量化目標,執行優化方案,驗證結果數據再回到觀察新問題。每一次閉環只能解決部分問題,只有不斷抓住細微的優化點「啃」下去,才能獲得螺旋上升的良好結果。

固然,隨着手機機型的日益碎片化,程序功能的複雜化多樣化,性能調優是沒有止境的,在部分低端機和低內存手機上手淘性能問題依然不容樂觀。欲窮千里目,還需更上一層樓,接下來咱們還會努力經過更多更細緻的優化方案來達到「如絲般順滑」。

相關文章
相關標籤/搜索