背景

在做图片、视频相关功能的时候,相册是一个绕不开的话题,因为大家基本都有从相册获取图片或者视频的需求。最直接的方式是调用系统相册接口,基本功能是满足的,一些高级功能就不行了,例如自定义UI、多选图片等。

我们调研了官方的image_picker,它也是调用系统的相册接口来处理的,可定制程度不高,不能满足我们的要求。所以我们选择自己来开发Flutter相册组件。

我们的组件需要有如下的功能:

  • 在app内完成图片、视频的选取,完全不用依赖系统相册组件
  • 可以多选图片,支持指定选定图片的总数目
  • 在多选的时候UI反应出选择的序号。
  • 可以控制视频、图片的选择。例如:只让用户选择视频,图片是灰色的。
  • 大图预览的时候可以放大缩小,也可直接加入到选取列表。

设计思路

API使用简单,功能丰富灵活,具有较高的订制性。业务方可以选择完全接入组件,也可以选择在组件上面进行UI定制。

Flutter做UI展现层,具体的数据由各Native平台提供。这种模式,天然从工程上把UI代码和数据代码进行了隔离。我们在开发一个native组件的时候常常会使用MVC架构。Flutter组件的开发的思路也基本类似。整体架构如下:

可以看出,在Flutter侧是一个典型的MVC架构,这里Widget就是View,View和Model绑定,在Model改变的时候View会重新build反映出Model的变化。View的事件会触发Controller去Native获取数据然后更新Model。Native和Flutter通过Method Channel进行通信,两层之间没有强依赖关系,只需要按约定的协议进行通信即可。

Native侧的组成部分,UIAdapter主要是负责机型的适配、刘海屏、全面屏之类的识别。Permission负责媒体读写权限的申请处理。Cache主要负责缓存GPU纹理,在大图预览的时候提高响应速度。Decoder负责解析Bitmap,OpenGL负责Bitmap转纹理。

需要说明的是:我们的这一套实现依赖于flutter外接纹理。在整个相册组件看到的大多数图片都是一个GPU纹理,这样给java堆内存的占用相对于以前的相册实现有大幅的降低。在低端机上面如果使用原生的系统相册,由于内存的原因,app有被系统杀掉的风险。现象就是,从系统相册返回,app重新启动了。使用Flutter相册组件,在低端机上面体验会有所改观。

一些细节

1分页加载

相册列表需要加载大量图片,Flutter的GridView组件有好几个构造函数,比较容易犯的错误是使用了第一个函数,这需要在一开始就提供大量的widget。应该选择第二个构造函数,GridView在滑动的时候会回调IndexedWidgetBuilder来获取widget,相当于一种懒加载。

GridView.builder({...List<Widget> children = const <Widget>[],...})
GridView.builder({...@required IndexedWidgetBuilder itemBuilder,int itemCount,...})

滑动过程中,图片滑过后,也就是不可见的时候要进行资源的回收,我们这里这里对应的就是纹理的删除。不断的滑动GridView,内存在上升后会处于稳定,不会一直增长。如果快速的来回滑动纹理会反复的创建和删除,这样会有内存的抖动,体验不是很好。

于是,我们维护了一个图片的状态机,状态有None,Loading,Loaded,Wait_Dispose,Disposed。开始加载的时候,状态从None进入Loading,这个时候用户看到的是空白或者是占位图,当数据回调回来会把状态设置为Loaded的这时候会重新build widget树来显示图片icon,当用户滑走的时候状态进入 Wait_Dispose,这时候并不会马上Dispose,如果用户又滑回来则会从Wait_Dispose进入Loaded状态,不会继续Dispose。如果用户没有往回滑则会从Wait_Dispose进入Disposed状态。当进入Disposed状态后,再需要显示该图片的时候就需要重新走加载流程了。

2 相册大图展示:

当点击GridView的某张图片的时候会进行这张图片的大图展示,方便用户查看的更清楚。我们知道相机拍摄的图片分辨率都是很高的,如果完全加载,内存会有很大的开销,所以我们在Decode Bitmap的时候进行了缩放,最高只到1080p。大图展示可以概括为三个步骤。

  • 1 从文件Decode出Bitmap
  • 2 Bitmap转换成为纹理,并释放Bitmap
  • 3 纹理交给Flutter进行展示

在步骤1中,Android原生的Bitmap Decode经验同样适用,先Decode出Bitmap的宽高,然后根据要展示的大小计算出缩放倍数, 然后Decode出需要的Bitmap。

Android相册的图片大多是有旋转角度的,如果不处理直接显示,会出现照片旋转90度的问题,所以需要对Bitmap进行旋转,采用Matrix旋转一张1080p的图片在我的测试机器上面大概需要200ms,如果使用OpenGL的纹理坐标进行旋转,大于只需要10ms左右,所以采用OpenGl进行纹理的旋转是一个较好的选择。

在进行大图预览的时候会进入一个水平滑动的PageView,Flutter的PageView一般来说是不会去主动加载相邻的page的。举个例子,在显示index是5的page的时候index为4,6的page也不会提前创建的。这里有一个取巧的办法,对于PageController的viewportFraction参数我们可以设置成为0.9999。对于前面这个例子,就是在显示index是5的page的时候,index为4,6的page也需要显示0.0001。这样index为4,6的page显示不到1个像素,基本上看不出来:

PageController(viewportFraction=0.9999)

还有另外一种办法,就是在Native侧做预加载。例如:在加载第5张图片的时候,相邻的4,6的图片纹理提前进行加载,当滑动到4,6的时候直接使用缓存的纹理。

纹理缓存后,一个直接的问题:什么时候释放纹理?等到预览页面退出的时候释放所有的纹理显示不是很合适,如果用户一直浏览内存则会无限增长。所以,我们维护了一个5个纹理的LRU缓存,在滑动过程中,最老的纹理会被释放掉。在页面退出的时候整个LRU的缓存会进行销毁。

3 关于内存

相册图片使用GPU纹理,会大幅减少Java堆内存的占用,对整个app的性能有一定的提升。需要注意的是,GPU的内存是有限的需要在使用完毕后及时删除,不然会有内存的泄漏的风险。另外,在Android平台删除纹理的时候需要保证在GPU线程进行,不然删除是没有效果的。

在华为P8,Android5.0上面进行了对比测试,Flutter相册和原native相册总内存占用基本一致,在GridView列表页面,新增最大内存13M左右。它们的区别在于原native相册使用的是Java堆内存,Flutter相册使用的是Native内存。

总结

相册组件API简单、易用,高度可定制。Flutter侧层次分明,有UI订制需求的可以重写Widget来达到目的。另外这是一个不依赖于系统相册的相册组件,自身是完备的,能够和现有的app保持UI、交互的一致性。同时为后面支持更多和相册相关的玩法打好基础。

后续计划

由于我们使用的是GPU纹理,可以考虑支持显示高清4K图片,而且客户端内存不会有太大的压力。但是4k图片的Bitmap转纹理需消耗更多的时间,UI交互上面需要做些loading状态的支持。

组件功能丰富,稳定后,进行开源,回馈给社区。

原文链接
本文为云栖社区原创内容,未经允许不得转载。

一个优秀的可定制化Flutter相册组件,看这一篇就够了相关推荐

  1. 安卓开发培训!没想到一个Handler还有中高级几种问法,看这一篇就够了!

    前言 随着移动网络的不断升级,客户端的网络传输由3G进化到Wifi.4G.5G,且Wifi场景越来越多.虽然网络环境在变好,但也对网络的应用提出了更高的要求,会发现很多大厂都十分重视网络指标,如果技术 ...

  2. 如何低成本实现Flutter富文本,看这一篇就够了!

    作者:闲鱼技术-玄川 背景 闲鱼是国内最早使用Flutter 的团队,作为一个电商App商品详情页是非常重要场景,其中最主要的技术能力是文字混排. 我们面对文本类的需求是复杂而且多变,然而Flutte ...

  3. 机器学习复习6-优化器等 面试看这一篇就妥妥够了

    该文对优化器进行了全面的梳理和整理,包括原理分析,面试看这一篇就妥妥够了! 预备知识 一.正定和半正定矩阵 (了解) 半正定矩阵包括了正定矩阵. 不定矩阵:特征值有正有负 二.牛顿法和拟牛顿法(二阶优 ...

  4. python 初始化一个4维向量_看图学NumPy:掌握n维数组基础知识点,看这一篇就够了...

    摘要:NumPy是Python的最重要的扩展程序库之一,也是入门机器学习编程的必备工具.国外有位程序员讲NumPy的基本运算以图解的方式写下来,让学习过程变得轻松有趣. NumPy是Python的最重 ...

  5. APP的UI自动化测试框架及平台化探索,看完这篇就够了

    一.UI能解决什么问题? 重复性的功能测试及验证 避免疲惫操作时的人为测试遗漏 通过UI自动化操作获取其他测试数据的能力 二.UI的优缺点是什么? 在实际应用中,UI自动化可以帮助我们节省人工测试成本 ...

  6. python中的且的符号and用两个和用一个的区别_Python 中的 or and 运算,看这一篇就够...

    问题出现: Python 中的 or,and 运算,可以写出比较复杂的演算表达式: 看了一些大神的演算规则解释,觉得不是很好理解,有些人甚至理解错了规则. 这里聊一下自己的理解. 其他人是怎么做的: ...

  7. [Golang梦工厂]一个小项目带你学会GIN框架、JWT鉴权、swagger生成接口文档,看这一篇就够了

    前言 哈喽,大家好,我是asong,这是我的第八篇原创文章.听说你们还不会jwt.swagger,所以我带来一个入门级别的小项目.实现用户登陆.修改密码的操作.使用GIN(后台回复Golang梦工厂: ...

  8. 用户体验设计—影响定制化设计的因素

    0 前言 最近在上信息构建这门课(名为信息构建,但感觉叫用户体验设计更好...)老师是研究信息行为.人智交互这块的,所以实验课要求我们先学习一个实际的设计案例,让我们搞懂影响定制化设计的因素.所以这篇 ...

  9. MyBatis简介——半自动的持久化层框架(SQL映射框架)|| 支持定制化sql

    MyBatis简介 1.MyBatis:和数据库进行交互:持久化层框架(SQL映射框架): 1).从原始的JDBC-----dbutils(QueryRunner)-------JdbcTemplat ...

最新文章

  1. 剑指offer-丑数
  2. 增强for循环_增强for循环实际用法
  3. 《sed的流艺术之四》-linux命令五分钟系列之二十四
  4. 12月16日文章排行点评及编辑部训练
  5. Mysql报错130_mysql 突然报错,连接不上
  6. maven项目发布到tomcat里lib包没有发布的问题
  7. mysql命中索引规律
  8. ASP.NET案例--新闻模块设计
  9. Zabbix安装和使用配置小结
  10. html弄多个按钮_如何为HTML / CSS页面添加更多按钮?
  11. 问题六十六:怎么用ray tracing画CSG(Constructive Solid Geometry 构造实体几何)图形
  12. hadoop工作流引擎azkaban
  13. [Android 9.0][RK3399/RK3328] Settings->Factory Reset:点击恢复出场设置崩溃的问题
  14. java虚拟机-d参数配置_Java虚拟机-JVM各种参数配置大全详细
  15. pdf转图片在线转换免费
  16. canal使用过程中的报错
  17. 【老罗笔记】异类(Outliers)
  18. 【原创】超级详细的iPhone铃声制作、导入、设置全过程教程
  19. 笔记本电脑黑屏后如何重装系统,解决笔记本电脑黑屏
  20. 云服务器挖矿病毒解毒方案

热门文章

  1. python在工厂中的运用_在python中使用元类实现工厂设计模式
  2. 哪种语言 连接 oracle,Go语言连接Oracle(就我这个最全)
  3. date类型_Quartz与Date---cron的相互转换
  4. bcp 不能调用where 子句_技术分享 || Mysql中IS NULL、IS NOT NULL不能走索引?
  5. 【学习笔记】第五章——I/O(设备分类、控制方式、软件层次结构、假脱机、缓冲)
  6. python 执行文件内容_python执行文件
  7. linux下rman自动备份,linux 下rman 自动备份
  8. 华人数学家可以“恐怖”到什么程度?
  9. 揭牌!国字头高校正式落户南京!
  10. ​【文末有福利】揭秘阿里中台!一文看懂阿里推荐业务的两项利器