内容简介:开发图片、视频相关功能时,相册是一个绕不开的话题,因为大家基本都有从相册获取图片或者视频的需求。最直接的方式是调用系统相册接口,虽然基本功能是满足的,却无法满足一些高级功能,例如自定义UI、多选图片等。闲鱼这套相册组件API使用简单,功能丰富灵活,具有较高的订制性。业务方可以选择完全接入组件,也可以选择在组件上面进行UI定制。Flutter做UI展现层,具体的数据由各Native平台提供。这种模式,天然从工程上把UI代码和数据代码进行了隔离。我们在开发一个native组件的时候常常会使用MVC架构。Flut
开发图片、视频相关功能时,相册是一个绕不开的话题,因为大家基本都有从相册获取图片或者视频的需求。最直接的方式是调用系统相册接口,虽然基本功能是满足的,却无法满足一些高级功能,例如自定义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相册组件,在低端机上面体验会有所改观。
一些难点
分页加载
相册列表需要加载大量图片,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状态后,再需要显示该图片的时候就需要重新走加载流程了。
相册大图展示
当点击GridView的某张图片的时候会进行这张图片的大图展示,方便用户查看的更清楚。我们知道相机拍摄的图片分辨率都是很高的,如果完全加载,内存会有很大的开销,所以我们在Decode Bitmap的时候进行了缩放,最高只到1080p。Android原生的Bitmap Decode经验同样适用,先Decode出Bitmap的宽高,然后根据要展示的大小计算出缩放倍数, 然后Decode出需要的Bitmap。
Android相册的图片大多是有旋转角度的,如果不处理直接显示,会出现照片旋转90度的问题,所以需要对Bitmap进行旋转,采用Matrix旋转一张1080p的图片在我的测试机器上面大概需要200ms,如果使用OpenGL的纹理坐标进行旋转,大于只需要10ms左右,所以采用OpenGl进行纹理的旋转是一个较好的选择。
在进行大图预览的时候会进入一个水平滑动的PageView,Flutter的PageView一般来说是不会去主动加载相邻的page的。这里有一个取巧的办法,对于PageController的viewportFraction参数我们可以设置成为0.9999,如下所示:
PageController(viewportFraction=0.9999) 复制代码
还有另外一种办法,就是在Native侧做预加载。例如:在加载第5张图片的时候,相邻的4,6的图片纹理提前进行加载,当滑动到4,6的时候直接使用缓存的纹理。
内存
相册图片使用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状态的支持。
组件功能丰富,线上稳定运行一段时间后将会开源回馈给社区。大家敬请关注闲鱼技术公众号。
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
The Web Designer's Idea Book, Vol. 2
Patrick McNeil / How / 2010-9-19 / USD 30.00
Web Design Inspiration at a Glance Volume 2 of The Web Designer's Idea Book includes more than 650 new websites arranged thematically, so you can easily find inspiration for your work. Auth......一起来看看 《The Web Designer's Idea Book, Vol. 2》 这本书的介绍吧!