Timer在Flutter中,一般是用来构建延时的异步使命的,在解说它的妙之前,咱们先来看看Timer的基操。

Timer基操

Timer广义上能够理解为一个倒计时器,它从创立到结束会经历下面三个进程:

  • Creates a timer
  • Executes a callback
  • The timer finishes

创立一个Timer十分简略。

final timer = Timer(
  const Duration(seconds: 3),
  () {
    // TODO
  },
);

�上面这段代码,就代表着3秒推迟后履行回调闭包,当时代码中,这个回调只会履行一次。

那么假如咱们需要创立一个周期性的计时器的话,就需要运用Timer.periodic�构造函数。

final periodicTimer = Timer.periodic(
  const Duration(seconds: 1),
  (timer) {
    // TODO
  },
);

�默认的周期性Timer会无限制的履行下去,所以,和一切的资源一样,咱们需要对其进行办理,开释掉不运用的Timer资源。

一般咱们会在Widget的dispose办法中开释。

首要便是Timer的cancel办法,拿到Timer的句柄后,咱们能够在适宜的时机对其进行cancel(多次调用cancel不会产生任何副作用,多余的cancel会被忽略)。

final timer = Timer.periodic(
  const Duration(seconds: 1),
  (timer) {
    // TODO
  },
);
final shouldStop = true;
test() {
  if (shouldStop || timer.isActive) {
    timer.cancel();
  }
}

�同时,Timer句柄还供给了isActive�来判断当时Timer是否现已毁掉,以及timer.tick来获取当时的计数值。

Timer意料之外

上面便是Timer最根底的运用,看上去是不是十分简略,可是,Timer在Flutter中实际上是一个十分重要的类,咱们来看下Future的完结。

FlutterComponent最佳实践之Timer的妙用
发现了吗,咱们创立异步代码的Future,其本质上也是经过Timer来完结的,所以,咱们能够凭借Timer来完结一个十分有意思的功用——以异步的方法尽快的履行回调。

print('normal code start');
final zeroDurationTimer = Timer(
  Duration.zero,
  () {
    // Execute this callback ASAP but asynchronously
    print('zeroDurationTimer callback');
  },
);
print('normal code end');

�咱们来看上面的代码,履行成果如下。

normal code start
normal code end
zeroDurationTimer callback

这是因为一切的异步函数都被放到一个行列里进行调度,所以其回调的履行需要一点时刻。因而,上面异步回调中的代码,会在最终履行。

上面的代码还有一个简化的写法——运用Timer.run构造函数。

final zeroDurationTimer = Timer.run(
  () {
    // Execute this callback ASAP but asynchronously
  },
);

多个Timer.run,或者说是Future,实际上也会按照异步的方法,按行列顺序依次履行。

批处理Channel

那么这个东西能干嘛呢——它能够帮咱们批量处理一部分Microtask里边的操作,例如咱们下面这个操作。

import 'package:flutter/material.dart';
import 'batch_method_channel.dart';
void main() => runApp(const MyApp());
class MyApp extends StatefulWidget {
  const MyApp({super.key});
  @override
  State<MyApp> createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(title: const Text('Plugin example app')),
        body: GridView.builder(
          gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: 4),
          itemBuilder: (context, index) => TestWidget(index),
        ),
      ),
    );
  }
}
class TestWidget extends StatelessWidget {
  final int index;
  const TestWidget(
    this.index, {
    Key? key,
  }) : super(key: key);
  @override
  Widget build(BuildContext context) {
    batchChannel.invokeMethod('getPlatformVersion', {'index': index.toString()});
    return GridTile(child: Text('data $index'));
  }
}

在上面的代码中,咱们模仿一个类似埋点的需求,在Widget build的时分,调用Channel传递一些数据到Native(这里运用的是Plugin Demo的工程进行改造的)。

假如咱们运用一般的Channel来处理这个需求,那么每个Item build的时分都会调用一次Channel,这些Channel的调用会造成一定的功用损耗,所以咱们需要对Channel进行批处理。

经过测验发现,Channel的功用损耗其完结已很低了,是否批处理带来的功用提升,只能在一些比较差的设备上才干有所表现。

这个批处理的需求,就能够运用Timer来完结,代码如下。

import 'dart:async';
import 'package:flutter/services.dart';
final BatchMethodChannel batchChannel = BatchMethodChannel();
class BatchMethodChannel {
  static const MethodChannel channel = MethodChannel('xys_flutter_batch_channel');
  Timer? _timer;
  final List<Map<String, String>> _methods = [];
  void invokeMethod(String method, [Map<String, String>? arguments]) {
    if (_timer == null || !_timer!.isActive) {
      _methods.clear();
      _timer = Timer(Duration.zero, () {
        channel.invokeMethod(
          'batch_channel_invoke',
          _methods,
        );
      });
    }
    _methods.add({'method': method}..addAll(arguments ?? {}));
  }
}

�当Widget build时,调用invokeMethod操作,初次调用时,会创立一个Timer,并将真实的channel.invokeMethod作为异步使命推迟履行,根据前面的解说,这个Callback只有当主线程行列悉数履行完结后才会履行,所以,当页面上有源源不断的Widget build时,invokeMethod一向被履行,非初次的履行,会将Method参数添加到List中,等一切Widget调用完结,Callback进行了处理,将前面List中的一切数据一次性经过channel.invokeMethod履行,然后形成了批处理的功用。

那么在Native侧收到批处理的消息后,就需要遍历List来处理每个Method,完结相应的操作。

when (call.method) {
    "batch_channel_invoke" -> {
        val callArguments: List<Map<String, String>> = call.arguments as List<Map<String, String>>
        Log.d("xys", "batch_channel_invoke: $callArguments")
    }
    else -> {
        result.notImplemented()
    }
}

这种对Channel批处理的方法,也是有一些运用局限的。首要,由于批处理的Channel在Native侧履行的时分,不一定悉数都是同步办法,所以,批处理Channel无法做到等一批数据处理好之后的一致返回。它最适宜的场景还是类似上面这种「单向」的Channel调用场景。其次,Channel的解析进程会比一般方法更加复杂一点,需要看场景运用。

当然,Batch Channel也不一定只能是「单向」的,经过一些其它方法,它也能够做到向一般Channel那样的双向通信,例如,咱们能够运用EventChannel�来配合BatchChannel运用,这样在Native侧处理好批处理数据后,经过Stream返回给Flutter侧的监听者,然后完结双向通信。这些内容,咱们后续再剖析。

欢迎大家关注我的公众号——【群英传】,专心于「Android」「Flutter」「Kotlin」 我的语雀知识库——www.yuque.com/xuyisheng