当前位置:网站首页>Flutter 常见异常分析
Flutter 常见异常分析
2022-08-02 20:07:00 【东方睡衣】
前言
在上篇 「Sentry 在百瓶的落地实践」中,笔者主要从方案选型 & 落地实践两个大的方面进行了阐述,本篇文章我们主要对 Sentry 在百瓶的落地实践中遇到的问题进行分析。本文中主要分析的问题主要包括以下几大类(Flutter SDK 版本为 1.22.6,Dart SDK 版本为 2.10.5):
- NoSuchMethodError
- Flutter 官方 bug (已经修复)
- StateError
- NetworkError(DNS)
NoSuchMethodError
问题一
问题描述:
在进行 List 、String 等类型数据判空处理时,直接使用 xxx.isNotEmpty,没有进行判断是否为 null,导致 NoSuchMethodError:The getter isNotEmpty was called null。
问题截图:
<img src=“https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/860fb4fd0f3148049d2e438d383308a8~tplv-k3u1fbpfcp-zoom-in-crop-mark:1956:0:0:0.image) <img src=“https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/037d0dac18234bd3b73de40a9fdf0fc2~tplv-k3u1fbpfcp-zoom-in-crop-mark:1956:0:0:0.image” style=“margin: auto” /” style=“margin: auto” />
解决方案:
// 问题代码
if(timeEndList.isNotEmpty){...
}
// 解决方案
static bool isNotNullOrEmpty<E>(Iterable<E> iterable) => iterable != null && iterable.isNotEmpty;
if (IterableUtils.isNotNullOrEmpty(timeEndList)){...
}
在进行判空处理时,需要首先判断是否为 null,然后再使用 isNotEmpty 进行判断,避免这种类型错误,考虑到我们在项目中会使用大量类似判断,所以我们可以对同一类型的数据判断方法进行封装,避免每处使用都要再去写一遍。
问题二
问题描述:
这里是使用了 Future.wait 并发请求多个 API,并且在第二个 API 设置超时,由于第二个 API 请求超时,在后续处理响应时,没有处理空异常判断导致获取不到 code。
问题截图:
<img src=“https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/a5f359fcd57a4152986e8a853b7500fc~tplv-k3u1fbpfcp-zoom-in-crop-mark:1956:0:0:0.image) <img src=“https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/f85d7d219b99495980cafd89bac3301d~tplv-k3u1fbpfcp-zoom-in-crop-mark:1956:0:0:0.image” style=“margin: auto” /” style=“margin: auto” />
解决方案:
// 问题代码
if (res[1].code == HttpCode.ok) {...
}
// 解决方案
if (res[1]?.code == HttpCode.ok) {...
}
在使用了 Future.wait 并发请求多个 API ,如果有设置超时处理,要考虑到 API 请求超时失败的问题尽量避免这种问题发生。
问题三
问题描述:
当我们需要获取到 与 Widget 上下文相关联的 RenderBox 尺寸或者位置时,发生错误。
问题截图:
<img src=“https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/adff6e8c6cd64df283d1bad7be586213~tplv-k3u1fbpfcp-zoom-in-crop-mark:1956:0:0:0.image) <img src=“https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/8798fc5027414ce8b7f2c936b97f9047~tplv-k3u1fbpfcp-zoom-in-crop-mark:1956:0:0:0.image” style=“margin: auto” /” style=“margin: auto” />
解决方案:
// 问题代码
if (IterableUtils.isNotNullOrEmpty(ctx.state.details) == true) {final RenderBox renderBox = ctx.state.detailsKey.currentContext.findRenderObject();final Offset postion = renderBox.localToGlobal(Offset.zero);ctx.dispatch(MallGoodsDetailActionCreator.setDetailsOffsetYAction(postion.dy));
}
// 解决方案
if (IterableUtils.isNotNullOrEmpty(ctx.state.details) == true) {WidgetsBinding.instance.addPostFrameCallback((_) {final RenderBox renderBox = ctx.state.detailsKey.currentContext.findRenderObject();final Offset postion = renderBox.localToGlobal(Offset.zero);ctx.dispatch(MallGoodsDetailActionCreator.setDetailsOffsetYAction(postion.dy));});
}
发生以上问题的原因是,上下文并没有与我们的 state 进行关联,如果要避免这种情况发生,我们可以在 Widget 渲染完毕后再进行获取 RenderBox 尺寸或者位置。
Flutter 官方 bug (已经修复)
问题描述:
在使用 NestedScrollView 组件时,由于 position.minScrollExtent 可以为空 ,在生产环境中运行会偶现 NoSuchMethodError nested_scroll_view.dart in _NestedScrollCoordinator.hasScrolledBody NoSuchMethodError: The method ‘>’ was called on null. Receiver: null Tried calling: >() 这个问题,目前官方已经解决并且合并到 master 分支。
问题截图:
那么这个问题是如何发生的呢?用官方的原文来解释就是:
1.scheduleAttachRootWidget 将调用 _firstBuild 并新建一个具有空像素的 _NestedScrollPosition;
2.FocusManager 将安排一个微任务;
3.完成 firstBuild 然后刷新 microTask,NestedScrollView 又 dirty 了;
4.scheduleWarmUpFrame 将重建 dirty 节点并触发异常(_NestedScrollPosition 仅在布局后可用)。
解决方案:
// 问题代码
bool get hasScrolledBody {for (final _NestedScrollPosition position in _innerPositions) {assert(position.minScrollExtent != null && position.pixels != null);if (position.pixels > position.minScrollExtent) {return true;}}return false;
}
// 解决方案
bool get hasScrolledBody {for (final _NestedScrollPosition position in _innerPositions) {if (!position.hasContentDimensions || !position.hasPixels) {continue;} else if (position.pixels > position.minScrollExtent) {return true;}}return false;
}
StateError
问题描述:
当我们在使用 list.firstWhere 的时候,通常会引发 Bad State: No element 这类问题。
问题截图:
<img src=“https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/2b5e5d26b75e4cd49a747909291b74ab~tplv-k3u1fbpfcp-zoom-in-crop-mark:1956:0:0:0.image) <img src=“https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/2f7dc543f513434eba6ad762d27298b8~tplv-k3u1fbpfcp-zoom-in-crop-mark:1956:0:0:0.image” style=“margin: auto” /” style=“margin: auto” />
解决方案:
// 问题代码
Map<String, String> getInitialSkuById(String skuId, List<Map<String, dynamic>> skuList) {final Map<String, String> selectedKeyValue = <String, String>{};final Map<String, dynamic> selectedSku =skuList.firstWhere((Map<String, dynamic> skuItem) => skuItem['id'] == skuId);if (selectedSku['stockNum'] > 0) {selectedSku.forEach((String k, dynamic v) {if (k.contains('keyStr')) {selectedKeyValue[k] = v;}});}return selectedKeyValue;
}
// 解决方案
Map<String, String> getInitialSkuById(String skuId, List<Map<String, dynamic>> skuList) {final Map<String, String> selectedKeyValue = <String, String>{};final Map<String, dynamic> selectedSku = skuList.firstWhere((Map<String, dynamic> skuItem) => skuItem['id'] == skuId,orElse: null,);if (selectedSku != null && selectedSku['stockNum'] > 0) {selectedSku.forEach((String k, dynamic v) {if (k.contains('keyStr')) {selectedKeyValue[k] = v;}});}return selectedKeyValue;
}
在我们使用 list.firstWhere 的时候,通常有匹配不到条件的时候,这个时候就非常有必要使用 orElse 来进行处理这种情况。
下面的代码根据条件筛选为 ‘green’ 的结果值,如果没有的话就返回 ‘No matching color found’,结果输出为:No matching color found。
final List<String> list = <String>['red', 'yellow', 'pink', 'blue'];
final String item = list.firstWhere((String element) => element == 'green',orElse: () => 'No matching color found',
);
print(item); // // No matching color found
如果没有写 orElse 的情况下会抛出异常: Unhandled exception: Bad state: No element。当然如果在 Null safety 版本下,可以直接使用 firstWhereOrNull 方法来进行处理。 下面我们来对比一下 firstWhere 和 firstWhereOrNull 的源码:
E firstWhere(bool test(E element), {E orElse()?}) {for (E element in this) {if (test(element)) return element;}if (orElse != null) return orElse();throw IterableElementError.noElement();
}
T? firstWhereOrNull(bool Function(T element) test) {for (var element in this) {if (test(element)) return element;}return null;
}
firstWhere 会首先进行匹配符合条件的结果,如果没有匹配到,再进行处理 orElse ,如果没有 orElse ,就会抛出异常;firstWhereOrNull 就简单的多了,如果没有匹配到符合条件的值,就会直接返回 null。
NetworkError(DNS)
网络错误是导致网络请求失败的错误条件,每个网络错误都有一个类型,它是一个字符串,每个网络错误都有一个阶段,它描述了错误发生在哪个阶段:
1.dns:DNS 解析过程中发生的错误;
2.connection:安全连接建立期间发生的错误;
3.application:请求和响应传输过程中发生的错误;
问题描述:
在客户端向服务单发起网络请求时,都会经过 DNS 解析的过程,一般情况下都是基于 DNS 协议向运营商 Local DNS 发起解析请求的传统方式,但是这种情况下可能会出现域名劫持和跨网访问的问题,造成域名解析异常。
解决方案:
那么,如果我们的 App 在发起网络请求的时候,发现 DNS 解析失败,我们应该怎么办?当然我们可以接入阿里云云解析 DNS 服务或者腾讯移动解析 HTTP DNS 等服务来更加有效的保障 App、小程序正常访问。
下面我们来一起回顾一下 DNS 相关的知识:
- 什么是 DNS
- 域名分层结构
- DNS 分层结构
- DNS 解析过程
DNS
DNS 是域名系统 (Domain Name System) 的缩写,是因特网的一项核心服务,它作为可以将域名和 IP 地址互相映射的一个分布式数据库,能够使人更方便的去访问互联网,而不用去记住能够被机器读取的 IP 数串。
域名分层结构
由于因特网的用户数量过多,所有因特网在命名时采用的是层次树状结构的命名方法。 任何一个连接在因特网上的主机或路由器,都有一个唯一的层次结构(域名)。 域名可以划分为各个子域,子域还可以继续划分为子域的子域,这样就形成了顶级域名、主域名、子域名等。
1.“.com” 是顶级域名;
2.“baiping.com” 是主域名(也可称托管一级域名),主要指企页名;
3.“example.baiping.com” 是子域名(也可称为托管二级域名);
4.“www.example.baiping.com” 是子域名的子域(也可称为托管三级域名)。
DNS 分层结构
域名是分层结构,域名 DNS 服务器也是对应的层级结构。有了域名结构,还需要有域名 DNS 服务器去解析域名,且是需要由遍及全世界的域名 DNS 服务器去解析,域名 DNS 服务器实际上就是装有域名系统的主机。
DNS 解析过程
DNS 查询的结果通常会在本地域名服务器中进行缓存,如果本地域名服务器中有缓存的情况下,则会跳过如下 DNS 查询步骤,很快返回解析结果。本地域名服务器没有缓存的情况下,DNS 查询所需的 8 个步骤:
1.用户在 Web 浏览器中输入 “example.com”,则由本地域名服务器开始进行递归查询。
2.本地域名服务器采用迭代查询的方法,向根域名服务器进行查询;
3.根域名服务器告诉本地域名服务器,下一步应该查询的顶级域名服务器 .com TLD(顶级域名服务器)的 IP 地址;
4.本地域名服务器向顶级域名服务器 .com TLD 进行查询;
5…com TLD 服务器告诉本地域名服务器,下一步查询 example.com 权威域名服务器的 IP 地址;
6.本地域名服务器向 example.com 权威域名服务器发送查询;
7.example.com 权威域名服务器告诉本地域名服务器所查询的主机 IP 地址;
8.本地域名服务器最后把查询的IP地址响应给 Web 浏览器。一旦 DNS 查询的 8 个步骤返回了 example.com 的 IP 地址,浏览器就能够发出对网页的请求;
9.浏览器向 IP 地址发出 HTTP 请求;
10.该 IP 处的 Web 服务器返回要在浏览器中呈现的网页。
名词解释:
1.DNS Resolve: 指本地域名服务器,它是 DNS 查找中的第一站,是负责处理发出初始请求的 DNS 服务器。运营商 ISP 分配的 DNS、谷歌 8.8.8.8 等都属于 DNS Resolver;
2.Root Server:指根域名服务器,当本地域名服务器在本地查询不到解析结果时,则第一步会向它进行查询,并获取顶级域名服务器的 IP 地址;
3.递归查询:是指 DNS 服务器在收到用户发起的请求时,必须向用户返回一个准确的查询结果。如果 DNS 服务器本地没有存储与之对应的信息,则该服务器需要询问其他服务器,并将返回的查询结构提交给用户;
4.迭代查询:是指 DNS 服务器在收到用户发起的请求时,并不直接回复查询结果,而是告诉另一台 DNS 服务器的地址,用户再向这台 DNS 服务器提交请求,这样依次反复,直到返回查询结果。
总结
以上四种异常是我们在编写代码初期经常遇到的问题,通过对以上四种异常的分析,我们可以得到一些经验总结,在后续的开发中,我们可以根据这些总结,进行改进,以便更好的解决问题。
边栏推荐
猜你喜欢
Leetcode刷题——字符串相加相关题目(415. 字符串相加、面试题 02.05. 链表求和、2. 两数相加)
Qt提升自定义控件,找不到头文件
Redis集群配置
Parse the commonly used methods in the List interface that are overridden by subclasses
顺序查找和折半查找,看这篇就够了
Triacetin是什么化学材料
Digital twins help visualize the construction of smart cities
第七章 噪声
姑姑:给小学生出点口算题
.NET如何快速比较两个byte数组是否相等
随机推荐
.NET如何快速比较两个byte数组是否相等
传感器工作原理
Xcode13.1运行工程报错fatal error: ‘IFlyMSC/IFly.h‘ file not found的问题
LeetCode:622. 设计循环队列【模拟循环队列】
使用位运算实现加减乘除(+、-、*、/)及比较器的用法
【 LeetCode 】 1374. Generate each character string is an odd number
Golang source code analysis: time/rate
【数据分析】:什么是数据分析?
Geoip2 - golang golang source code analysis
信息学奥赛一本通(1256:献给阿尔吉侬的花束)
Implement fashion_minst clothing image classification
你是几星测试/开发程序员?技术型选手王大拿......
OP-5,输入/输出信号范围-一信号处理能力
Qt提升自定义控件,找不到头文件
Which thread pool does Async use?
Thread线程类基本使用(下)
供电系统电气图
SQL Server实现group_concat功能
golang源码分析:time/rate
Leetcode刷题——23. 合并K个升序链表