简介:拿什么拯救你,flutter内存

作者:闲鱼技术——靖书

背景

我们闲鱼使用的图片方案是自研的外接纹理方案:

  • Android侧创建SurfaceTexture,通过FlutterJNI注册到Flutter engine里,最后返回texture id给Flutter应用层,应用层使用Texture Widget和textue id去显示图片纹理。
  • 纹理数据则是在Android侧,通过OpenGL将图片纹理写入到SurfaceTexture,然后通过Flutter engine里的共享内存,将纹理数据传入到应用层,最终交给Skia渲染。

这里面存在的问题:
Flutter应用层的纹理数据没有缓存,每次都需要重新将Bitmap数据渲染成纹理,再交给Flutter应用层使用。Native图片加载会内存缓存,Flutter自身提供的图片库也存在缓存,这2个缓存相互隔离,占用很大的内存空间。而且Flutter图片缓存基本都是存放的本地资源图,而我们Flutter页面上大部分其实都是网络下载的外接纹理图片,导致缓存资源利用率很低。

分析

针对上述的3个问题,我们先抛开技术实现,假设下要解决这3个问题,最理想的一个解决方案是什么:

  • 纹理没有缓存,那我们在应用层增加一个纹理的内存缓存就解决了。
  • 当上层的应用层已经缓存纹理,那Native侧的Bitmap的内存缓存也可以被去掉,只保留图片资源的磁盘缓存。
  • 整个App的内存缓存,只有纹理缓存,Flutter的ImageCache缓存,为了避免内存资源的浪费,将这2个缓存合成一个

所以最理想的解决方案:
整个App内只存在一个内存缓存,并且它既能缓存纹理,也能缓存Flutter的Image Widget加载的图片数据。

解决方案

ImageCache是官方提供的,我们没办法去掉,而且闲鱼App里也有一些地方使用Image Widget。现在解决方案就变成:
将纹理数据也放到ImageCache里缓存。使用纹理时,先从imageCache里取。

我们先看下现有的Flutter图片加载逻辑,以及图片是如何缓存的

从图中可以看到,Flutter的图片加载,都会调用ImageCache.putIfAbsent方法,通过该方法取缓存,没命中缓存则会使用传入有的loader方法,去构造对应的ImageStreamCompleter,由ImageStreamCompleter去完成图片加载的逻辑。

当命中缓存时,putIfAbsent方法会直接返回ImageStreamCompleter,该对象里持有了imageInfo,ImageWidget直接拿imageInfo的ui.Image去渲染。

方案一:扩展ImageCache,缓存纹理

ImageCache对外提供取缓存方法就一个putIfAbsent

一开始我们想的是按照该方法参数,构建对应的key,loader,以及ImageStreamCompleter,然后也使用putIfAbsent方法去取缓存。

尝试过后发现不行,如下图所示,当图片下载解码成功后,会回调这个listener方法,在该方法中,会将图片存放进ImageCache的缓存队列

这个listener回调有2个参数,ImageInfo里面存放着图片数据ui.Image。

我们应用层根本没办法去构造 ui.Image,因为该类是Flutter engine底层完成图片解码之后set到应用层的。应用层根本没办法去主动set值。这样就导致在listener里,无法计算出imageSize的值,自然也没办法存到缓存里。

方案二:自定义ImageCache

因为ImageCache的缓存队列是私有的,只有putIfAbsent方法可以往里面存数据。那我们只有另外一条路,从ImageCache的源码入手,去自定义imageCache,然后对其进行功能扩展。

将ImageCache替换成我们自定义的

因为Flutter提供的ImageCache没办法修改代码,所以我们直接把ImageCache的源码copy出来一份,继承ImageCache,然后将PaintingBinding的imageCache替换成自定义的。


如图所示:Flutter的PaintingBinding有暴露出createImageCache的方法,我们继承WidgetsFlutterBinding,重写该方法返回我们自己的ImageCache, 另外在这里还可以针对ImageCache的各种缓存大小做设置。

对ImageCache进行功能扩展

为了尽可能不修改ImageCache的代码,我们直接定义了新的缓存纹理的方法,对齐了putIfAbsent方法的逻辑,核心代码逻辑如下:

该方法主要是参考putIfAbsent的逻辑来实现的,为了将纹理也缓存进ImageCache,主要做了以下几个关键扩展:

  1. TextureCacheKey是唯一标识纹理的key,该key是主要是根据宽高,url来判断是否是同一个纹理的。
  2. TextureImageStreamCompleter 则是纹理的管理类,该类继承ImageStreamCompleter,内部持有纹理数据和下载成功的回调。当命中缓存时,返回该对象给应用层,并从中拿到纹理id交给Texture Widget渲染
  3. 当没有命中缓存时,会调用传入的loader方法构造TextureImageStreamCompleter,并且会执行纹理的加载逻辑。同时会构造一个listener回调,注册进TextureImageStreamCompleter。
  4. 当纹理加载成功时,会执行listener方法回调,该方法里主要是计算纹理大小,将它放入缓存队列里,检查缓存大小是否超过最大值,超过则淘汰之前最久未使用的纹理。

这里要注意的一个点
因为普通的图片是dart对象,会被Dart VM自动回收,但是我们的纹理对象真实的数据是在Engine的共享内存里,所以这里需要手动的管理纹理的释放,我们对纹理对了引用计数,只有当没有widget持有纹理时,引用计数为0时,才会真正的释放。

同理,上层Texture Widget 在dispose时,也会调用下ImageCache提供的接口,看下当前使用的纹理是否被缓存或者正在被使用。只有否的时候才会真正的释放纹理

效果

我们采用搜索结果页作为测试页面,该页面存在很多宝贝大图,以及各种重复的标签小图。使用华为荣耀20来测试优化前后的物理内存占用。

操作步骤是:打开app,进入搜索结果页,搜索相同的关键字后进入搜索结果页,然后静默10s后滑动浏览100条数据,最后停止操作。期间每秒采样一次物理内存,一共持续100s,得出如下的数据

蓝色曲线是优化前的内存占用,橘黄色曲线是优化后,进入时可以看到占用的内存基本一致。滑动时内存占用下降是因为出发了GC回收App的内存导致的。总体上看,优化后总的内存占用比优化前要少,因为GC导致的毛刺也比优化前要少。

展望

上述的方案虽然实现了一个App内一个内存缓存,并且将纹理和Flutter图片都存进去了,节省了内存空间,提高了内存使用率,但还是侵入了ImageCache源码,后续flutter engine的升级和代码维护,需要有额外的工作。

此外因为Flutter侧加载原生图片,都走的putIfAbsent方法,并且因为加载原生图片都走的原图加载,我们app内时不时存在着这种情况,一张图片可能会占用好几M的内存,所以我们直接在putIfAbsent加上了大图监控的方法,当发现加载的图片大小超过2M时,会进行数据上报,包括图片的url,图片使用信息,图片大小等。通过该方式,我们发现了好几例图片使用不当的情况:直接使用Image.network加载原图,或者是Image.asset加载一张很大的本地资源。

原文链接:https://developer.aliyun.com/article/776520?

版权声明:本文内容由阿里云实名注册用户自发贡献,版权归原作者所有,阿里云开发者社区不拥有其著作权,亦不承担相应法律责任。具体规则请查看《阿里云开发者社区用户服务协议》和《阿里云开发者社区知识产权保护指引》。如果您发现本社区中有涉嫌抄袭的内容,填写侵权投诉表单进行举报,一经查实,本社区将立刻删除涉嫌侵权内容。

一个方案提升Flutter内存利用率(干货)相关推荐

  1. 计算一个image的大小_一个方案提升Flutter内存利用率(干货)

    简介:拿什么拯救你,flutter内存 作者:闲鱼技术--靖书 背景 我们闲鱼使用的图片方案是自研的外接纹理方案: Android侧创建SurfaceTexture,通过FlutterJNI注册到Fl ...

  2. 腾讯音乐MOO音乐应用的Flutter内存治理实战分享

    MOO音乐是 TME 旗下的新锐音乐服务,其团队是公司内最早实践 Flutter 的先行者之一.本系列文章将提炼 MOO APP 开发中遇到的情况,就 Flutter 内存占用治理方面,分享日常开发的 ...

  3. 定位Flutter内存问题很难么?

    简介:flutter内存泄漏定位 作者:闲鱼技术-三莅 内存水位升高导致的稳定性问题严重影响app用户体验,所以开发者们非常关注Flutter的内存表现.随着Flutter业务越来越多,闲鱼也面临着o ...

  4. Android Flutter 内存机制初探

    阿里妹导读:闲鱼技术团队一直在探索如何使用Flutter来统一移动App开发.移动设备上的资源有限,内存使用成了日常开发中的常见问题.那么,Flutter是如何使用内存,又会对Native App的内 ...

  5. 走近科学,探究阿里闲鱼团队通过数据提升Flutter体验的真相

    背景 闲鱼客户端的flutter页面已经服务上亿级用户,这个时候Flutter页面的用户体验尤其重要,完善Flutter性能稳定性监控体系,可以及早发现线上性能问题,也可以作为用户体验提升的衡量标准. ...

  6. C进阶指南(1):整型溢出和类型提升、内存申请和管理(转)

    转自:http://blog.jobbole.com/72830/ C语言可用于系统编程.嵌入式系统中,同时也是其他应用程序可能的实现工具之一. 当你对计算机编程怀有强烈兴趣的时候,却对C语言不感冒, ...

  7. 深度解读 | ElasticDL 深度学习框架简化编程,提升集群利用率和研发效率的秘诀...

    文 / 蚂蚁集团, ElasticDL 主力工程师 齐俊 ElasticDL 是一个基于 TensorFlow 2.x 和 Kubernetes 的开源的分布式深度学习编程框架.2019 年秋天的 G ...

  8. flutter 获取定位_定位Flutter内存问题如何做

    内存水位升高导致的稳定性问题严重影响app用户体验,所以开发者们非常关注Flutter的内存表现.随着Flutter业务越来越多,闲鱼也面临着oom导致的crash率提升的问题,下面我们结合项目中实际 ...

  9. 操作系统采用哪些方法提高内存利用率

    操作系统采用哪些方法提高内存利用率 从操作系统的需求开始说起 要想搞清楚操作系统采用哪些方法来提高内存利用率,首先,我们应该明白操作系统为什么需要提高内存的利用率. 计算机的系统资源分为处理机.存储器 ...

最新文章

  1. 48个Python练手项目(附详细教程)
  2. ViewPager的缓存机制
  3. C++ STL学习之容器set和multiset (补充材料)
  4. 【mybatis mapper XML】List类型入参参数处理
  5. gradle拷贝静态资源文件_android-Gradle Copy任务不会复制文件
  6. siteservercms 缺点_SiteServer CMS 术语大全
  7. Latex 编译错误 Error Launching Console Application XeLaTeX ...系统找不到指定的文件。
  8. 奶瓶(beini)破解无线密码流程:安装、抓包、从虚拟机(VMware)拷贝握手包(拷贝到硬盘、U盘)、跑包...
  9. [Python爬虫案例]西刺免费代理IP
  10. python playsound 报UnicodeDecodeError
  11. ios苹果开发者账号申请流程(公司类型)
  12. 英:最常用有用的英语口语900句
  13. Educational Codeforces Round 89 (Rated for Div. 2)
  14. 计算机DNS怎么配置,如何设置电脑的dns地址
  15. win10专业版使用vmware安装虚拟机时“出现此主机支持Intel VT-x,但Intel VT-x处于禁用状态”。
  16. zookeeper启动报错:already running as process
  17. 番外篇 之 Win32Api
  18. 简历个人计算机水平怎么xie,个人简历计算机水平书写格式
  19. Linux学习笔记(二) 安装Fedora Linux
  20. 变量提升以及函数提升

热门文章

  1. 基于R语言的梯度推进算法介绍
  2. 想学数据分析但不会Python,过来看看SQL吧(上)~
  3. 用不好这七大工具,别说你懂项目管理!
  4. 贪吃蛇程序设计报告python_20192116 2019-2020-2 《Python程序设计》实验四报告
  5. virtualbox配置apache_VirtualBox虚拟机网络设置(四种方式)
  6. idea 使用maven构建项目时,target bytecode version经常自动变化
  7. mybatis当遇到,用mysql关键字作为的字段的表,如何处理
  8. Solr安装(单机版)
  9. Java多线程技术-Volatile关键字解析
  10. CSS border-image属性