外接原生圖片庫,共享本地文件緩存、內存緩存。git
圖片請求取消功能,解決網絡併發限制引發的排隊加載緩慢,以及無效的解碼、紋理上傳形成資源浪費的狀況。github
圖片解碼併發管理,下降 CPU、內存峯值。緩存
支持 GIF,在播放 GIF 時逐幀上傳紋理,下降內存佔用。安全
簡單易用的 Placeholder。bash
容許將 Flutter 內置的各類圖片解碼庫剝離,減少包大小。網絡
業務無感的方式解決 List 滾動時,大 Cell 中的圖片不能動態加載、回收的問題。解決 Native、Weex 體系下的頑疾。併發
Image.networkapp
Image.file異步
Image.asset
這些方法建立了背後不一樣的 ImageProvider。當 Widget 構建並更新 State 時,調用相應的 ImageProvider 進行解析。ImageProvider 返回一個 ImageStream 對象,並讓這些 Stream 對象共同監聽一個 ImageStreamCompleter。與此同時,ImageProvider 爲這個 Completer 提供不一樣的 load 方法加載來自網絡、文件或資源中的圖片數據(未解碼)。當數據加載好後,調用 Engine 的 instantiateImageCodec 方法建立 C++ Codec(ui.Codec) 對象。由 Codec 負責解碼,上傳 GPU 紋理,生成 ui.Image。所有完成後,回調 Completer,以 Provider 做爲 Key 將 Completer 加入緩存,並通知 Widget 重繪。async
// File: lib/src/widgets/image.dart
Image.external_adapter(
String src, {
Key key,
....
int targetWidth, // 請求的圖片的寬
int targetHeight, // 請求的圖片的高
Map<String, String> parameters, // 透傳給圖片庫的參數
Map<String, String> extraInfo,
ImageProvider placeholderProvider, // placeholder 能夠指定爲其它 Provider
}) : image = ExternalAdapterImage(src, // 建立自定義的 ExternalAdapterImage Provider
targetWidth: targetWidth, targetHeight: targetHeight,
placeholderProvider: placeholderProvider,
parameters: parameters, extraInfo: extraInfo),
super(key: key);複製代碼
這個方法中的 placeholderProvider 提供了更簡單直觀的方式爲圖片指定 placeholder。例如
// 使用本地資源做爲 placeholder
Image.external_adapter(
'https://gw.alicdn.com/tfs/TB1Aa0UcF67gK0jSZPfXXahhFXa-750-140.png',
placeholderProvider: AssetImage("assets/placeholder.jpg"),
)
// 使用另外一個網絡資源做爲 placeholder
Image.external_adapter(
'https://gw.alicdn.com/tfs/TB1Aa0UcF67gK0jSZPfXXahhFXa-750-140.png',
placeholderProvider: ExternalAdapterImage("https://alicdn.com/image1024.jpg"),
)複製代碼
處理 placeholderProvider,在主圖返回前,讓 Image Widget 顯示 placeholder 圖片。
建立 C++ 層 ExternalAdapterImageFrameCodec 對象,調用 getNextFrame 獲取圖片信息(是否爲動圖、幀數、播放時間),以及紋理對象 ui.Image 並通知 Widget 顯示。
對於 GIF 等多幀圖片,循環調用 ExternalAdapterImageFrameCodec 對象的 getNextMultiframe 接口獲取動圖的每一幀 ui.Image 並通知 Widget 顯示。
當無監聽者時,調用 ExternalAdapterImageFrameCodec 的 cancel 接口取消圖片任務。
void request``(requestId, requestInfo, callback(platformImage, releaseFunc))
該方法向圖片庫請求圖片,圖片庫完成後,經過 callback 異步返回。platformImage 封裝平臺層的圖片對象(如 UIImage),callback 同時返回一個 releaseFunc,Flutter 使用完成該圖片後,調用該方法釋放圖片。
void cancel(requestId)
通知圖片庫取消某個請求
Bitmap decode(platformImage, frameIndex)
解碼圖片的某一幀,並返回 Bitmap 數據。
evaluateDeviceStatus(&cpuCount, &maxMemory)
容許併發的圖片解碼任務數量,以及解碼數據的內存使用量。這個方法會常常被 ExternalAdapterImageFrameCodec 調用,控制多圖加載時的資源消耗。
struct PlatformImage {
uintptr_t handle = 0;
int width = 0; // width in pixel
int height = 0; // height in pixel
int frameCount = 1; // multiframe image such as GIF
int repetitionCount = -1; // infinite
int durationInMs = 0; // in milliseconds
};複製代碼
class ExternalAdapterImageFrameCodec {
ExternalAdapterImageProvider provider;
void getNextFrame() {
async(provider.request([](image) {
if (cancelled) {
return;
}
async(workerThread, {
if (cancelled) {
return;
}
bitmap = provider.decode(image);
async(ioThread, {
if (cancelled) {
return;
}
ui.Image texture = uploadToGPU(bitmap);
async(uiThread, {
if (cancelled) {
return;
}
callbackDart(texture);
})
})
})
}))
}
void cancel() {
provider.cancel()
cancelled = true
}
}複製代碼
執行時序圖:
@protocol FlutterExternalAdapterImageRequest <NSObject>
- (void)cancel;
@end
@protocol FlutterExternalAdapterImageProvider <NSObject>
- (id<FlutterExternalAdapterImageRequest>)request:(NSString*)url
targetWidth:(NSInteger)targetWidth
targetHeight:(NSInteger)targetHeight
parameters:(NSDictionary<NSString*, NSString*>*)parameters
extraInfo:(NSDictionary<NSString*, NSString*>*)extraInfo
callback:(void(^)(UIImage* image))callback;
@end複製代碼
進入詳情頁面,並退出,反覆進入退出。無內存泄漏。(不進入二級詳情)
進入詳情頁面,點寶貝推薦再進入一個詳情頁面,返回,再返回。產生內存泄漏。
也就是說使用 Boost 管理多個 Flutter 棧時,只要有二級 Flutter 頁面,就會產生內存泄漏。看上去是整個 Widget 樹泄漏,致使底層的 ui.Image 紋理對象不能釋放。
// Class _InkResponseState
void didChangeDependencies() {
super.didChangeDependencies();
_focusNode?.removeListener(_handleFocusUpdate);
_focusNode = Focus.of(context, nullOk: true);
_focusNode?.addListener(_handleFocusUpdate);
// 原來的代碼缺乏這一行,致使屢次添加 listener 形成組件泄漏。
WidgetsBinding.instance.focusManager.removeHighlightModeListener(_handleFocusHighlightModeChange);
WidgetsBinding.instance.focusManager.addHighlightModeListener(_handleFocusHighlightModeChange);
}複製代碼
該問題在 Flutter 新版中已經修復了,整個代碼徹底變了,官方用其它方式避免了這種狀況。