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

 


 

相关文章
|
21天前
|
SQL 分布式计算 大数据
Flutter技术实践问题之Flutter应用过程中的基础建设如何解决
Flutter技术实践问题之Flutter应用过程中的基础建设如何解决
22 10
|
21天前
|
新零售 前端开发 小程序
Flutter技术实践问题之基于Flutter的Canvas的应用优势如何解决
Flutter技术实践问题之基于Flutter的Canvas的应用优势如何解决
20 2
|
21天前
|
Web App开发 新零售 前端开发
Flutter技术实践问题之阿里集团内Flutter体系化建设如何解决
Flutter技术实践问题之阿里集团内Flutter体系化建设如何解决
27 1
|
12天前
|
Kubernetes Cloud Native 搜索推荐
探索云原生技术:Kubernetes入门与实践打造个性化安卓应用:从零开始的Flutter之旅
【8月更文挑战第31天】云原生技术正改变着应用开发和部署的方式。本文将带你了解云原生的基石——Kubernetes,通过实际的代码示例,从安装到部署一个简单的应用,让你迅速掌握Kubernetes的核心概念和操作方法。无论你是初学者还是有一定经验的开发者,这篇文章都将成为你进入云原生世界的桥梁。
|
3月前
|
开发框架 前端开发 测试技术
Flutter开发常见问题解答
Flutter开发常见问题解答
|
4月前
|
前端开发 C++ 容器
Flutter-完整开发实战详解(一、Dart-语言和-Flutter-基础)(1)
Flutter-完整开发实战详解(一、Dart-语言和-Flutter-基础)(1)
|
2天前
|
安全 Android开发 开发者
探索安卓开发的未来:Kotlin的崛起与Flutter的挑战
在移动开发的广阔天地中,安卓平台始终占据着举足轻重的地位。随着技术的不断进步和开发者需求的多样化,Kotlin和Flutter成为了改变游戏规则的新玩家。本文将深入探讨Kotlin如何以其现代化的特性赢得开发者的青睐,以及Flutter凭借跨平台的能力如何挑战传统的安卓开发模式。通过实际案例分析,我们将揭示这两种技术如何塑造未来的安卓应用开发。
17 6
|
19天前
|
开发框架 Android开发 iOS开发
Flutter相关痛点解决问题之淘特选择桌面端开发框架如何解决
Flutter相关痛点解决问题之淘特选择桌面端开发框架如何解决
|
1月前
|
移动开发 前端开发 JavaScript
"跨界大战!React Native、Weex、Flutter:三大混合开发王者正面交锋,揭秘谁才是你移动应用开发的终极利器?"
【8月更文挑战第12天】随着移动应用开发的需求日益增长,高效构建跨平台应用成为关键。React Native、Weex与Flutter作为主流混合开发框架各具特色。React Native依托Facebook的强大支持,以接近原生的性能和丰富的组件库著称;Weex由阿里巴巴开发,性能优越尤其在大数据处理上表现突出;Flutter则凭借Google的支持及独特的Dart语言和Skia渲染引擎,提供出色的定制能力和开发效率。选择时需考量项目特性、团队技能及生态系统的成熟度。希望本文对比能助你做出最佳决策。
76 1
|
19天前
|
Dart Android开发 iOS开发
Flutter相关痛点解决问题之提升开发效率如何解决
Flutter相关痛点解决问题之提升开发效率如何解决