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

 


 

相关文章
|
22天前
|
设计模式 前端开发 测试技术
Flutter 项目架构技术指南
探讨Flutter项目代码组织架构的关键方面和建议。了解设计原则SOLID、Clean Architecture,以及架构模式MVC、MVP、MVVM,如何有机结合使用,打造优秀的应用架构。
Flutter 项目架构技术指南
|
3月前
|
安全 Go 数据安全/隐私保护
Flutter开发笔记:Flutter路由技术
Flutter开发笔记:Flutter路由技术
282 0
|
1月前
|
开发框架 Dart 前端开发
构建响应式Web界面:Flutter的跨界前端技术
【2月更文挑战第23天】随着移动互联网的飞速发展,响应式Web设计成为现代前端开发的重要趋势。在众多框架中,Google推出的Flutter以其高效的渲染性能、跨平台能力及丰富的组件生态,为前端开发者带来了新的选择。本文将深入探讨如何利用Flutter进行高效、美观的响应式界面构建,同时剖析其与传统前端技术的差异和优势。
|
3月前
|
存储 容器
Flutter 应用服务:主题、暗黑、国际化、本地化-app_service库
Flutter 应用服务:主题、暗黑、国际化、本地化-app_service库
62 0
|
3月前
|
移动开发 前端开发 JavaScript
探究移动端混合开发技术:React Native、Weex、Flutter的比较与选择
移动端混合开发技术在移动应用开发领域日益流行,为开发者提供了更高效的跨平台开发方案。本文将比较三种主流混合开发技术:React Native、Weex和Flutter,从性能、生态系统和开发体验等方面进行评估,以帮助开发者在选择适合自己项目的技术时做出明智的决策。
|
3月前
|
移动开发 前端开发 weex
React Native、Weex、Flutter 混合开发技术的比较与选择
移动应用已经成为人们日常生活中不可或缺的一部分,而混合开发技术也随之崛起并逐渐成为主流。本文将比较 React Native、Weex 和 Flutter 三种混合开发技术,并探讨它们各自的优缺点,以及如何根据项目需求做出选择。
44 1
|
3月前
|
监控 Dart 安全
创建一个Dart应用,监控局域网上网记录的软件:Flutter框架的应用
在当今数字时代,网络安全变得愈发重要。为了监控局域网上的上网记录,我们可以借助Flutter框架创建一个强大的Dart应用。在这篇文章中,我们将深入讨论如何使用Flutter框架开发这样一个监控局域网上网记录的软件,并提供一些实用的代码示例。
271 1
|
6月前
|
Dart Android开发 UED
带你读《深入浅出Dart》二十七、Flutter路由管理
带你读《深入浅出Dart》二十七、Flutter路由管理
|
2月前
|
Dart JavaScript
Flutter - Dart 基础(数据类型)
【2月更文挑战第3天】
30 1
|
2月前
|
Dart JavaScript 安全