深入理解 Flutter 圖片加載原理

作者:京東零售 徐宏偉
來源:京東云開發者社區

前言

隨著Flutter穩定版本逐步迭代更新,京東APP內部的Flutter業務也日益增多,Flutter開發為我們提供了高效的開發環境、優秀的跨平臺適配、豐富的功能組件及動畫、接近原生的交互體驗,但隨之也帶來了一些OOM問題,通過線上監控信息和Observatory工具結合分析我們發現問題的原因是由于Flutter頁面中加載的大量圖片導致的內存溢出,這也是在原生開發中常見的問題之一,Flutter官方為我們提供的Image widget實現圖片加載及顯示,只有了解Flutter中圖片的加載原理及圖片內存管理方式才能真正發現問題的本質,本文將重點介紹Flutter中圖片的加載原理,使用過程中有哪些需要注意的地方及優化思路和手段,希望能給大家帶來一些啟發和幫助。

基本使用

下面是 Image 的基本使用方法,image參數是 Image 控件中的必選參數,也是數據源類型可以是Asset、網絡、文件、內存,下面將以我們常用的網絡圖片加載方式為例子講解原理,基本使用如下:

Image(image: NetworkImage("https://avatars2.githubusercontent.com/u/20411648?s=460&v=4"),width: 100.0,heitht: 100.0)

圖片加載流程

Flutter 的圖片加載原理與原生客戶端中的圖片框架加載原理相似,具體可點擊下方大圖查看,加載步驟如下:

1、 區分數據來源生成緩存列表中數據映射的唯一key;
2、 通過key讀取緩存列表中的圖片數據;
3、 緩存存在,返回已存在的圖片數據;
4、 緩存不存在,按來源加載圖片數據,解碼后同步到緩存中并返回;
5、 設置回調監聽圖片數據加載狀態,數據加載完成后重新渲染控件顯示圖片;

大家可能注意到了上面流程圖中的文件緩存部分是灰色的,目前官方還不支持此功能,下面我們會通過源碼逐步分析加載流程及如何通過修改源碼補全文件緩存功能。

源碼分析

下面將通過流程圖結合UML類圖分析圖片加載流程:

這個UML類圖看起來稍微有點兒復雜,但仔細看會發現已將圖片數據加載流程分成幾大模塊,下面將按照模塊進行逐步分析,下面將以網絡圖片加載方式為例講解核心類和核心方法功能。

核心類及方法介紹

啟動緩存相關類

PaintingBinding:圖片緩存類和著色器預加載,該類是基于框架的應用程序啟動時綁定到Flutter引擎的膠水類,在啟動入口main.dart的runApp方法中創建WidgetsFlutterBinding類時被初始化的,通過覆蓋父類的initInstances()方法初始化內部的著色器預加載(Skia第一次在GPU上繪制需要編譯相應的著色器,這個過程大概20ms~200ms)及圖片緩存等,圖片緩存以單例的方式(PaintingBinding.instance.imageCache)對外提供方法使用,也就是說這個圖片緩存在APP中是全局的,并在這個類中還提供了圖像解碼(instantiateImageCodec)、緩存清除(evict)等功能。

ImageCache: 圖片緩存類,默認提供緩存最大個數限制1000個對象和最大容量限制100MB,由于圖片加載過程是一個異步操作,所以緩存的圖片分為三種狀態:已使用、已加載、未使用,分別對應三個圖片緩存列表,當圖片列表超限時會將圖片緩存列表中最近最少使用圖片進行刪除,緩存列表分別是:活躍中圖片緩存列表(_cache)、已加載圖片緩存列表(_pendingImages)、未活躍圖片緩存列表(_liveImages),并對外提供以下方法:獲取緩存(putIfAbsent)、清空緩存(clear、clearLiveImages)、驅逐單個圖片(evict)、最大緩存個數限制(maximumSize)、最大緩存大小限制(maximumSizeBytes)等方法。

從源碼中我們可以看到緩存列表是Map類型,Flutter中的Map創建的對象是LinkedHashMap是有序的,按鍵值插入順序迭代,Flutter使用LinkedHashMap存儲圖片數據并實現類似LRU算法的緩存,當緩存列表中的圖片被使用后會將圖片數據重新插入到緩存列表的末尾,這樣最近最少使用的圖片始終會被放在列表的頭部。

當緩存列表增加圖片數據后,會通過最大緩存個數和最大緩存大小兩個緯度進行檢查緩存列表是否超限,若存在超限情況則通過Map的keys.first方法獲取緩存列表頭部最近最少使用的圖片對象進行刪除,直到滿足緩存限制。

啟動緩存小結:

Flutter啟動后在PaintingBinding中創建ImageCache緩存,圖片緩存是全局的并以單例方式對外提供使用方法,緩存默認最大個數限制1000個對象、最大容量限制100MB,緩存中的Map列表通過key/value方式存儲圖片信息,并通過keys.first方法實現的類似LRU算法管理圖片緩存列表,對外提供putIfAbsent()方法獲取已緩存圖像,若緩存中不存在則通過回調圖片加載類中的load()方法加載圖片數據,另外圖片緩存中還提供clear()和evict()方法用來刪除緩存。

圖片數據加載相關類

ImageProvider: 圖片數據提供抽象類,該類定義了圖像數據解析方法(resolve)、唯一key生成方法(obtainKey)、數據加載方法(load),obtainKey 和load方法均由子類實現,obtainKey方法生成的對象用于內存緩存的key值使用,load方法將按照不同數據源加載圖像數據,常用的Provider子類有:NetworkImage、AssetImage、FileImage、MemoryImage,我們可以看到resolve方法返回的是圖片加載對象類(ImageStream),load方法返回的是ImageStreamCompleter類用來管理圖像加載狀態及圖像數據(ImageInfo)。

ImageStreamCompleter: 是一個抽象類,用于管理加載圖像對象(ImageInfo)加載過程的一些接口,Image控件中正是通過它來監聽圖片加載狀態的。

ImageStream: 圖像的加載對象,可監聽圖像數據加載狀態,由ImageStreamCompleter返回一個ImageInfo對象用于圖像顯示****

NetworkImage: 網絡圖片加載類,ImageProvider的實現類,通過URL加載網絡圖像,覆蓋load()方法返回ImageStreamCompleter的實現類MultiFrameImageStreamCompleter,構建該類需要一個codec參數類型是Future<ui.Codec>,通過調用_loadAsync()方法下載網絡圖片數據獲得字節流后通過調用PaintingBinding.instance.instantiateImageCodec方法對數據進行解碼后獲得Future<ui.Codec>對象,obtainKey方法我們發現返回的是SynchronousFuture(this)對象,正是NetworkImage 自己本身,我們通過該類的==方法可以看到判斷兩個NetworkImage類是否相等通過runtimeType 、url 、scale 這三個參數來判斷,所以圖片緩存中的key相等判斷取決于圖片的url、scale、runtimeType參數。

MultiFrameImageStreamCompleter: 是ImageStreamCompleter的子類是Flutter SDK的預置類,構建該類需要一個codec參數類型是Future<ui.Codec>,Codec 是處理圖像編解碼器的句柄也是Flutter Engine API的包裝類,可通過其內部的frameCount變量獲取圖像幀數,分別處理單幀和多幀(動態圖)圖像,內部的getNextFrame()方法獲取每幀的圖像數據并創建Image控件中渲染需要的ImageInfo數據,調用onImage方法將ImageInfo返回給Image控件。

圖像數據加載小結:

上面以網絡圖像加載流程分析,首先通過ImageProvider的resolve()方法創建ImageStream對象,obtainKey()方法創建圖像緩存列表中的唯一key(取決于圖像url和scale),通過load()方法加載圖像數據并返回MultiFrameImageStreamCompleter對象,并將其設置給ImageStream中的setCompleter()方法添加監聽圖像加載完成狀態,圖像數據通過Codec 處理幀數分別處理最終創建ImageInfo對象通過ImageStreamListener的onImage方法返回給Image控件。

圖片渲染相關類

_ImageState: 是Image控件創建的State類,通過調用ImageProvider的resolve()方法解析圖片數據,resolve()方法返回的ImageStream對象,通過addListener()增加圖片解析狀態監聽,通過ImageStreamListener的onImage回調中獲取圖片數據(ImageInfo)加載完成狀態,onChunk回調監聽數據加載進度,onError監聽圖片加載錯誤狀態,最終通過調用setState進行數據更新繪制。

細心的同學會發現ImageProvider的實例對象(widget.image)被ScrollAwareImageProvider包裝了一下又重新創建了一個provider,在ScrollAwareImageProvider內部主要是重寫了其中的resolveStreamForKey()方法,Flutter SDK 1.17版本中對圖片解析增加了快速滾動優化,當判斷當前屏幕處在快速滾動狀態時,則將圖片解析過程延遲下一幀幀尾進行。

RawImage: RenderObjectWidget的子類,重寫createRenderObject方法創建RenderObject子類。

RenderImage: 渲染樹中RenderObject的實現類,Flutter的三棵樹Widget、Element、RenderObject ,而RenderObject這是負責繪制渲染的,RenderImage重寫performLayout()方法度量渲染尺寸并布局,重寫paint()方法獲取畫布Canvas,Canvas是記錄圖片操作的接口類,通過參數處理圖片鏡像、裁剪、平鋪等邏輯后調用的drawImageNine()和drawImageRect()方法將圖片合成到畫布上最終調用Skia引擎API進行繪制。

圖片渲染小結:

Image控件中通過調用ImageProvider的resolve()方法獲取圖片數據ImageInfo對象,通過setState方法將數據更新給圖片渲染控件(RenderImage),RenderImage中重寫paint()方法根據傳入參數對圖片數據處理后繪制到Canvas畫布上并調用Skia引擎API進行繪制。

總結

以上是 Image 圖片加載原理及源碼分析,那么我們在翻閱了Image源碼后能做些什么呢?使用過程中有哪些可以優化的部分呢?讓我們繼續往下看。

圖片緩存池大小限制優化

Flutter本身提供了定制化Cache的能力,所以優化ImageCache的第一步就是要根據機型的實際物理內存去做緩存大小的適配,通過PaintingBinding.instance.imageCache調用的maximumSize和maximumSizeBytes動態設置合理的圖片緩存大小限制避免因圖片過多導致OOM。

未顯示圖像內存優化

可結合StatefulWidget控件生命周期中的deactive()、dispose()等方法,在頁面控件中的圖片未顯示在屏幕上或控件已銷毀時調用圖片緩存中的evict()方法進行資源釋放。

圖片預緩存處理

Image控件中提供了precacheImage()方法可以將需要顯示的圖片預先加載到ImageCache的緩存列表中,緩存列表中通過key值區分相同圖片,在頁面打開后直接從內存緩存獲取,可快速顯示圖片。

圖片文件緩存

通過查看網絡圖片加載類NetworkImage源碼可以發現,圖片數據下載和解碼過程都是通過_loadAsync()方法完成的,所以我們可以通過改造這個方法中圖片文件下載、讀取、保存過程去增加圖片文件本地存儲、獲取原生圖片庫緩存、圖片下載DNS處理等功能。

自定義占位圖、錯誤圖效果

Image控件中的frameBuilder和errorBuilder參數分別為我們提供了占位圖和錯誤圖的自定義方式,也可使用FadeInImage控件提供的占位圖(placeholder)、錯誤圖imageErrorBuilder等參數,FadeInImage內部實現也是Image控件,感興趣的同學可以查看其源碼實現。

大圖下載進度自定義顯示

圖片可拉伸區域設置(.9圖片)

RenderImage的paint方法中我們發現在調用Canvas API繪制前會判斷centerSlice參數分別調用drawImageNine()和drawImageRect()方法,Image正式通過centerSlice參數配置圖片的可拉伸區域,參考代碼:centerSlice: Rect.fromLTWH(20, 20, 1, 1),L:橫向可拉伸區域左邊起始點位置,T:縱向可拉伸區域上邊起始點位置,W:橫向可拉伸區域寬度,H:縱向可拉伸區域寬度。

未來規劃

本文介紹了京東APP中Flutter探索遇到的問題以及圖片的加載原理和使用過程中的一些技巧,隨著Flutter SDK版本迭代更新,我們將繼續對圖片加載框架進行優化,原生開發中的多個優秀圖片框架已經經歷了大量用戶的考驗這也一直是我們渴望在Flutter上復用的能力,所以我們也在積極探索原生和Flutter中圖片內存共享方案,我們希望這個增強能力是非侵入式的,我們也在嘗試外接紋理等方案,這塊技術細節進展將在后續文章中繼續和大家一起探討。

Android 學習筆錄

Android 性能優化篇:https://qr18.cn/FVlo89
Android 車載篇:https://qr18.cn/F05ZCM
Android 逆向安全學習筆記:https://qr18.cn/CQ5TcL
Android Framework底層原理篇:https://qr18.cn/AQpN4J
Android 音視頻篇:https://qr18.cn/Ei3VPD
Jetpack全家桶篇(內含Compose):https://qr18.cn/A0gajp
Kotlin 篇:https://qr18.cn/CdjtAF
Gradle 篇:https://qr18.cn/DzrmMB
OkHttp 源碼解析筆記:https://qr18.cn/Cw0pBD
Flutter 篇:https://qr18.cn/DIvKma
Android 八大知識體:https://qr18.cn/CyxarU
Android 核心筆記:https://qr21.cn/CaZQLo
Android 往年面試題錦:https://qr18.cn/CKV8OZ
2023年最新Android 面試題集:https://qr18.cn/CgxrRy
Android 車載開發崗位面試習題:https://qr18.cn/FTlyCJ
音視頻面試題錦:https://qr18.cn/AcV6Ap

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/40288.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/40288.shtml
英文地址,請注明出處:http://en.pswp.cn/news/40288.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

用對角線去遍歷矩陣

聲明 該系列文章僅僅展示個人的解題思路和分析過程&#xff0c;并非一定是優質題解&#xff0c;重要的是通過分析和解決問題能讓我們逐漸熟練和成長&#xff0c;從新手到大佬離不開一個磨練的過程&#xff0c;加油&#xff01; 原題鏈接 用對角線遍歷矩陣https://leetcode.c…

wsl2(debian)安裝python的不同版本例如3.8

要在Debian上安裝 Python 3.8&#xff0c;可以按照以下步驟操作&#xff1a; 1.確保你的 Debian 系統已經更新到最新版本&#xff0c;可以使用以下命令更新&#xff1a; sudo apt update sudo apt upgrade2.安裝 Python 3.8 的依賴項&#xff0c;以及構建 Python 時需要的工具…

django中實現事務的幾種方式

1.實現事務的三種方式 1.1 全局開啟事務---> 全局開啟事務&#xff0c;綁定的是http請求響應整個過程 DATABASES {default: {#全局開啟事務&#xff0c;綁定的是http請求響應整個過程ATOMIC_REQUESTS: True, }} from django.db import transaction# 局部禁用事務 transac…

數據結構——棧(C語言)

需求&#xff1a;無 棧的概念&#xff1a; 棧&#xff1a;一種特殊的線性表&#xff0c;其只允許在固定的一端進行插入和刪除元素操作。進行數據插入和刪除操作的一端稱為棧頂&#xff0c;另一端為棧底。棧中的數據元素遵守后進先出&#xff08;LIFO&#xff09;原則。壓棧&…

GPIO 配置 和 PINCTRL有啥區別

GPIO&#xff08;通用輸入/輸出&#xff09;和 PINCTRL&#xff08;引腳控制器&#xff09;是在嵌入式系統中用于管理和控制硬件引腳的關鍵概念。它們在硬件層面上起著不同的作用。 GPIO配置&#xff1a; GPIO 是一種通用的硬件接口&#xff0c;用于控制和讀取數字信號。每個 …

自動駕駛——駛向未來的革命性技術

自動駕駛——駛向未來的革命性技術 自動駕駛的組件自動駕駛的優勢自動駕駛的應用自動駕駛的未來中國的自動駕駛 自動駕駛是一種技術&#xff0c;它允許車輛在沒有人類駕駛員的情況下自主地進行行駛。它利用各種傳感器、計算機視覺、人工智能和機器學習算法來感知和理解周圍環境…

.net連接mysql,提示找不到請求的 .Net Framework Data Provider。可能沒有安裝

開發完成的.net程序需要連接mysql數據庫&#xff0c;在個人電腦上運行沒問題&#xff0c;別人運行時提示“提示找不到請求的 .Net Framework Data Provider。可能沒有安裝”。經過查詢&#xff0c;安裝Connector/NET 8.1.0&#xff0c;下載地址如下所示&#xff1a; https://d…

Linux touch 命令指南大全

1. 概述 在本教程中,我們將學習touch命令。簡而言之,這個命令允許我們更新文件或目錄的最后修改時間和最后訪問時間。 因此,我們將重點關注如何使用該命令及其各種選項。 請注意,我們使用 Bash 測試了此處顯示的所有命令;但是,它們應該與任何兼容 POSIX 的 shell 一起使…

使用騰訊云輕量服務器Matomo應用模板建網站流量統計系統

騰訊云百科分享使用騰訊云輕量應用服務器Matomo應用模板搭建網站流量統計系統&#xff0c;Matomo 是一款開源的網站數據統計軟件&#xff0c;可以用于跟蹤、分析您的網站的流量&#xff0c;同時充分保障數據安全性、隱私性。該鏡像基于 CentOS 7.6 64位操作系統&#xff0c;已預…

postgresql字段被截斷問題

前言 最近遇到一個問題就是字段名過長&#xff0c;會被pg給截斷&#xff0c;導致原始字段和下游用的的字段不一樣&#xff0c;就會報錯。當然&#xff0c;小伙伴可能會說為什么會用那么長的字段名&#xff0c;每個應用程序里面處理不一樣&#xff0c;我們數據字段每次被使用就…

06-加密算法

加密算法 一、前言知識1、加密解密2、MD5&#xff08;最常見&#xff09;3、SHA4、進制5、時間戳6、URL編碼7、base64編碼8、unescape編碼9、AES加密10、DES&#xff08;類似于base64&#xff09; 二、常見加密形式算法解析三、演示案例1、某 CTF 比賽題目解析2、某 CMS 密碼加…

爆肝整理,Python自動化測試-Pytest參數化實戰封裝,一篇打通...

目錄&#xff1a;導讀 前言一、Python編程入門到精通二、接口自動化項目實戰三、Web自動化項目實戰四、App自動化項目實戰五、一線大廠簡歷六、測試開發DevOps體系七、常用自動化測試工具八、JMeter性能測試九、總結&#xff08;尾部小驚喜&#xff09; 前言 參數化&#xff1…

uniapp案例30余種實戰項目

uniapp案例30余種實戰項目 mpvue框架仿滴滴出行didi-masteruni-app自定義導航欄title-customvue-mpvue-ChatRobot聊天機器人vue-mpvue-ChatRobot-master一款播課類小程序, 基于 mpvue 構建mp-podcast-mpvue-mastermpVue高仿美團小程序教程mpvue-meituan-masteruni-app 二維碼生…

【RS485 - 為什么要接收端計算時間偏移量】

我以前一直以為計算機等的信號傳輸速率都是非常快的&#xff0c;不用計算時間差。 然而在實際應用中發現信息是需要傳輸時間的&#xff0c;而這些時間somehow是可以計算的。 前提信息 波特率 9600&#xff1b; 控制器和執行器通過RS485通信&#xff1b; 控制器發出同步的命令…

spring框架,以及和spring框架相關的Java面試題和spring ioc的注入方式

目錄 一.spring來源&#xff0c;以及介紹 1.spring誕生的背景 2.spring框架 介紹 3.spring框架在使用中的優點以及不足 3.1優點 3.2不足 3.3總結 4.為什么要使用spring 二.將spring框架部署在IDEA中 1.替換pom.xml 2.構建spring所需要的xml文件 三.spring的三種注入…

網絡通信原理IP頭部格式(第四十二課)

字段作用解析:1)版本: 指的IP地址的版本 (IPv4 或 IPV6)2)首部長度: 次數據包的首部長度一共是多少,沒有加可選項3)優先級與服務類型:表示****數據包是否需要優選傳遞4)總長度: 表示的是整個數據包的大小,也就****是首部+數據5)標識符、標志、段偏移量:的作用將拆開的…

無涯教程-Perl - syswrite函數

描述 此函數嘗試將SCALAR中的LENGTH個字節寫入與FILEHANDLE相關的文件。如果指定了OFFSET,則從提供的SCALAR中的OFFSET字節中讀取信息。該函數使用C /操作系統的write()函數,該函數繞過普通緩沖。 語法 以下是此函數的簡單語法- syswrite FILEHANDLE, SCALAR, LENGTH, OFFS…

draw.io導出矢量圖到word報錯text is not svg - cannot display

先參考https://blog.csdn.net/a625750076/article/details/126384831 如果不行&#xff0c;可能是轉存的問題 解決方法&#xff1a;直接在draw.io上操作 第一步 第二步 然后再word中粘貼&#xff0c;依舊是矢量圖哦&#xff01;

Ajax入門+aixos+HTTP協議

一.Ajax入門 概念:AJAX是瀏覽器與服務器進行數據通信的技術 axios使用: 引入axios.js使用axios函數:傳入配置對象,再用.then回調函數接受結果,并做后續處理 <!DOCTYPE html> <html><head><meta charset"utf-8"><title>01.axios使用…

面試題. 零矩陣

編寫一種算法&#xff0c;若M N矩陣中某個元素為0&#xff0c;則將其所在的行與列清零。 示例 1&#xff1a; 輸入&#xff1a; [[1,1,1],[1,0,1],[1,1,1] ] 輸出&#xff1a; [[1,0,1],[0,0,0],[1,0,1] ] 示例 2&#xff1a; 輸入&#xff1a; [[0,1,2,0],[3,4,5,2],[1,3…