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 { 
  //...
}
相关推荐
天天喝旺仔1 天前
Docker 镜像瘦身实战:多阶段构建把体积缩小 90%
运维·后端·ci/cd·docker·云原生·容器·性能优化
「、皓子~1 天前
海狸IM 2.1 正式发布
flutter·微服务·golang·electron·开源软件·im·海狸im
呆呆敲代码的小Y1 天前
【游戏开发】xLua 性能优化技巧
junit·性能优化·lua·xlua
里欧跑得慢1 天前
AI 驱动的 UI 国际化方案自动适配:从单一语言到多语言布局的智能转换
前端·css·flutter·web
恋猫de小郭1 天前
AI 时代,一个优化 Flutter 的重复代码工具 Deslop
android·前端·flutter
raindayinrain1 天前
深入理解Linux内核--系统调用,性能优化
linux·性能优化·系统调用·返回值·入参·内核态用户态地址访问
youngerwang1 天前
【WSL2 VHDX 文件格式深度研究报告(开发 + 性能优化向)】
性能优化·wsl·vhdx·文件格式解析
weixin_431600441 天前
前端数据埋点(7):Web Vitals——页面慢不慢,不是再包一层 fetch
前端·性能优化·sentry·数据埋点
袁震2 天前
HarmonyOS 应用包体积优化与上架自检实战:从 76.2MB 到 3.6MB
java·华为·性能优化·harmonyos
raindayinrain2 天前
深入理解Linux内核--ext文件系统,open/write/read/close执行流程,性能优化
linux·性能优化·文件系统·文件系统调用