veImageX 演進之路:iOS 高性能圖片加載 SDK
1. SDK簡介
圖片在業務應用場景是一個常見的元素,veImageX(簡稱ImageX)為業務提供了靈活、高效的一站式圖片處理解決方案,包括了服務端 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。在前沿能力應用上,隨著圖片超分算法的不斷迭代優化,相信在未來也能帶來不錯的體驗上的提升和成本上的節省。