时间过得很快,《假装看天气》这个小项目诞生已经有一年了,因为是练手项目,写完之后也没过多的维护,直到有一次在站酷上看到一个天气设计原型,我被她简洁又漂亮的 UI 惊艳到,进而有了重构天气模块的念头。

理想是丰满的,现实总是残酷的。

站酷上的原型只有图片展示,并没有提供原始设计档下载,所以我只能“抄”图片了。由于没有 Mac 设备,自然用不了神器 Sketch,又畏惧于功能强大但复杂的 PS 和 AI,最后我选择了 Figma,一个基于浏览器的 UI 设计工具,上手简单,功能强大。

陆陆续续的花了几个晚上完成了天气场景中背景图片的绘制:

虽然完成的不够精细,但对于一个不懂设计的程序猿,我还挺满意。

万事俱备,只差 coding,正好这段时间在看扔物线的《给高级 Android 工程师的进阶手册》,收获颇多,拿天气模块开刀简直再合适不过了。

重构天气模块主要涉及以下:

  1. 页面布局重新设计
  2. 动态天气的绘制
  3. 多城市切换以及管理
  4. 新的每日天气趋势图
  5. 新的空气质量指示图

一、遇到的问题及踩过的坑

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也是可以的。更多相关资料可以参考以下链接:

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 界面下面一截是空白的,貌似这个问题只有更换天气源或者等官方良心发现了,亦或等下次重构了。

二、可能对你有帮助的知识点

如果你有幸看到这篇文章,并给你带来了一点点帮助,那么我会非常开心的。

  1. 11 种动态天气效果,平滑渐变切换,还算优雅的动画效果
  2. 一个滑动淡入淡出的 ViewPager 指示器
  3. 随波逐流的小船儿
  4. 一个简单粗暴的闪电生成算法
  5. 一个固定子控件高度比例的 LinearLayout
  6. 一个新的气温折线图,更加直观
  7. 一个新的空气质量 AQI 展示控件,附带动画效果
  8. NestedScrollView 滚动时触发相关子控件动画执行
  9. Fragment 中 public void setUserVisibleHint(boolean isVisibleToUser) 可见&不可见事件只有在 ViewPager 中使用时才会触发

虽然这次重构难度不是很大,但是里面涉及的知识点还是挺有意思的,比如 SurfaceView 的使用Canvas 的绘制Path 贝塞尔曲线属性动画等等,更多细节可以下载 APK 然后直接参考工程代码。

三、写在最后

App 中还有很多细节待完善,一定也存在着诸多 Bug,任何问题和建议可以在项目 Issues 提出,当然也可以发邮件给