重构天气
时间过得很快,《假装看天气》这个小项目诞生已经有一年了,因为是练手项目,写完之后也没过多的维护,直到有一次在站酷上看到一个天气设计原型,我被她简洁又漂亮的 UI 惊艳到,进而有了重构天气模块的念头。
理想是丰满的,现实总是残酷的。
站酷上的原型只有图片展示,并没有提供原始设计档下载,所以我只能“抄”图片了。由于没有 Mac 设备,自然用不了神器 Sketch,又畏惧于功能强大但复杂的 PS 和 AI,最后我选择了 Figma,一个基于浏览器的 UI 设计工具,上手简单,功能强大。
陆陆续续的花了几个晚上完成了天气场景中背景图片的绘制:
虽然完成的不够精细,但对于一个不懂设计的程序猿,我还挺满意。
万事俱备,只差 coding,正好这段时间在看扔物线的《给高级 Android 工程师的进阶手册》,收获颇多,拿天气模块开刀简直再合适不过了。
重构天气模块主要涉及以下:
- 页面布局重新设计
- 动态天气的绘制
- 多城市切换以及管理
- 新的每日天气趋势图
- 新的空气质量指示图
一、遇到的问题及踩过的坑
1. 动态天气用自定义 View 、SurfaceView 还是 TextureView ?
在纠结这个问题之前,我们先简单的对比下它们:
- View :Android 基础类,内置画布,提供图形绘制、触屏事件、按键事件等,特点是:必须在 UI 主线程内更新画面。
- SurfaceView :本质上也是一个 View,但它与普通 View 不同的是,它的渲染绘制可以放到其他线程中进行,同时使用双缓冲机制,播放视频时画面更流畅。相对的缺点就是,因为不在 View Hierachy 中,它的显示不受 View 的属性控制,所以不能进行平移、缩放等变换。
- TextureView :官方定义是可以直接将视频流或 OpenGL 场景投影其中;与 SurfaceView 不同的是,TextureView 不会创建一个单独的窗口,而是像普通的 View 那样工作,所以它可以进行平移、缩放等变换。相对的缺点就是,必须在硬件加速的窗口中使用,占用内存比 SurfaceView 高,在 Android 5.0 以前在主线程渲染,5.0 以后有单独的渲染线程。
简单对比后,回到问题本身,我们该如何选择?哪个性能更好?
这里又不得不提到一个名词:硬件加速:Android 3.0(API 11) 开始,在绘制 View 的时候支持硬件加速,充分利用 GPU 的特性,使得绘制更加平滑,但是会多消耗一些内存。
由于平时并没有深入了解过这一块的知识,网络上搜寻后找到了一些答案:
- 普通 View 中的 Canvas 是默认开启硬件加速的。
- SurfaceView 和 TextureView 里面
lockCanvas()
方法得到的 Canvas 是没有硬件加速的。 - Android 6.0(API 23) 之后,
android.view.Surface
里新增了lockHardwareCanvas()
方法来提供硬件加速的 Canvas。
所以说了这么多,一个简单的结论就是:如果需要绘制复杂逻辑场景以及视频流等内容,优选 SurfaceView,如果同时需要有动画效果,选 TextureView;一般的自定义控件以及被动刷新的控件,选择普通 View 可以获得更好的性能表现。
这里我们的动态天气选的是 SurfaceView,当然选择普通 View 或 TextureView也是可以的。更多相关资料可以参考以下链接:
- SurfaceView
- TextureView
- 为什么你的 canvas 那么慢?浅析 Android 的 canvas 性能
- 视频画面帧的展示控件 SurfaceView 及 TextureView 对比
- Android 开源弹幕引擎·烈焰弹幕使 使用多种方式(View/SurfaceView/TextureView)实现高效绘制
2. SurfaceView 在某些设备上会出现 ANR 错误
这个问题最初在一台刷了锤子 OS 的 Nexus 5 上出现,百思不得其解,在 Stack Overflow 上找到了一个相同的提问:ANR in SurfaceView on specific devices only — The only fix is a short sleep time
,虽然没找出问题根源,但是找到了一个奇葩的解决方案:
mSurface.unlockCanvasAndPost(canvas);
System.out.print("fuck"); // 是的,没看错,加上这一句就不会报 ANR 错误
3. 天气源引发的思考
App 定位是练手项目,所以数据请求量不是很大,一直使用和风天气,期间有次 key 连续几天超额请求,猜想应该是哪位小伙伴 Fork 项目后直接拿去用了,问题不大,重置下即可。后面把 key 搬到了服务器上,再遇到这种问题就不需要尴尬的重新发布 Apk 了。
重构期间也有考虑过其他天气源,不是收费就是难用。不过为了后期能快速扩展其他天气源,这次还是预留了接口。
和风天气的免费接口,总体来说还算稳定,唯一的缺点就是夜间的时候,逐小时天气没有数据,导致 App 界面下面一截是空白的,貌似这个问题只有更换天气源或者等官方良心发现了,亦或等下次重构了。
二、可能对你有帮助的知识点
如果你有幸看到这篇文章,并给你带来了一点点帮助,那么我会非常开心的。
- 11 种动态天气效果,平滑渐变切换,还算优雅的动画效果
- 一个滑动淡入淡出的 ViewPager 指示器
- 随波逐流的小船儿
- 一个简单粗暴的闪电生成算法
- 一个固定子控件高度比例的 LinearLayout
- 一个新的气温折线图,更加直观
- 一个新的空气质量 AQI 展示控件,附带动画效果
- NestedScrollView 滚动时触发相关子控件动画执行
- Fragment 中
public void setUserVisibleHint(boolean isVisibleToUser)
可见&不可见事件只有在 ViewPager 中使用时才会触发
虽然这次重构难度不是很大,但是里面涉及的知识点还是挺有意思的,比如 SurfaceView 的使用、Canvas 的绘制、Path 贝塞尔曲线、属性动画等等,更多细节可以下载 APK 然后直接参考工程代码。
三、写在最后
App 中还有很多细节待完善,一定也存在着诸多 Bug,任何问题和建议可以在项目 Issues 提出,当然也可以发邮件给我。