veImageX演進之路:iOS高效能圖片載入SDK

陶然陶然發表於2023-04-24

  1. SDK簡介

  圖片在業務應用場景是一個常見的元素,veImageX(簡稱ImageX)為業務提供了靈活、高效的一站式圖片處理解決方案,包括了服務端 SDK、上傳 SDK 和客戶端圖片載入 SDK。本文就來介紹下 iOS 客戶端圖片載入 SDK(下文中簡稱 SDK),SDK 主要提供圖片網路載入、影像解碼、圖片基礎處理與變換以及圖片服務質量監控上報等能力。

  1.1 業內主流開源圖片載入 SDK

  在介紹 veImageX 圖片載入 SDK 之前先看看業內目前有哪些主流的圖片載入 SDK,veImageX 圖片載入 SDK 是使用 Objective-C 語言開發的,業內使用 Objective-C 語言實現的主流開源圖片載入 SDK 有 YYWebImage,SDWebImage 等

  YYWebImage:一個非同步圖片載入框架(YYKit 的一個元件)。它是作為 SDWebImage、PINRemoteImage 和 FLAnimatedImage 的改進替代品而建立的。它使用 YYCache 支援記憶體和磁碟快取,使用 YYImage 支援 WebP/APNG/GIF 圖片解碼,但可惜的是此優秀的框架於 2017 年左右已停止更新;

  SDWebImage:目前使用較廣泛的一個圖片處理框架,可以非同步載入網路圖片,並支援圖片本地快取等特性,也是一款優秀的圖片載入框架。

  1.2 veImaegX 的 SDK 優勢

  veImageX 圖片載入 SDK 也是借鑑各家之所長,基於一些業務實際線上應用的屬性自研了一套圖片載入 SDK,相比於這些開源圖片載入 SDK,主要有以下特性:

  採用分層與模組化架構設計,根據業務需要選擇相應功能模組,最大程度精簡包大小;

  支援 WebP、AVIF、HEIF 這種高壓縮率圖片格式,特別是在自研的高效能HEIF軟體解碼庫支援下,能夠高效解碼 HEIF 格式,並擺脫 HEIF 原生 iOS 系統版本的限制;

  支援雲端加密、客戶端解密,保障圖片隱私安全;

  SDK 的網路庫支援 HTTPDNS,可以高效防止內容劫持及域名劫持,能夠有效降低圖片解碼失敗率,提升客戶端圖片載入體驗;

  支援採集各項圖片相關資料並上報,配合 veImageX 控制檯實時大盤資料檢視,可以為業務的運營及產品的體驗提升提供全面的從資料發現、資料分析、資料監控、資料診斷、資料追蹤等全鏈路支援。

  2. SDK 架構

  隨著時間的推移,SDK 的功能越來越多,各種業務對 SDK 的功能選擇也開始多樣化起來,特別是在 App 包體積日益增長需要降低的大背景下,SDK 也需要做包體積瘦身,面對以上種種問題,SDK 對功能的模組化/外掛化能力的要求也越來越高,SDK 的架構也就隨之演變成下圖的樣子。  

  SDK 主要分為三層

  介面層,也是最上層,這一層提供圖片載入與處理的各種介面,介面設計與主流開源圖片載入 SDK 保持一致,在這一層提供介面卡,提供了開源圖片載入 SDK(如 YYWebImage,SDWebImage 等)的適配層,方便業務快速上手與無縫切換;

  管理層,作為中間層負責各種模組的互動管理,也包括雲控配置管理和授權管理等;

  模組層,這一層包含了圖片載入流程的各個模組:下載模組,快取模組,解碼模組,日誌上報模組等,業務可以根據自身需求來選擇性依賴這些模組的各種功能,達到最小化依賴的原則。

  3. UIImageView 如何透過 SDK 渲染出一張網路圖片

  業務上圖片的主流應用場景就是載入網路圖片,以 iOS 原生系統控制元件 UIImageView 為例,透過 SDK 載入一張網路圖片的完整流程如下:

  發起圖片請求 -> 查詢記憶體快取 -> 查詢磁碟快取 -> 加入下載佇列 -> 開始下載 -> 獲取到服務端圖片未解碼資料 -> 從圖片未解碼資料中解碼後得到可以渲染的圖片 -> 將解碼後的圖片和圖片未解碼資料分別快取進記憶體和磁碟 -> UIImageView 渲染解碼後的圖片,至此,一張網路圖片被成功載入並展示給使用者。  

  4. SDK 模組介紹

  在瞭解完 SDK 的主流場景中網路圖片的完整載入流程後,下面分別介紹一下 SDK 載入流程中的下載、快取、解碼、日誌上報與圖片後處理這五大主要模組。

  4.1 下載模組

  下載模組的主要任務是透過網路庫把網路圖片從服務端下載到客戶端,這個過程對圖片載入來講是非常重要的一環,下載的成功與否直接決定了圖片能否正確展示,而網路庫的效能也決定了圖片下載的快慢,最終反映到使用者的感受體驗上。所以,下載模組中的下載任務除了支援蘋果原生系統的網路庫實現外,也支援位元組內部強大的自研網路庫 TTNetwork 實現,該庫不僅做了一些網路相關最佳化,例如 HTTPDNS,HTTP2+HTTPS 連線複用最佳化、鏈路選擇、動態策略等,支援最新的網路協議 QUIC,也提供了更為細粒度的網路監控,為 SDK 的圖片下載提供了高效的支援。SDK 預設支援原生網路庫與自研網路庫,如果業務有自己的網路庫,也可以透過外掛化的形式整合進來。  

  業務上一般會併發下載多張圖片,在 Feed 流場景中如果使用者來回滑動圖片,同樣的圖片會發生多次請求,如果相同圖片的多個請求都去反覆下載圖片,這樣顯然會浪費使用者流量,也會增加頻寬成本。SDK 會管理這些併發的下載任務,並標記相同的圖片請求,避免這種問題的發生。下載任務的管理與排程透過 iOS 系統原生的 NSOperation 與 NSOperationQueue 實現,同時會根據請求引數生成一個 Identifier,用來唯一標識一個下載任務,交由下載管理器去管理,這樣就能避免在同一個時間段內重複多次下載相同的圖片。  

  4.2 快取模組

  快取模組由記憶體和磁碟共同組成一個二級快取結構,當一張圖片被下載到客戶端上時,會被快取進記憶體和磁碟快取,如果 App 生命週期內再請求這張圖片,則可以從記憶體快取中查到,如果冷啟動 App 後再請求這張圖片,則可以從磁碟快取中查到。這樣不僅可以加快圖片的載入速度,提升使用者體驗,也可以降低使用者流量,節省頻寬成本。再對快取加上過期時間限制,就可以解決圖片的時效性問題。

  記憶體快取方面除了支援 iOS 原生的 NSCache 外,還支援 Strong-Weak 的弱引用快取,當快取物件無人持有時會被及時釋放掉,降低記憶體佔用,同時也支援 LRU 快取。在收到記憶體不足的通知時會主動釋放記憶體,緩解記憶體壓力,同時保證執行緒安全。磁碟快取方面除了支援最基本的 iOS 系統檔案管理 NSFileManager,還支援 LRU 快取,同時保證執行緒安全。

  整體看,如果 App 內只使用同一種固定的快取演算法的話,由於圖片使用場景各不相同,同一種快取演算法無法滿足所有場景,快取命中率就會偏低。除了 SDK 預設支援的快取演算法外,由於記憶體和磁碟快取都是由協議定義的,業務也可以根據需求去自定義快取,在不同場景下使用不同的快取演算法,這樣可以極大的提高快取命中率。在一些業務特定場景上 SDK 的快取命中率能夠達到 80% 左右,隨著快取命中率的提升,帶來的頻寬成本節省收益也越大。  

  4.3 解碼模組

  圖片下載到客戶端上後都是未經過解碼前的資料,想要把圖片正確展示給使用者,就必須對它進行解碼。圖片解碼上支援透過 iOS 原生系統的解碼框架 ImageIO 進行解碼,即蘋果原生能支援的格式,SDK 也能支援。除此之外,像 WebP、AVIF、VVIC(位元組基於 BVC 演算法自研的圖片格式)等原生不支援的圖片格式,SDK 透過自研解碼器或者開源解碼器的支援,也都能解碼這些格式的圖片。當有新格式的圖片要支援時,只需實現對應格式的動靜圖協議就能以外掛化的形式整合進 SDK,達到支援新格式圖片的目的。

  4.3.1 SDK 特色能力:iOS 全系統支援 HEIF

  HEIF 這種高壓縮率格式的圖片在位元組跳動公司內部的應用已經比較成熟了。頻寬節省方面,相比 WebP,在同質量下還能再節約 30% 的頻寬成本,為公司節省了大量的頻寬成本。載入最佳化方面,HEIF 支援漸進式載入,可以先載入 HEIF 縮圖,再載入 HEIF 原圖,在網路質量不好的場景下也能有不錯的圖片載入體驗。SDK 有了公司內部自研的高效能 HEIF 軟體解碼庫的支援,讓 HEIF 格式圖片的解碼支援擺脫了 iOS 系統的限制,不再侷限在 iOS 11 及以上才能使用 HEIF 靜圖,iOS 13 及以上才能使用 HEIF 動圖,在低版本 iOS 上也能支援 HEIF 動靜圖,極大的提升了 HEIF 的應用範圍,收穫了大量的頻寬成本節省收益。  

  4.4 圖片後處理模組

  在圖片載入完後,業務也可以根據需要再次對圖片進行各種實時轉換,比如說加圓角、超分等,這些都是透過圖片後處理來完成。下面介紹下 SDK 的一個特色能力:超分。

  4.4.1 SDK 特色能力:超分

  超分,即超解析度,指的是基於機器學習/深度學習方法,從給定的低解析度圖片中恢復高解析度的圖片,藉助圖片後處理,可以在移動端上做到圖片實時超分。

  一般可以用於兩種場景,一是用於提升使用者體驗,當原圖片解析度低、清晰度低時,對其進行超分後,可以用來提升清晰度,以達到提升使用者觀看體驗的目的;二是用於降檔超分,使用者在請求高解析度的圖片時,可以在傳輸過程中降低圖片的解析度,然後在客戶端上進行超分,提升到原請求的解析度,以達到節省頻寬成本的目的。  

  4.5 日誌上報模組

  SDK 包含了三大日誌模組,圖片效能日誌、使用者感知日誌,大圖監控日誌,為業務的運營及產品的體驗提升提供了全面的資料支援。配合火山引擎 veImageX 的控制檯,可以實時檢視各項視覺化大盤資料,全方位的監控圖片的各項指標。

  其中,圖片效能日誌包括了圖片 URL、下載耗時、解碼耗時、錯誤碼、圖片來源等資料,用來監控圖片各項效能指標;使用者感知日誌包括了圖片 URL、ImageView 的 Size、ImageView 展示圖片耗時等資料,用來監控使用者體驗各項指標;大圖監控日誌則包含了大圖 URL、記憶體佔用大小、圖片檔案體積、圖片解析度大小等資料,可以全面的監控異常大圖情況。  

  5. 演進:效能最佳化

  SDK 致力於極致的圖片載入使用者體驗,為此,SDK 做了很多相關效能最佳化,下面主要介紹下 SDK 如何提升圖片載入體驗、降低記憶體佔用、最佳化動圖播放。

  5.1 提升圖片載入體驗

  圖片載入的快慢直接影響到使用者的使用體驗,高效的圖片載入是 SDK 不可或缺的能力。

  漸進式載入

  載入靜圖大圖,或者載入多幀數動圖,亦或者在弱網場景下,都可以開啟圖片漸進式載入來提升圖片的載入體驗。

  SDK 支援傳統的 PNG、JPEG 靜圖漸進式載入,也支援HEIF靜圖漸進式載入,先載入 HEIF 縮圖,再載入HEIF原圖。SDK 同時也支援動圖的漸進式載入,動圖可以邊下載邊播放,在正常網路下,可以提高首幀的載入速度,在弱網下,類似於影片播放的緩衝機制,也可以提升動圖的播放體驗。

  Force Decode

  在圖片解碼方面,SDK 支援 Force Decode,能夠提前把 Bitmap Buffer 轉移到渲染程式,減少了未來渲染時再去複製的耗時,如果原始解碼出來的 Bitmap Buffer,iOS 硬體螢幕不直接支援,會提前轉換好,避免渲染時在主執行緒的轉換開銷,提高圖片的載入幀率。

  5.2 優雅的記憶體控制

  通常情況下,App 內圖片的場景還是很多的,當載入大量圖片時,圖片所佔的記憶體可能會很大,如果記憶體佔用過高,會帶來 OOM 問題,給使用者的感受跟 Crash 一樣,都是應用突然閃退。

  SDK 有如下的幾種方案來降低圖片記憶體佔用:

  釋放記憶體快取

  當系統記憶體緊張,收到記憶體不足通知時,快取模組會及時釋放記憶體快取,同時也提供介面,由業務在適當時機主動釋放記憶體快取。

  全域性圖片降取樣

  圖片在記憶體中的佔用大小可以簡單用如下公式來估算:

  memoryCost(單位:位元組)= imageWidth(單位:畫素)* imageHeight(單位:畫素)* 4

  由公式可以看出,如果想要降低記憶體,那麼就要想辦法在不影響功能和體驗的前提下儘量降低圖片的寬高,由此,當不能明確下載後的圖片大小是否會遠大於需要展示的 ImageView 的大小時,可以使用全域性圖片降取樣功能。全域性圖片降取樣分為以尺寸大小限制進行降取樣和以記憶體大小限制進行降取樣。

  以尺寸大小限制進行降取樣:

  如果當前圖片的長寬都大於降取樣的長寬,那麼把原圖片長寬等比例縮放到恰好能貼到降取樣尺寸的輪廓  

  以記憶體大小限制進行降取樣:

  如果當前圖片的記憶體佔用超過記憶體限制,那麼把原圖片長寬等比例縮放到恰好低於記憶體限額  

  禁止圖片渲染

  每次需要渲染前,都會給業務回撥當前圖片的元資訊,例如圖片的長寬尺寸、動圖的幀數、以及預估的記憶體消耗量,業務可以根據此資訊來禁止不符合預期的超大圖渲染。

  大圖監控

  實際業務場景中,待展示圖片的解析度和幀數都是未知的。在一些極端情況下(線上真實案例),某個動圖解析度是 1080p、幀數上百幀,是使用者錄屏生成的,是個超大的動圖,解碼後有超過 1 個 GB的記憶體佔用,在一些低端機上就直接 OOM了。對於這類 OOM 情況,很難根據常規方法排查。那怎麼有效監控這種不符合預期的線上大圖呢,SDK 透過圖片展示尺寸,圖片解碼後記憶體佔用大小和圖片檔案體積這三個維度來定義一個大圖,當一張圖片觸發這三個維度中任意一個維度的閾值限制時,就會被記錄到大圖監控日誌內,這些資料後續會被上報。業務透過 veImageX 控制檯就可以看到大圖監控這個指標下的詳細資料,當發現記憶體佔用大小這個值異常大後,就可以及時查到相應的圖片 URL,然後結合實際業務場景,及時下線這種不符合預期的超大圖,降低線上 OOM 率。

  5.3 動圖播放的最佳化

  動圖在業務上也是一個常見應用場景,如果能做好動圖的最佳化,也可以帶來使用者體驗的提升。動圖在播放時,會不斷解碼每一幀圖片,這時會大量消耗 CPU 資源,SDK 內部會計算當前可用的記憶體以及渲染動圖的所有幀需要的記憶體,如果當前可用記憶體滿足渲染動圖所有幀需要的記憶體時,SDK 會快取動圖的所有幀,以此來節省 CPU 資源,如果當前可用記憶體不滿足渲染動圖所有幀需要的記憶體時,SDK 會在每一幀圖片播放結束之後捨棄前一幀,也就是不斷重複渲染下一幀圖片,透過消耗 CPU 資源節約記憶體,達到 CPU 消耗與記憶體節省的一個平衡。

  6. 寫在最後

  業內雖然已經有很多很成熟的圖片載入 SDK 了,但要契合公司自己業務發展的 SDK 也很重要,圖片載入 SDK 作為 veImageX 整體產品端到端不可或缺的一環,也是在這種背景下應運而生了。除了一些效能最佳化外,在成本節省上,HEIF 格式的應用為公司節省了大量頻寬成本,收益非常可觀,並且也在持續嘗試新的壓縮率更高的圖片格式,例如 VVIC。在前沿能力應用上,隨著圖片超分演算法的不斷迭代最佳化,相信在未來也能帶來不錯的體驗上的提升和成本上的節省。

來自 “ 位元組跳動技術團隊 ”, 原文作者:周旋;原文連結:http://server.it168.com/a2023/0424/6800/000006800713.shtml,如有侵權,請聯絡管理員刪除。

相關文章