flutter开发小技巧

Uri对象的使用

直接使用字符串来拼接 URI 地址需要关注地址中拼接的每个部分的合法性,并且在处理复杂逻辑时需要更冗长的处理,如果变量包含非法字符(如中文),整个地址非法。

如:在路由跳转中使用

Dart 复制代码
方式一:使用Uri对象(推荐)
Uri address = Uri(path: path, queryParameters: queryParameters);
NavigatorUtils.push(context, address.toString());


方式二:参数处理,不推荐
NavigatorUtils.push(context,
        '${Routes.webViewPage}?title=${Uri.encodeFull(title)}&url=${Uri.encodeComponent(url)}');

类型转换

建议使用 is 而不是 as 来进行类型转换。 is 运算符允许更安全地进行类型检查,如果转换失败,也不会抛出异常。as 进行类型失败会抛出异常。如:

Dart 复制代码
  if (animal is Bird) {
    animal.fly();
  } else {
    print('转换失败');
  }

  (animal as Animal).eat('meat'); // 强制类型转换一旦失败就会抛异常

ChangeNotifier 使用

1.ChangeNotifier 的属性访问或方法调用:

ChangeNotifier 及其子类在 dispose 之后将不可使用,dispose 后访问其属性(hasListener)或方法(notifyListeners)时均不合法,在 dispose 后访问属性或调用方法通常出现在异步调用的场景下,由其是在网络请求之后刷新界面。典型场景如下:

Dart 复制代码
class PageNotifier extends ChangeNotifier { 
  dynamic pageData;
  
 Future<voud> beginRefresh() async {
    final response = await API.getPageContent();
    if (!response.success) return;
    pageData = response.data;
    // 接口返回之后此实例可能被 dispose,从而导致异常
    notifyListeners();
  }
}

解决:

Dart 复制代码
// 统一定义如下 mixin
mixin Disposed on ChangeNotifier {
  bool _disposed = false;
  
  bool get hasListeners {
    if (_disposed) return false;
    return super.hasListeners;
  }

  @override
  void notifyListeners() {
    if (_disposed) return;
    super.notifyListeners();
  }

  @override
  void dispose() {
    _disposed = true;
    super.dispose();
  }
}

// 在必要的 ChangeNotifier 子类混入 Disposed
class PageNotifier extends ChangeNotifier with Disposed { 

 Future<voud> beginRefresh() async {
    final response = await API.getPageContent();
    if (!response.success) return;
    pageData = response.data;
    // 异步调用不会异常
    notifyListeners(); 
  }

}

2.ChangeNotifier 禁止实例复用:

单个 ChangeNotifier 实例在多个独立的组件或页面中使用会造成潜在的问题:复用的实例一旦在某个组件中被意外 dispose 之后就无法使用,从而影响其它组件展示逻辑并且这种影响是全局的

Dart 复制代码
@override
void initState() {
  super.initState();
  // 添加监听
  ShoppingCart.instance.addListener(_update);
}

@override
void dispose() {
  // 正确移除监听
  ShoppingCart.instance.removeListener(_update);
  // 在组件中这样移除监听,将产生致命影响
  // ShoppingCart.instance.dispose();
  super.dispose();
}

解决:因此在 Flutter 开发中应禁止 ChangeNotifier 实例对外跨组件直接复用,如需跨组件复用应借助providerget_it 等框架将 ChangeNotifer 子类实例对象置于顶层;

Dart 复制代码
void main() {
  runApp(
    MultiProvider(
      providers: [
        Provider<Something>.value(ShoppingCart.instance),
      ],
      child: const MyApp(),
    )
  );
}

如果你非得要 「「单例化」」 自定义 ChangeNotifier 子类实例,记得一定要重新 dispose 函数。

Controller 使用

在 Flutter 中大多数 Controller 都直接或间接继承自 ChangeNotifier。为使代码逻辑更加严谨,增强整个代码的健状性,建议:所有 Controller 需要显式调用 dispose 方法,所有自定义 Controller 需要重写或者添加 dispose 方法。

Dart 复制代码
// ScrollController 源码
class ScrollController extends ChangeNotifier {
//...
}

// 自定义 Controller 需要添加 dispose 方法
class MyScrollController {
 ScrollController scroll = ScrollController();

 // 添加 dispose 方法
  void dispose() {
    scroll.dispose();
  }
}

避免资源释放遗忘

在 Flutter 中有很多需要主动进行资源释放的类型,包含但不限于:TimerStreamSubscriptionScrollControllerTextEditingController等,另外很多第三方库存在需要进行资源释放的类型。

如此多的资源释放类型管理起来是非常麻烦的,一旦忘记某个类型的释放很会造成整个页面的内存泄漏。而资源的创建一般都位于 initState 内,资源释放都位于 dispose 内。

「为了减小忘记资源释放的可能性,dispose 应为 State 内的第一个函数并尽可能的将 initsate 紧跟在 dispose 后」

示例:

Dart 复制代码
final _controller = TextEditingController();
late Timer _timer;

// 属性后第一个函数应为 dispose
void dispose() {
  _controller.dispose();
  _timer.cancell();
  super.dispose();
}
// 中间不要插入其它函数,紧跟着写 initState
void initState() {
  super.initState();
  _timer = Timer(...);
}

创建一个通用的mixin来处理

Dart 复制代码
// 创建下面的 Mixin
mixin AutomaticDisposeMixin<T extends StatefulWidget> on State<T> {
  Set<VoidCallback> _disposeSet = Set<VoidCallback>();

  void autoDispose(VoidCallback callabck) {
    _disposeSet.add(callabck);
  }

  void dispose() {
    _disposeSet.forEach((f) => f());
    _disposeSet.removeAll();
    super.dispose();
  }
}

1.局部变量场景下使用:

Dart 复制代码
//使用前处理方式
late CancelToken _token;

Future<void> _refreshPage() async {
  // _token 只在页面刷新的函数中使用,却不得不加一个变量来引用它
  _token = CancelToken();

  Dio dio = Dio();
  Response response = await dio.get(url, cancelToken: _token);
  int code = response.statusCode;
  // ...
}

void dispose() {
  super.dispose();
  _token.cancel();
}
//使用后处理方式
class _PageState extends State<Page> with AutomaticDisposeMixin {
  Future<void> _refreshPage() async {
    final token = CancelToken();
    // 添加到自动释放队列
    autoDispose(() => token.cancel());
    Dio dio = Dio();
    Response response = await dio.get(url, cancelToken: token);
    int code = response.statusCode;
    // ...
  }
}

2.在 initState 内进行资源声明的同时进行资源释放,这种写法相对来讲更加直观,更不易遗漏资源释放

Dart 复制代码
final _controller = TextEditingController();

void initState() {
  super.initState();
  _timer = Timer(...);
  autoDispose(() => _timer.cancel());
  autoDispose(() => _controller.dispose());
}

State 中存在异步刷新

1.如下单独处理方式

Dart 复制代码
Future<void> _refreshPage() async {
  // 异步可能是接口、文件读取、状态获取等
  final response = await API.getPageDetaile();
  if (!response.success) return;
  // 当前 Widget 存在于渲染树中才刷新
  if (!mounted) return; 
  setState((){
    _data = response.data;
  });
}

2.统一处理方式

Dart 复制代码
// 统一定义如下 mixin
mixin Stateable<T extends StatefulWidget> on State<T> {
  @override
  void setState(VoidCallback fn) {
    if (!mounted) return;
    super.setState(fn);
  }
}

// 在存在异步刷新的 State 中 with 如上 mixin
class SomPageState extends State<SomePageWidget> with Stateable { 
  //...
}
相关推荐
文人sec7 小时前
性能测试-jmeter9-逻辑控制器、定时器压力并发
测试工具·jmeter·性能优化·模块测试
鼠鼠我捏,要死了捏12 小时前
RocketMQ 高可用集群原理深度解析与性能优化实践指南
性能优化·消息队列·rocketmq
ALLIN16 小时前
Flutter 三种方式实现页面切换后保持原页面状态
flutter
Dabei16 小时前
Flutter 国际化
flutter
Dabei17 小时前
Flutter MQTT 通信文档
flutter
Dabei20 小时前
Flutter 中实现 TCP 通信
flutter
孤鸿玉20 小时前
ios flutter_echarts 不在当前屏幕 白屏修复
flutter
李游Leo20 小时前
JavaScript事件机制与性能优化:防抖 / 节流 / 事件委托 / Passive Event Listeners 全解析
开发语言·javascript·性能优化
不想被吃掉氩21 小时前
MySQL的性能优化。
数据库·mysql·性能优化
DemonAvenger21 小时前
数据库日志系统深度解析:从binlog到redo/undo日志的实践指南
数据库·mysql·性能优化