百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 热门文章 > 正文

燃烧我的卡路里 ---- Flutter瘦内存瘦包之图片组件

bigegpt 2024-08-16 14:23 2 浏览

背景

在电商类APP里,图片到现在为止仍然是最重要的信息承载媒介,不得不说逛淘宝的过程,其实就是一个看图片的过程。而商品详情页中的图片,通常是页面中内存占用最多的内容,占用了整个页面内存的超过 50%。

闲鱼在Flutter化的过程中,选择了商品详情页作为第一个落地的场景。通过多版本的迭代完善,基于Flutter的详情页已经在闲鱼稳定运行。然而正因为详情页的图片量大,导致Flutter里图片相关的问题一直挥之不去。

1:内存问题 --- 连续push flutter界面内存累积

2:安装包问题 --- 过渡时期两份重复资源文件。

3:寻址缓存问题 --- 原有的寻址缓存策略无法复用。

4:图片复用问题 --- Native和Flutter重复下载相同图片。

解决方案----FXTexImage_V1

为了解决这些问题,我们尝试着寻找一种新的思路,一种能够将flutter与native串联起来的思路。而之前做视频播放器的方案给了我们启发。

熟悉Flutter的同学应该都知道,Flutter的视频组件是基于一个Flutter提供的一个叫“外接纹理”的技术实现的,关于flutter外接纹理,本人另外有一篇文章有更详细的论述,这里不再赘述。

https://mp.weixin.qq.com/s/KkCsBvnRayvpXdI35J3fnw

我们将每一张图片假想成一个:静态的视频。图片的内容由一个external_texture来负责显示,而这个external_texture则由native端提供具体的渲染数据。

通过这种方案,我们便可以通过external_texture这座桥梁,将flutter作为native端图片的一个最终展示场所。而所有的下载、缓存、裁剪等逻辑都可以复用原来的native图片库。

基于这个基本框架,我们形成了我们第一版本的图片渲染组件:FXTexImage----V1。这个组件很好的解决了Flutter引入的安装包、图片缓存、图片复用等问题。

但是图片最大的问题:内存问题,并没有得到解决。

内存优化----FXTexImage_V2

为了用户体验,通常会有连续push若干个界面的场景(比如闲鱼的详情页,点击底部的推荐列表,可以一直往下push新的详情页),这种场景下,每一个界面都有大量的图片展示。所以在引入flutter以后,闲鱼在iPhone 6P等机型上通常只能push10个左右详情页就挂了。

在考虑到在显示过程中,真正用户可见的页面,其实只有当前栈顶的两个页面,基于这个特征我们就做了优化逻辑:

1:在push详情页过程中,我们只保留了当前展示页和当前页的前一页的图片资源,而之前的资源全部都做了释放(只是图片资源的释放,整个页面还有页面中的其他元素还是做了保留)。

2:为了做到用户无感知,我们在pop过程中,会预先去加载当前界面下一个界面的图片资源。

通过这种方式,理论上我们可以释放掉不可见的资源,从而保证在持续Push界面过程中内存缓慢增长,但是实践过程中发现内存仍然持续增长。

经过排查,我们发现flutter 1.0版本以及0.8.2版本里,SurfaceTextureRegistry提供了release方法,这里将会把创建的SurfaceTexture进行释放。然而测试过程中发现,单单对SurfaceTexture释放,并没有完全释放内存,当反复创建对象时仍然会闪退。为此,我们在AndroidExternalTextureGL的析构函数中增加了纹理的释放glDeleteTextures逻辑。

然而,AndroidExternalTextureGL的析构是在flutter的GPU线程调用的,而external_texture的release方法通常是在主线程,也就是PlatForm线程调用的。不同线程调用的问题就是会导致一个诡异的问题:

推测是不同线程释放的逻辑影响了GL环境,导致文字渲染出了问题。

所以,为了解决该问题,我们删除了SurfaceTextureRegistry的release方法里面SufaceTexture的释放逻辑,并且将SurfaceTexture的释放,放到AndroidExternalTextureGL析构阶段,通过Jni调用java方法实现资源释放。

AndroidExternalTextureGL::~AndroidExternalTextureGL(){
 if (state_ == AttachmentState::attached) {
 Detach();
 if (texture_name_ != 0)
 {
 glDeleteTextures(1, &texture_name_);
 texture_name_ = 0;
 }
 }
 Release();
 state_ = AttachmentState::detached;
}
void AndroidExternalTextureGL::Release() {
 JNIEnv* env = fml::jni::AttachCurrentThread();
 fml::jni::ScopedJavaLocalRef<jobject> surfaceTexture =
 surface_texture_.get(env);
 if (!surfaceTexture.is_null()) {
 SurfaceTextureRelease(env, surfaceTexture.obj());
 }
}
void SurfaceTextureRelease(JNIEnv* env, jobject obj) {
 env->CallVoidMethod(obj, g_release_method);
 FML_CHECK(CheckException(env));
}
 g_release_method = env->GetMethodID(g_surface_texture_class->obj(), "release", "()V");

CPU优化----FXTexImage_V3

通过外界纹理渲染图片+不可见页面资源释放,我们解决了上述提出的一系列问题,但是又引入了新的问题:CPU偏高,滑动帧率偏低。通过测试,在详情页滑动过程中,IOS和Android的CPU都比Flutter原生组件高10%以上,这个显然无法应用。

经过排查,发现CPU高的原因是:

IOS端: iOS的IOSExternalTextureGL模型是一个拉数据的模型,native端register一个CVPixelBuffer的生产者,当需要绘制时,都会调用一次这个生产者的copyPixelbuffer方法去拉一次数据。然后将拉到的CVPixelBuffer对象转换成GPU Texture。这里每一次转换都换造成CPU较大开销。

并且这种拉数据的机制就要求这个生产者的必须一直保留着这个CVPixelBuffer对象(否则界面重刷以后,图片区域就显示白屏)。

Android端: android 的数据存储在SurfaceTexture中。每一次external_texture layer需要绘制时候都会从SurfaceTexture中去update 数据到纹理中,由于SurfaceTexture使用基于EGLImage共享内存,所以虽然没有双份内存的问题,但是每一次update 都会带来较大的CPU开销。

在之前外接纹理的文章中,我们提出了一种新的基于共享上下文的外接纹理方案。并在我们视频的拍摄和编辑中得到了很好的应用。该方案当初提出来,是为了解决视频数据从CPU -> GPU -> CPU -> GPU 输送的问题而提出来的。

但是在图片这个场景下, 新的外接纹理方案下,一张图片在native端加载完成以后,立刻被转换成一个OpenGL的Texture,然后图片的资源马上被释放。当界面刷新时,对于同一张图片的重新渲染,IOSExternalTextureGL不需要再去做将数据(CVPixelBuffer或者SurfaceTexture)转换到Texture的逻辑,而是直接使用之前创建好的Texture。

经过这一步优化,我们很好的限制了iOS的CPU和内存,Android的CPU。

通过测试对比,V3版本的图片组件,相比于Flutter原生图片组件,在详情页正常滑动操作过程中,平均CPU高出3%左右,虽然仍差于原生组件,单相对是可以接受的。

结果

内存: 基于新图片组件,我们很好的限制住了连续push 下的内存增长速度,顺利的将iPhone 6P上的详情页push 最大数量从10个增加到了30个以上。

在同一线上版本中,我们通过控制ABTest开关,测试新的图片渲染方案和Flutter自带图片组件方案的Abort率,发现新图片组件下闲鱼的Abort率降低20%。

安装包:新组件下,所有的资源组件与原来native资源共用,所有flutter期间新引入的资源出了gif图,全部删除,减少安装包900k+。并且后续可以不用继续新增。

寻址策略: 复用native图片组件,基于阿里系自己的图片下载组件,这样可以做到随着集团组件升级版本,兼容版本过程中各种新的寻址方式和图片格式。

图片复用:复用native图片组件,当图片地址命中缓存,可直接缓存加载,尺寸不一致时可以预先返回缓存图同时加载大图,这样大大增强详情页大图预览的浏览体验。

遗留问题

图片组件已经在闲鱼上全量部署,然而还是有一些问题没有得到很好的解决,上文提到过CPU比原生图片组件高3%左右,虽然用户没有感官体验,但是还是有优化空间。

还有就是Flutter针对ExternalTexture的纹理渲染时没有开启抗锯齿,导致小图在大区域渲染时比原生组件效果要差。这里还需要继续排查原因。

最后,FXTexImage组件还在持续优化中,当解决上述遗留问题以后便会在Github上开源。

作者:闲鱼技术-炉军

相关推荐

了解Linux目录,那你就了解了一半的Linux系统

大到公司或者社群再小到个人要利用Linux来开发产品的人实在是多如牛毛,每个人都用自己的标准来配置文件或者设置目录,那么未来的Linux则就是一团乱麻,也对管理造成许多麻烦。后来,就有所谓的FHS(F...

Linux命令,这些操作要注意!(linux命令?)

刚玩Linux的人总觉得自己在演黑客电影,直到手滑输错命令把公司服务器删库,这才发现命令行根本不是随便乱用的,而是“生死簿”。今天直接上干货,告诉你哪些命令用好了封神!喜欢的一键三连,谢谢观众老爷!!...

Linux 命令速查手册:这 30 个高频指令,拯救 90% 的运维小白!

在Linux系统的世界里,命令行是强大的武器。对于运维小白而言,掌握一些高频使用的Linux命令,能极大提升工作效率,轻松应对各种系统管理任务。今天,就为大家奉上精心整理的30个Linu...

linux必学的60个命令(linux必学的20个命令)

以下是Linux必学的20个基础命令:1.cd:切换目录2.ls:列出文件和目录3.mkdir:创建目录4.rm:删除文件或目录5.cp:复制文件或目录6.mv:移动/重命名文件或目录7....

提高工作效率的--Linux常用命令,能够决解95%以上的问题

点击上方关注,第一时间接受干货转发,点赞,收藏,不如一次关注评论区第一条注意查看回复:Linux命令获取linux常用命令大全pdf+Linux命令行大全pdf为什么要学习Linux命令?1、因为Li...

15 个实用 Linux 命令(linux命令用法及举例)

Linux命令行是系统管理员、开发者和技术爱好者的强大工具。掌握实用命令不仅能提高效率,还能解锁Linux系统的无限潜力,本文将深入介绍15个实用Linux命令。ls-列出目录内容l...

Linux 常用命令集合(linux常用命令全集)

系统信息arch显示机器的处理器架构(1)uname-m显示机器的处理器架构(2)uname-r显示正在使用的内核版本dmidecode-q显示硬件系统部件-(SMBIOS/DM...

Linux的常用命令就是记不住,怎么办?

1.帮助命令1.1help命令#语法格式:命令--help#作用:查看某个命令的帮助信息#示例:#ls--help查看ls命令的帮助信息#netst...

Linux常用文件操作命令(linux常用文件操作命令有哪些)

ls命令在Linux维护工作中,经常使用ls这个命令,这是最基本的命令,来写几条常用的ls命令。先来查看一下使用的ls版本#ls--versionls(GNUcoreutils)8.4...

Linux 常用命令(linux常用命令)

日志排查类操作命令查看日志cat/var/log/messages、tail-fxxx.log搜索关键词grep"error"xxx.log多条件过滤`grep-E&#...

简单粗暴收藏版:Linux常用命令大汇总

号主:老杨丨11年资深网络工程师,更多网工提升干货,请关注公众号:网络工程师俱乐部下午好,我的网工朋友在Linux系统中,命令行界面(CLI)是管理员和开发人员最常用的工具之一。通过命令行,用户可...

「Linux」linux常用基本命令(linux常用基本命令和用法)

Linux中许多常用命令是必须掌握的,这里将我学linux入门时学的一些常用的基本命令分享给大家一下,希望可以帮助你们。总结送免费学习资料(包含视频、技术学习路线图谱、文档等)1、显示日期的指令:d...

Linux的常用命令就是记不住,怎么办?于是推出了这套教程

1.帮助命令1.1help命令#语法格式:命令--help#作用:查看某个命令的帮助信息#示例:#ls--help查看ls命令的帮助信息#netst...

Linux的30个常用命令汇总,运维大神必掌握技能!

以下是Linux系统中最常用的30个命令,精简版覆盖日常操作核心需求,适合快速掌握:一、文件/目录操作1.`ls`-列出目录内容`ls-l`(详细信息)|`ls-a`(显示隐藏文件)...

Linux/Unix 系统中非常常用的命令

Linux/Unix系统中非常常用的命令,它们是进行文件操作、文本处理、权限管理等任务的基础。下面是对这些命令的简要说明:**文件操作类:*****`ls`(list):**列出目录内容,显...