闲鱼技术2022年度白皮书-Flutter主题-节日献礼:Flutter 图片库重磅开源!(上)

简介: 闲鱼技术2022年度白皮书-Flutter主题-


 

作者:新宿

 

一、 背景

 

去年,闲鱼技术团队新一代图片库PowerImage在经过一系列灰度、问题修复、代码调优后,已全量稳定应用于闲鱼。相对于上一代IFImage,PowerImage经过进一步的演进,适应了更多的业务场景与最新的flutter特性,解决了一系列痛点

 

比如,因为完全抛弃了原生的ImageCache,在与原生图片混用的场景下,会让一些低频的图片反而占用了缓存;比如,我们在模拟器上无法展示图片;比如我们在相册中,需要在图片库之外再搭建图片通道。

 

二、 简介

 

PowerImage是一个充分利用native原生图片库能力、高扩展性的flutter图片库。我们巧妙地将外接纹理与ffi方案组合,以更贴近原生的设计,解决了一系列业务痛点。

 

能力特点

 

支持加载ui.Image能力。在基于外接纹理的方案中,使用方无法拿到真正的ui.Image去使用,这导致图片库在这种特殊的使用场景下无能为力。

 

支持图片预加载能力。正如原生precacheImage一样。这在某些对图片展示速度要求较高的场景下非常有用。

 

新增纹理缓存,与原生图片库缓存打通!统一图片缓存,避免原生图片混用带来的内存问题。

 

支持模拟器。在flutter-1.23.0-18.1.pre之前的版本,模拟器无法展示Texture Widget。

 

完善自定义图片类型通道。解决业务自定义图片获取诉求。

 

完善的异常捕获与收集。

 

支持动图。(来自淘特的PR)

 

三、 Flutter原生方案

 

在介绍新方案开始之前,先简单回忆一下flutter原生图片方案。

 

image.png


原生Image Widget先通过ImageProvider得到ImageStream,通过监听它的状态,进行各种状态的展示。比如frameBuilder、loadingBuilder,最终在图片加载成功后,会rebuild出RawImage,RawImage会通过RenderImage来绘制,整个绘制的核心是ImageInfo中的ui.Image。

 

Image:负责图片加载的各个状态的展示,如加载中、失败、加载成功展示图片等。

ImageProvider:负责ImageStream的获取,比如系统内置的NetworkImage、AssetImage等。

ImageStream:图片资源加载的对象。

 

在梳理flutter原生图片方案之后,我们发现是不是有机会在某个环节将flutter图片和native以原生的方式打通?

 

四、 新一代方案

 

我们巧妙地将FFi方案与外接纹理方案组合,解决了一系列业务痛点。

 

1. FFI

 

正如开头说的那些问题,Texture方案有些做不到的事情,这需要其他方案来互补,这其中核心需要的就是ui.Image。我们把native内存地址、长度等信息传递给flutter侧,用于生成ui.Image。

 

首先native侧先获取必要的参数(以iOS为例):

 

_rowBytes = CGImageGetBytesPerRow(cgImage);
    CGDataProviderRef dataProvider = CGImageGetDataProvider(cgImage);
    CFDataRef rawDataRef = CGDataProviderCopyData(dataProvider);
    _handle = (long)CFDataGetBytePtr(rawDataRef);
    NSData *data = CFBridgingRelease(rawDataRef);
    self.data = data;
    _length = data.length;

 

dart侧拿到后

 

@override
  FutureOr createImageInfo(Map map) {
    Completer completer = Completer();
    int handle = map['handle'];
    int length = map['length'];
    int width = map['width'];
    int height = map['height'];
    int rowBytes = map['rowBytes'];
    ui.PixelFormat pixelFormat =
        ui.PixelFormat.values[map['flutterPixelFormat'] ?? 0];
    Pointer pointer = Pointer.fromAddress(handle);
    Uint8List pixels = pointer.asTypedList(length);
    ui.decodeImageFromPixels(pixels, width, height, pixelFormat,
        (ui.Image image) {
      ImageInfo imageInfo = ImageInfo(image: image);
      completer.complete(imageInfo);
      //释放 native 内存
      PowerImageLoader.instance.releaseImageRequest(options);
    }, rowBytes: rowBytes);
    return completer.future;
  }

我们可以通过ffi拿到native内存,从而生成ui.Image。这里有个问题,虽然通过ffi能直接获取native内存,但是由于decodeImageFromPixels会有内存拷贝,在拷贝解码后的图片数据时,内存峰值会更加严重。

 

这里有两个优化方向:

 

解码前的图片数据给flutter,由flutter提供的解码器解码,从而削减内存拷贝峰值。

与flutter官方讨论,尝试从内部减少这次内存拷贝。

 

FFI这种方式适合轻度使用、特殊场景使用,支持这种方式可以解决无法获取ui.Image的问题,也可以在模拟器上展示图片(flutter <= 1.23.0-18.1.pre),并且图片缓存将完全交给ImageCache管理。

 

2. Texture

 

Texture方案与原生结合有一些难度,这里涉及到没有ui.Image只有textureId。这里有几个问题需要解决:

 

问题一:Image Widget需要ui.Image去build RawImage从而绘制,这在本文前面的Flutter原生方案介绍中也提到了

 

问题二:ImageCache依赖ImageInfo中ui.Image的宽高进行cache大小计算以及缓存前的校验;问题三:native侧texture生命周期管理。

 

分别都有解决方案:

 

问题一:通过自定义Image解决,透出imageBuilder来让外部自定义图片widget

 

问题二:为Texture自定义ui.image,如下:

 

import 'dart:typed_data';
import 'dart:ui' as ui show Image;
import 'dart:ui';
class TextureImage implements ui.Image {
  int _width;
  int _height;
  int textureId;
  TextureImage(this.textureId, int width, int height)
      : _width = width,
        _height = height;
  @override
  void dispose() {
    // TODO: implement dispose
  }
  @override
  int get height => _height;
  @override
  Future toByteData(
      {ImageByteFormat format = ImageByteFormat.rawRgba}) {
    // TODO: implement toByteData
    throw UnimplementedError();
  }
  @override
  int get width => _width;
}

 

这样的话,TextureImage实际上就是个壳,仅仅用来计算cache大小。实际上,ImageCache计算大小,完全没必要直接接触到ui.Image,可以直接找ImageInfo取,这样的话就没有这个问题了。

 

问题三:关于native侧感知flutter image释放时机的问题。

 

修改的ImageCache释放如下部分代码

 

 

typedef void HasRemovedCallback(dynamic key, dynamic value);
class RemoveAwareMap implements Map {
  HasRemovedCallback hasRemovedCallback;
  ...
}
//------
  final RemoveAwareMap _pendingImages = RemoveAwareMap();
//------
void hasImageRemovedCallback(dynamic key, dynamic value) {
    if (key is ImageProviderExt) {
      waitingToBeCheckedKeys.add(key);
    }
    if (isScheduledImageStatusCheck) return;
    isScheduledImageStatusCheck = true;
    //We should do check in MicroTask to avoid if image is remove and add right away
    scheduleMicrotask(() {
      waitingToBeCheckedKeys.forEach((key) {
        if (!_pendingImages.containsKey(key) &&
            !_cache.containsKey(key) &&
            !_liveImages.containsKey(key)) {
          if (key is ImageProviderExt) {
            key.dispose();
          }
        }
      });
      waitingToBeCheckedKeys.clear();
      isScheduledImageStatusCheck = false;
    });
  }

 

接下篇:https://developer.aliyun.com/article/1225986?groupCode=idlefish

 


 

相关文章
|
1月前
|
开发框架 UED 计算机视觉
flutter:图片&stful 生命周期 (三)
本文档介绍了如何在Flutter中处理图片,包括加载网络图片、本地图片、创建圆形图片和带有圆角的图片,以及如何配置`pubspec.yaml`文件来添加资源文件。还展示了如何使用`AssetImage`对象来显示本地资源图片,并通过代码示例详细说明了这些操作的实现方法。最后,简要介绍了StatefulWidget的生命周期。
|
3月前
|
SQL 分布式计算 大数据
Flutter技术实践问题之Flutter应用过程中的基础建设如何解决
Flutter技术实践问题之Flutter应用过程中的基础建设如何解决
30 10
|
3月前
|
新零售 前端开发 小程序
Flutter技术实践问题之基于Flutter的Canvas的应用优势如何解决
Flutter技术实践问题之基于Flutter的Canvas的应用优势如何解决
32 2
|
3月前
|
Web App开发 新零售 前端开发
Flutter技术实践问题之阿里集团内Flutter体系化建设如何解决
Flutter技术实践问题之阿里集团内Flutter体系化建设如何解决
39 1
|
3月前
|
Kubernetes Cloud Native 搜索推荐
探索云原生技术:Kubernetes入门与实践打造个性化安卓应用:从零开始的Flutter之旅
【8月更文挑战第31天】云原生技术正改变着应用开发和部署的方式。本文将带你了解云原生的基石——Kubernetes,通过实际的代码示例,从安装到部署一个简单的应用,让你迅速掌握Kubernetes的核心概念和操作方法。无论你是初学者还是有一定经验的开发者,这篇文章都将成为你进入云原生世界的桥梁。
|
Dart JavaScript Shell
Flutter技术解析与实战——闲鱼技术演进与创新-第1章(8)
本书将详细讲解闲鱼Flutter&FaaS云端一体化架构,以及闲鱼基于Flutter的架构演进与创新,学习一套全面的Flutter架构应用方案。本书介绍闲鱼技术团队利用Flutter技术改造和上线复杂业务的混合工程改造实践,抽取Flutter依赖到远程的实现细节,以及使用Plugin桥接获取设备信息、使用基础网络库等混合开发实践指南。这些实践遍布闲鱼各大业务线和应用场景,为读者使用Flutter打造自己的研发体系探索一条实践之路。除了介绍闲鱼Flutter应用框架Fish Redux、开发利器AspectD、FlutterBoost等一众开源工具与开发实践指南,你还将在......
Flutter技术解析与实战——闲鱼技术演进与创新-第1章(8)
|
Dart JavaScript API
Flutter技术解析与实战——闲鱼技术演进与创新-第1章(10)
本书将详细讲解闲鱼Flutter&FaaS云端一体化架构,以及闲鱼基于Flutter的架构演进与创新,学习一套全面的Flutter架构应用方案。本书介绍闲鱼技术团队利用Flutter技术改造和上线复杂业务的混合工程改造实践,抽取Flutter依赖到远程的实现细节,以及使用Plugin桥接获取设备信息、使用基础网络库等混合开发实践指南。这些实践遍布闲鱼各大业务线和应用场景,为读者使用Flutter打造自己的研发体系探索一条实践之路。除了介绍闲鱼Flutter应用框架Fish Redux、开发利器AspectD、FlutterBoost等一众开源工具与开发实践指南,你还将在......
Flutter技术解析与实战——闲鱼技术演进与创新-第1章(10)
|
缓存 Dart JavaScript
Flutter技术解析与实战——闲鱼技术演进与创新-第1章(9)
本书将详细讲解闲鱼Flutter&FaaS云端一体化架构,以及闲鱼基于Flutter的架构演进与创新,学习一套全面的Flutter架构应用方案。本书介绍闲鱼技术团队利用Flutter技术改造和上线复杂业务的混合工程改造实践,抽取Flutter依赖到远程的实现细节,以及使用Plugin桥接获取设备信息、使用基础网络库等混合开发实践指南。这些实践遍布闲鱼各大业务线和应用场景,为读者使用Flutter打造自己的研发体系探索一条实践之路。除了介绍闲鱼Flutter应用框架Fish Redux、开发利器AspectD、FlutterBoost等一众开源工具与开发实践指南,你还将在......
Flutter技术解析与实战——闲鱼技术演进与创新-第1章(9)
|
JavaScript 持续交付 开发工具
Flutter技术解析与实战——闲鱼技术演进与创新-第1章(7)
本书将详细讲解闲鱼Flutter&FaaS云端一体化架构,以及闲鱼基于Flutter的架构演进与创新,学习一套全面的Flutter架构应用方案。本书介绍闲鱼技术团队利用Flutter技术改造和上线复杂业务的混合工程改造实践,抽取Flutter依赖到远程的实现细节,以及使用Plugin桥接获取设备信息、使用基础网络库等混合开发实践指南。这些实践遍布闲鱼各大业务线和应用场景,为读者使用Flutter打造自己的研发体系探索一条实践之路。除了介绍闲鱼Flutter应用框架Fish Redux、开发利器AspectD、FlutterBoost等一众开源工具与开发实践指南,你还将在......
Flutter技术解析与实战——闲鱼技术演进与创新-第1章(7)
|
Dart JavaScript Java
Flutter技术解析与实战——闲鱼技术演进与创新-第1章(6)
本书将详细讲解闲鱼Flutter&FaaS云端一体化架构,以及闲鱼基于Flutter的架构演进与创新,学习一套全面的Flutter架构应用方案。本书介绍闲鱼技术团队利用Flutter技术改造和上线复杂业务的混合工程改造实践,抽取Flutter依赖到远程的实现细节,以及使用Plugin桥接获取设备信息、使用基础网络库等混合开发实践指南。这些实践遍布闲鱼各大业务线和应用场景,为读者使用Flutter打造自己的研发体系探索一条实践之路。除了介绍闲鱼Flutter应用框架Fish Redux、开发利器AspectD、FlutterBoost等一众开源工具与开发实践指南,你还将在......
Flutter技术解析与实战——闲鱼技术演进与创新-第1章(6)