(转载)flutter Isolate及compute使用

这篇文章将会讲解flutter中的Isolate,这有助于帮你解决某些耗时计算问题导致的卡顿。

一 . 原始代码

为什么要Isolate,我们先看一段比较简单的代码:

import 'package:flutter/material.dart';
import 'package:flutter/foundation.dart';
 
class TestWidget extends StatefulWidget {
  @override
  State createState() {
    return TestWidgetState();
  }
}
 
class TestWidgetState extends State {
  int _count = 0;
 
  @override
  Widget build(BuildContext context) {
    return Material(
      child: Center(
        child: Column(
          children: [
            Container(
              width: 100,
              height: 100,
              child: CircularProgressIndicator(),
            ),
            FlatButton(
                onPressed: () async {
                  _count = countEven(1000000000);
                  setState(() {});
                },
                child: Text(
                  _count.toString(),
                )),
          ],
          mainAxisSize: MainAxisSize.min,
        ),
      ),
    );
  }

  //计算偶数的个数
  static int countEven(int num) {
    int count = 0;
    while (num > 0) {
      if (num % 2 == 0) {
        count++;
      }
      num--;
    }
    return count;
  }
}

UI包含两个部分,一个不断转圈的progress指示器,一个按钮,当点击按钮的时候,找出比某个正整数n小的数的偶数的个数(请忽视具体算法,故意做耗时计算用,哈哈)。我们来运行一下代码看看效果:


image

可以看到,本来是很流畅的转圈,当我点击按钮计算的时候,UI出现了卡顿,为什么会出现卡顿,因为我们的计算默认是在UI线程中的,当我们调用countEven的时候,这个计算需要耗时,而在这期间,UI是没有机会去调用刷新的,因此会卡顿,计算完成后,UI恢复正常刷新。

二. 使用async优化

那么有些同学就会说了,在dart中,有async关键字,我们可以用异步计算,这样就不会影响UI的刷新了,事实真的是这样吗?我们一起来修改一下代码:

a. 将count改为asyncCountEven

  static Future asyncCountEven(int num) async{
    int count = 0;
    while (num > 0) {
      if (num % 2 == 0) {
        count++;
      }
      num--;
    }
    return count;
  }

b. 调用:

_count = await asyncCountEven(1000000000);

我们继续运行一下代码,看现象:


image

仍然卡顿,说明异步是解决不了问题的,为什么?因为我们仍旧是在同一个UI线程中做运算,异步只是说我可以先运行其他的,等我这边有结果再返回,但是,记住,我们的计算仍旧是在这个UI线程,仍会阻塞UI的刷新,异步只是在同一个线程的并发操作。

三. 使用compute优化

那么我们怎么解决这个问题呢,其实很简单,我们知道卡顿的原因是在同一个线程中导致的,那我们有没有办法将计算移到新的线程中呢,当然是可以的。不过在dart中,这里不是称呼线程,是Isolate,直译叫做隔离,这么古怪的名字,是因为隔离不共享数据,每个隔离中的变量都是不同的,不能相互共享。

但是由于dart中的Isolate比较重量级,UI线程和Isolate中的数据的传输比较复杂,因此flutter为了简化用户代码,在foundation库中封装了一个轻量级compute操作,我们先看看compute,然后再来看Isolate。

要使用compute,必须注意的有两点,一是我们的compute中运行的函数,必须是顶级函数或者是static函数,二是compute传参,只能传递一个参数,返回值也只有一个,我们先看看本例中的compute优化吧:

真的很简单,只用在使用的时候,放到compute函数中就行了。

_count = await compute(countEven, 1000000000);

再次运行,我们来看看效果吧:


image

可以看到,现在的计算并不会导致UI卡顿,完美解决问题。

四. 使用Isolate优化

但是,compute的使用还是有些限制,它没有办法多次返回结果,也没有办法持续性的传值计算,每次调用,相当于新建一个隔离,如果调用过多的话反而会适得其反。在某些业务下,我们可以使用compute,但是在另外一些业务下,我们只能使用dart提供的Isolate了,我们先看看Isolate在本例中的使用:
a. 增加这两个函数

  static Future isolateCountEven(int num) async {
    final response = ReceivePort();
    await Isolate.spawn(countEvent2, response.sendPort);
    final sendPort = await response.first;
    final answer = ReceivePort();
    sendPort.send([answer.sendPort, num]);
    return answer.first;
  }
 
  static void countEvent2(SendPort port) {
    final rPort = ReceivePort();
    port.send(rPort.sendPort);
    rPort.listen((message) {
      final send = message[0] as SendPort;
      final n = message[1] as int;
      send.send(countEven(n));
    });
  }

b. 使用

_count = await isolateCountEven(1000000000);

相对于compute复杂了很多,效果就不贴了,和compute一样,毫无卡顿。。

五. 扩展
isolate使用这么复杂,那么我们在那些情况下使用它呢?

想象一下,假如你有一个业务,是使用socket和服务器连接,不断的从服务器中读取tcp流,如果业务需要拆分包的话,我们需要不断的将读取到的tcp流进行拆包分包,然后使用获取的数据来更新UI。首先,我们的socket与服务器通信,如果服务器推送消息过快,那么肯定会出现上面的情况,socket这边一直阻塞线程,导致UI刷新卡顿,所以我们需要将socket这边的业务放到隔离里面,但是,compute函数的限制我们也说了,它基本是是一次运行一次返回,但是业务要求是通过tcp的长连接不断获取服务器的数据,所以,这里我们就只能使用isolate,在UI和isolate里面使用ReceivePort进行双向通信,这样才能保证UI不卡顿的情况下仍然保持业务的完整性。
————————————————
版权声明:本文为CSDN博主「Hirabbit_jaden」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/email_jade/article/details/88941434

你可能感兴趣的:((转载)flutter Isolate及compute使用)