闲鱼技术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

 


 

相关文章
|
10月前
|
JSON Dart API
Flutter 使用图片和资源
Flutter 使用图片和资源
252 2
Flutter 使用图片和资源
|
11月前
|
开发框架 前端开发 定位技术
Flutter框架中的插件市场及开源资源的利用方法。内容涵盖插件市场的扩展功能、时间节省与质量保证
本文深入探讨了Flutter框架中的插件市场及开源资源的利用方法。内容涵盖插件市场的扩展功能、时间节省与质量保证,常见插件市场的介绍,选择合适插件的策略,以及开源资源的利用价值与注意事项。通过案例分析和对社区影响的讨论,展示了这些资源如何促进开发效率和技术进步,并展望了未来的发展趋势。
243 11
|
SQL 分布式计算 大数据
Flutter技术实践问题之Flutter应用过程中的基础建设如何解决
Flutter技术实践问题之Flutter应用过程中的基础建设如何解决
106 10
|
开发框架 UED 计算机视觉
flutter:图片&stful 生命周期 (三)
本文档介绍了如何在Flutter中处理图片,包括加载网络图片、本地图片、创建圆形图片和带有圆角的图片,以及如何配置`pubspec.yaml`文件来添加资源文件。还展示了如何使用`AssetImage`对象来显示本地资源图片,并通过代码示例详细说明了这些操作的实现方法。最后,简要介绍了StatefulWidget的生命周期。
|
新零售 前端开发 小程序
Flutter技术实践问题之基于Flutter的Canvas的应用优势如何解决
Flutter技术实践问题之基于Flutter的Canvas的应用优势如何解决
141 2
|
Web App开发 新零售 前端开发
Flutter技术实践问题之阿里集团内Flutter体系化建设如何解决
Flutter技术实践问题之阿里集团内Flutter体系化建设如何解决
127 1
|
Kubernetes Cloud Native 搜索推荐
探索云原生技术:Kubernetes入门与实践打造个性化安卓应用:从零开始的Flutter之旅
【8月更文挑战第31天】云原生技术正改变着应用开发和部署的方式。本文将带你了解云原生的基石——Kubernetes,通过实际的代码示例,从安装到部署一个简单的应用,让你迅速掌握Kubernetes的核心概念和操作方法。无论你是初学者还是有一定经验的开发者,这篇文章都将成为你进入云原生世界的桥梁。
|
缓存 算法 数据挖掘
闲鱼Flutter图片框架架构演进(超详细)
flutter图片内存大?加载慢?本地资源利用率低?看这篇就够了
3914 0
闲鱼Flutter图片框架架构演进(超详细)
|
9月前
flutter开发中Use ‘const’ with the constructor to improve performance. Try adding the ‘const’ keyword to the constructor invocation.报错如何解决-优雅草卓伊凡
flutter开发中Use ‘const’ with the constructor to improve performance. Try adding the ‘const’ keyword to the constructor invocation.报错如何解决-优雅草卓伊凡
127 1
|
8月前
|
前端开发 安全 开发工具
【11】flutter进行了聊天页面的开发-增加了即时通讯聊天的整体页面和组件-切换-朋友-陌生人-vip开通详细页面-即时通讯sdk准备-直播sdk准备-即时通讯有无UI集成的区别介绍-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草Alex
【11】flutter进行了聊天页面的开发-增加了即时通讯聊天的整体页面和组件-切换-朋友-陌生人-vip开通详细页面-即时通讯sdk准备-直播sdk准备-即时通讯有无UI集成的区别介绍-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草Alex
479 90
【11】flutter进行了聊天页面的开发-增加了即时通讯聊天的整体页面和组件-切换-朋友-陌生人-vip开通详细页面-即时通讯sdk准备-直播sdk准备-即时通讯有无UI集成的区别介绍-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草Alex