iT邦幫忙

2025 iThome 鐵人賽

DAY 22
0
Mobile Development

Android 性能戰爭:從 Profiler 開始的 30 天優化實錄系列 第 22

# Day 22:【資源戰爭】圖片的最適化載入 (Image Loading)

  • 分享至 

  • xImage
  •  

各位戰士,歡迎來到第二十二天的戰場。從今天起,我們戰爭的重心將從「前線作戰」轉向「後勤管理」。一支出色的軍隊,不僅要贏得眼前的戰鬥,更要懂得如何高效地利用資源,以應對漫長的持久戰。如果應用程式耗盡了使用者的記憶體、數據流量或手機電量,即便它啟動再快、滑動再流暢,也終將被使用者拋棄。

在這場資源戰爭中,我們的第一個目標,就是應用程式中最大的資源消耗單位——圖片。一張未經優化的圖片,就像一輛油耗驚人的重型坦克,會迅速耗盡我們的寶貴補給。

今天的任務,就是學習如何使用現代化的武器(如 Glide 和 Coil),對圖片資源進行最適化管理,確保每一次載入都是高效且節制的。


選擇你的武器:Glide vs. Coil

在現代 Android 開發中,我們早已不再手動處理 Bitmap。主流的圖片載入函式庫為我們處理了所有複雜的工作。目前最主流的兩個選擇是:

  • Glide: 身經百戰的沙場老將。功能極其豐富,穩定性高,擁有龐大的社群和詳細的文件。
  • Coil (Coroutine Image Loader): 為現代化戰爭而生的新貴。它是一個 Kotlin-First 的函式庫,完全基於協程構建,體積輕量,並且與 Jetpack Compose 整合得天衣無縫。

結論:兩者都是頂級的武器。如果你的專案是 Kotlin-First,特別是使用了 Compose,Coil 通常是更自然的首選。對於其他專案,Glide 依然是無比可靠的選擇。今天我們討論的優化原則,對兩者都完全適用。


戰術一:精準打擊 —— 載入正確尺寸的圖片

這是最常見,也是最致命的錯誤:將一張 4000x3000 像素的超高解析度原圖,直接載入到一個 100dp x 100dp 的頭像 ImageView 中。

為何這是災難性的?

  1. 記憶體爆炸:Android 系統需要將整張 4000x3000 的圖片解碼到記憶體中,它所佔用的記憶體約為 4000 * 3000 * 4 bytes ≈ 48MB!僅僅一張圖片就可能耗盡大量記憶體,引發 OutOfMemoryError
  2. 流量浪費:使用者被迫下載一張幾 MB 大小的圖片,而他們在螢幕上看到的只是一個小小的縮圖,造成了大量的數據流量浪費。
  3. 性能損耗:解碼如此巨大的圖片需要消耗大量的 CPU 時間,這個過程如果發生在 RecyclerView 的滑動中,幾乎必然會導致 Jank。

解決方案:永遠不要載入超過目標 ImageView 尺寸的圖片。讓圖片載入函式庫為你處理圖片的「縮放 (Resizing)」。

函式庫非常聰明,只要你的 ImageView 在 XML 中有明確的尺寸(例如 100dp),它們會自動等待 ImageView 測量完成後,只解碼出一個接近 100dp 對應像素大小的 Bitmap

在特定情況下,你也可以手動指定尺寸:

// Glide
Glide.with(context)
    .load(imageUrl)
    .override(300, 300) // 強制將圖片解碼為 300x300 像素
    .into(imageView)

// Coil
imageView.load(imageUrl) {
    size(300, 300) // 強制將圖片解碼為 300x300 像素
}

軍規:用多大的碗,就盛多大的飯。絕不用炮兵陣地去打一個蒼蠅。

戰術二:更新彈藥 —— 使用現代化的圖片格式 (WebP/AVIF)

JPEG 和 PNG 是傳統戰爭中的標準彈藥,但我們現在有了更先進的選擇。

  • WebP: 由 Google 開發,是目前 Android 上的最佳通用格式。它在提供與 JPEG 相同圖片品質的情況下,檔案體積能小 25-35%。它還支援透明度,可以完美取代 PNG。Android 4.3+ 已原生支援。

  • AVIF: 最新一代的圖片格式,壓縮率比 WebP 更高,在同等畫質下體積能再小 20-30%。Android 12+ 已原生支援。

作戰策略:這需要與你的後端部隊協同作戰。強烈建議後端伺服器在儲存和提供圖片時,優先使用 WebP 格式。這能極大地減少網路傳輸時間和使用者流量消耗。對於圖片載入函式庫來說,它們原生就支援這些格式的解碼,你無需做任何額外工作。

戰術三:建立補給站 —— 善用快取策略

每一次都從網路下載圖片,就像每次打仗都從首都運糧草一樣愚蠢。Glide 和 Coil 都內建了強大的多級快取系統。

  1. 記憶體快取 (RAM):第一級快取,速度最快。它將已經解碼好的、可以直接顯示的 Bitmap 物件儲存在 RAM 中。非常適合 RecyclerView 中快速重複顯示的圖片。應用程式被殺死後,快取失效。

  2. 磁碟快取 (Disk):第二級快取,速度慢於記憶體但快於網路,並且是持久化的。它將從網路下載的原始圖片檔案儲存在手機的儲存空間裡。當請求一張圖片時,函式庫會:

  • 檢查記憶體快取 -> 命中則直接使用。

  • 未命中 -> 檢查磁碟快取 -> 命中則讀取檔案進行解碼,並放入記憶體快取。

  • 未命中 -> 發起網路請求下載圖片,存入磁碟快取,解碼後放入記憶體快取,最後顯示。

作戰策略:絕大多數情況下,函式庫的預設快取策略(DiskCacheStrategy.AUTOMATIC in Glide, CachePolicy.ENABLED in Coil)就是最佳選擇。你需要了解這個機制,並在特殊情況下(例如需要顯示一張即時更新、絕不能快取的驗證碼圖片)知道如何去調整它。

// Glide - 禁用所有快取
Glide.with(context)
    .load(imageUrl)
    .diskCacheStrategy(DiskCacheStrategy.NONE)
    .skipMemoryCache(true)
    .into(imageView)

// Coil - 禁用所有快取
imageView.load(imageUrl) {
    memoryCachePolicy(CachePolicy.DISABLED)
    diskCachePolicy(CachePolicy.DISABLED)
}

今日總結

今天,我們打響了資源戰爭的第一槍,學習了圖片最適化載入的三大核心戰術:

  1. 尺寸最適化:根據 ImageView 的大小載入圖片,避免巨大的記憶體和 CPU 浪費。

  2. 格式現代化:協同後端使用 WebP 或 AVIF 格式,節省寶貴的網路流量。

  3. 快取智慧化:充分利用函式庫的記憶體與磁碟快取,避免不必要的網路請求。

管理好圖片資源,是打贏持久戰的第一步。明天,我們將把目光從單個資源轉向整個軍隊的行囊——APK 本身。我們將學習【APK 瘦身術】,為我們的應用程式進行一次徹底的減負。

我們明天見!


上一篇
# Day 21:【流暢度戰爭】主執行緒的守護者:嚴禁 I/O 操作
系列文
Android 性能戰爭:從 Profiler 開始的 30 天優化實錄22
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言