Flutter 常见异常分析

前言

在这里插入图片描述

本文中主要分析的问题主要包括以下几大类(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。

问题截图:

在这里插入图片描述
在这里插入图片描述

解决方案:

// 问题代码
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。

问题截图:
在这里插入图片描述
在这里插入图片描述

解决方案:

// 问题代码
if (res[1].code == HttpCode.ok) {
  ...
}

// 解决方案
if (res[1]?.code == HttpCode.ok) {
  ...
}

在使用了 Future.wait 并发请求多个 API ,如果有设置超时处理,要考虑到 API 请求超时失败的问题尽量避免这种问题发生。

问题三

问题描述:

当我们需要获取到 与 Widget 上下文相关联的 RenderBox 尺寸或者位置时,发生错误。

问题截图:

在这里插入图片描述
在这里插入图片描述

解决方案:

// 问题代码
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 这类问题。

问题截图:

在这里插入图片描述
在这里插入图片描述

解决方案:

// 问题代码
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” 是子域名的子域(也可称为托管三级域名)。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-SXC03vZw-1681710025335)(https://segmentfault.com/img/remote/1460000042010226 “sentry_network_error_2”)]

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 服务器提交请求,这样依次反复,直到返回查询结果。
    在这里插入图片描述

总结

以上四种异常是我们在编写代码初期经常遇到的问题,通过对以上四种异常的分析,我们可以得到一些经验总结,在后续的开发中,我们可以根据这些总结,进行改进,以便更好的解决问题。

需要更多Flutter学习资料,或者与 Android相关的资料都可以扫码免费领取!

Flutter技术解析与实战》

目录

img

第一章 混合工程

​ ● Flutter工程体系

​ ● 混合工程改造实战

​ ● 混合工程与持续集成

​ ● 快速完成混合工程搭建

​ ● 使用混合栈框架开发

img

第二章 能力增强

​ ● 基于原生能力的插件扩展

​ ● 基于外接纹理的同层渲染

​ ● 多媒体能力扩展实践

​ ● 富文本能力应用实践

img

第三章 业务架构设计

​ ● 应用框架设计实践

​ ● 轻量级动态化渲染引擎的设计

​ ● 面向切面编程的设计实践

​ ● 高性能的动态模板渲染实践

img

第四章 数据统计与性能

​ ● 数据统计框架的设计

​ ● 性能稳定性监控方案的设计

​ ● 高可用框架的设计与实践

​ ● 跨端方案性能对比实践

img

第五章 企业级应用实战

​ ● 基于Flutter的端结构演进与创新

​ ● Flutter与FaaS云端一体化架构

img

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
flutter-app逆向分析是指对于一个使用flutter框架开发的应用进行逆向工程分析。逆向工程是通过分析应用的代码、二进制文件等来了解其内部实现细节。 首先,我们需要获取该应用的安装包文件(APK或IPA文件),然后进行解包操作,将其转换为可读取的文件目录结构。 接下来,我们可以使用一些工具来提取应用的资源文件、代码文件等。对于flutter-app来说,可以提取出dart文件,这是flutter的主要代码文件,其包含了应用的逻辑实现。 通过阅读dart文件,我们可以了解应用的代码结构、数据模型、界面设计等。可以分析应用的逻辑实现方法,包括各种函数、类、方法的调用关系。 同时,还可以通过分析相关配置文件、资源文件等来了解应用的各种设置、资源加载方式等。 在逆向过程,还可以使用一些调试工具来进一步了解应用的运行机制。例如,hook工具可以拦截应用的函数调用,并捕获输入输出数据,用于进一步分析。 逆向分析的目的可以有很多,比如了解应用的工作原理、发现潜在的漏洞或安全问题、提供参考用于自己的开发等。 需要注意的是,逆向分析需要遵守法律规定。未经授权的逆向分析可能侵犯他人的知识产权,涉及到隐私等方面的问题。因此,在进行逆向分析之前,应该了解并遵守当地相关法律法规,避免产生法律纠纷。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值