当前位置: 首页 > Web前端 > HTML

Flutter常见异常分析

时间:2023-03-28 15:10:53 HTML

前言笔者在上一篇《百瓶中哨兵的实现》中主要阐述了方案选择和实现两大方面。在本文中,我们主要针对百瓶中哨兵的实现,分析项目实现中遇到的问题。本文分析的主要问题主要有以下几类(FlutterSDK版本为1.22.6,DartSDK版本为2.10.5):NoSuchMethodErrorFlutter官方bug(已修复)StateErrorNetworkError(DNS)NoSuchMethodError问题1问题描述:清单,String等类型数据判断为空,直接使用xxx.isNotEmpty,不判断是否为null,导致NoSuchMethodError:ThegetterisNotEmptywascallednull。问题截图:解决方法://问题代码if(timeEndList.isNotEmpty){...}//解决方法staticboolisNotNullOrEmpty(Iterableiterable)=>iterable!=null&&iterable.isNotEmpty;if(IterableUtils.isNotNullOrEmpty(timeEndList)){...}在进行空判断处理时,需要先判断是否为null,再使用isNotEmpty进行判断,避免此类错误。考虑到我们会用到很多类似的判断,所以我们可以封装同类型的数据判断方法,避免每次使用都要重新写一遍。问题2问题描述:这里是使用Future.wait并发请求多个API,并在第二个API中设置超时时间。因为第二次API请求超时,在后续的响应处理中,没有处理空异常判断,导致获取码失败。问题截图:解决方案://问题代码if(res[1].code==HttpCode.ok){...}//解决方案if(res[1].code==HttpCode.ok){...}当使用Future.wait并发请求多个API时,如果设置了超时处理,要考虑API请求超时失败的问题,尽量避免此类问题。问题3问题描述:当我们需要获取Widget上下文关联的RenderBox的大小或位置时,出现错误。问题截图:解决方法://问题代码最终偏移位置=renderBox.localToGlobal(Offset.zero);ctx.dispatch(MallGoodsDetailActionCreator.setDetailsOffsetYAction(postion.dy));}//解决方案if(IterableUtils.isNotNullOrEmpty(ctx.state.details)==true){WidgetsBinding.instance.addPostFrameCallback((_){finalRenderBoxrenderBox=ctx.state.detailsKey.currentContext.findRenderObject();finalOffsetposition=renderBox.localToGlobal(Offset.zero);ctx.dispatch(MallGoodsDetailActionCreator.setDetailsOffsetYAction(postion.dy));});}出现上述问题的原因是上下文没有关联到我们的状态。如果我们想避免这种情况,我们可以在Widget渲染完成后获取RenderBox的大小或者位置。Flutter官方bug(已修复)问题描述:使用NestedScrollView组件时,由于position.minScrollExtent可以为空,在生产环境运行时偶尔会出现NoSuchMethodErrornested_scroll_view.dartin_NestedScrollCoordinator.hasScrolledBodyNoSuchMethodError:Themethod'>'wascalledon无效的。Receiver:nullTriedcalling:>()该问题已经正式解决并合并到master分支。问题截图:那么这个问题是怎么发生的呢?官方的解释是:scheduleAttachRootWidget会调用_firstBuild并新建一个空像素的_NestedScrollPosition;FocusManager会安排一个微任务;完成firstBuild再刷新microTask,NestedScrollView又脏了;scheduleWarmUpFrame将重建脏节点并触发异常(_NestedScrollPosition仅在布局后可用)。解决方法://问题代码boolgethasScrolledBody{for(final_NestedScrollPositionpositionin_innerPositions){assert(position.minScrollExtent!=null&&position.pixels!=null);如果(position.pixels>position.minScrollExtent){returntrue;}}returnfalse;}//解决方案boolgethasScrolledBody{for(final_NestedScrollPositionpositionin_innerPositions){if(!position.hasContentDimensions||!position.hasPixels){continue;}elseif(position.pixels>position.minScrollExtent){returntrue;}}returnfalse;}StateError问题描述:当我们使用list.firstWhere时,通常会出现BadState:Noelement这样的问题。问题截图:解决方案://问题代码MapgetInitialSkuById(StringskuId,List>skuList){finalMapselectedKeyValue={};finalMapselectedSku=skuList.firstWhere((MapskuItem)=>skuItem['id']==skuId);if(selectedSku['stockNum']>0){selectedSku.forEach((Stringk,dynamicv){if(k.contains('keyStr')){selectedKeyValue[k]=v;}});}returnselectedKeyValue;}//解决方案MapgetInitialSkuById(StringskuId,List>skuList){finalMapselectedKeyValue={};最终MapselectedSku=skuList.firstWhere((MapskuItem)=>skuItem['id']==skuId,orElse:null,);如果(selectedSku!=null&&selectedSku['stockNum']>0){selectedSku.forEach((Stringk,dynamicv){if(k.contains('keyStr')){selectedKeyValue[k]=v;}});}returnselectedKeyValue;}我们在使用list.firstWhere的时候,通常有条件匹配不上的时候,就非常有必要使用orElse来处理这种情况。在这种情况下,以下代码根据条件过滤'green'的结果值。如果没有,则返回'Nomatchingcolorfound',结果输出为For:Nomatchingcolorfound。finalListlist=['red','yellow','pink','blue'];最终字符串项=列表。firstWhere((Stringelement)=>element=='green',orElse:()=>'找不到匹配的颜色',);print(item);////没有找到匹配的颜色如果没有写orElse,会抛出异常:Unhandledexception:Badstate:Noelement。当然,如果你是Null安全版本,可以直接使用firstWhereOrNull方法进行处理。我们来对比一下firstWhere和firstWhereOrNull的源码:EfirstWhere(booltest(Eelement),{EorElse()?}){for(Eelementinthis){if(test(element))returnelement;}if(orElse!=null)返回orElse();抛出IterableElementError.noElement();}T?firstWhereOrNull(boolFunction(Telement)test){for(varelementinthis){if(test(element))returnelement;}returnnull;}firstWhere会先匹配符合条件的结果,如果不匹配,再处理orElse,如果不存在orElse,则抛出异常;firstWhereOrNull就简单多了,如果没有匹配到符合条件的值,就直接返回null。NetworkError(DNS)网络错误是导致网络请求失败的错误情况,每个网络错误都有一个类型,是一个字符串,每个网络错误都有一个阶段,描述错误发生在哪个阶段:dns:Errorsthatoccur在DNS解析期间;连接:建立安全连接期间发生的错误;应用:请求和响应传输过程中发生的错误;问题描述:当客户端向serviceticket发起网络请求时,会经过DNS解析的过程一般是传统的基于DNS协议向运营商的LocalDNS发起解析请求的方式。但是,在这种情况下,可能会出现域名劫持和跨网访问,导致域名解析异常。解决方案:那么,如果我们的App在发起网络请求时,发现DNS解析失败,应该怎么办呢?当然,我们可以接入阿里云的云解析DNS服务或者腾讯移动的解析HTTPDNS等服务,更有效的保证APP和小程序的正常访问。一起来回顾一下DNS相关知识:什么是DNS域名层次结构DNS层次结构DNS解析过程一个与IP地址映射的分布式数据库,可以让人们更方便的上网,而不必去记住那些可以查询的IP号码串由机器阅读。域名层级结构由于互联网用户众多,互联网在命名时都采用层级树状结构的命名方式。任何连接到互联网的主机或路由器都有一个独特的层次结构(域名)。一个域名又可以划分为子域,子域又可以进一步划分为子域的子域,从而形成顶级域名、主域名、子域名等。“.com”为顶级域名;“baiping.com”为主域名(也称托管一级域名),主要指企业页面名称;“example.baiping.com”为子域名(也称托管二级域名)域名;“www.example.baiping.com”是子域的子域(也称为托管三级域)。DNS层次结构域名是一个层次结构,域名的DNS服务器也是一个对应的层次结构。有了域名结构,还需要一个域名DNS服务器来解析域名,需要全世界的域名DNS服务器来解析。域名DNS服务器实际上就是一台装有域名系统的主机。DNS解析过程中DNS查询的结果通常缓存在本地域名服务器中。如果本地域名服务器有缓存,则跳过后面的DNS查询步骤,很快返回解析结果。没有本地名称服务器缓存的DNS查找所需的8个步骤:用户在Web浏览器中键入“example.com”,本地名称服务器开始递归查找。本地域名服务器采用迭代查询的方式查询根域名服务器;根域名服务器告诉本地域名服务器接下来要查询的顶级域名server.comTLD(顶级域名服务器)的IP地址;服务器.comTLD进行查询;.com顶级域名服务器通知本地域名服务器接下来查询example.com权威域名服务器的IP地址;本地域名服务器向example.com权威域名服务器发送查询;example.com权威域名服务器告诉本地域名服务器查询到的主机IP地址;本地域名服务器最终以查询的IP地址响应Web浏览器。一旦DNS查询的8个步骤返回了example.com的IP地址,浏览器就可以对该网页进行请求;浏览器向IP地址发出HTTP请求;该IP的Web服务器返回要在浏览器中呈现的网页。术语解释:DNSResolve:指本地域名服务器,是DNS查找的第一站,负责处理来自DNS服务器的初始请求。运营商ISP分配的DNS,Google8.8.8.8等都属于DNSResolver;根服务器:指根域名服务器。当本地域名服务器无法在本地查询解析结果时,第一步会查询并获取顶级域名服务器的IP地址;递归查询:是指DNS服务器在收到用户发起的请求后,必须向用户返回准确的查询结果。如果DNS服务器在本地没有存储相应的信息,则服务器需要查询其他服务器,并将返回的查询结构提交给用户;迭代查询:是指DNS服务器收到用户发起的请求时,不直接回复,而是告诉另一台DNS服务器的地址,用户再向这台DNS服务器提交请求,如此依次重复直到返回查询结果。综上所述,以上四种异常是我们在写代码的前期经常遇到的问题。通过以上四种异常的分析,我们可以得到一些经验总结。在后续的开发中,我们可以在这些总结的基础上进行改进,从而更好的解决问题。更多精彩,敬请关注我们的公众号《百瓶科技》,不定期有福利哦!