Flutter总结

一、Widget的生命周期

1、StatelessWidget

StatelessWidget只有build的过程,是用来创建widget的。

2、StatefullWidget

* createState():在StatefulWidget第一次插入树时调用,用于创建State对象。
* initState():初始化State对象,设置初始状态。
* didChangeDependencies():State依赖的InheritedWidget发生变化时调用。
* build():每次setState()被调用时,重新构建UI。
* didUpdateWidget():父widget重新构建时调用,用于比较新旧Widget的差异。
* deactivate():State对象从树中移除时调用,用于清理资源。
* dispose():State对象永久从树中移除时调用,进行资源释放。

二、Flutter的渲染流程三棵树

Widget树:描述UI的结构,是不可变的声明式配置。
Element树:Widget的实例,管理Widget与渲染对象的关系,并保存UI状态。
Render树:负责实际渲染内容,计算布局和绘制。

三、Flutter的底层结构

Framework(Dart):提供动画、手势、绘制能力,widget提供基础组件。Material/Cupertion提供iOS和Android风格的组件库。
Engine:使用C/C++实现的,执行渲染、线程管理、平台事件等操作。
Embedder : 平台到中间层的渲染设置、原生插件、打包等处理。

四、怎么让组件不随着状态变化而重新构建,避免不必要的性能开销。

* 使用 const 构造函数避免不必要的重建。
* 使用 ValueListenableBuilder 或 StreamBuilder 来精细控制哪些部分需要更新。
* 使用 RepaintBoundary 来避免不必要的绘制。使用它包裹的widget在父视图重绘的时候不会重绘。   Repaint:重绘   Boundary:范围,界限。
* 使用状态管理工具(如 Provider, Riverpod)来控制更新粒度。Provider 时,如果某个组件只依赖于某一部分状态,你可以使用 Consumer 或 Selector 来精确控制哪些部分需要更新。
* 通过 setState 和局部更新来优化组件性能。
* 使用 GlobalKey 或 Key 来控制组件的重新创建。

五、Flutter的methodchannel和eventchannel的区别是什么?

1、Flutter methodchannel典型调用原生方法并获取结果setMethodCallHandler通过block返回数据。
2、Flutter eventchannel它允许从原生端向Flutter端持续发送数据流,传感器数据、设备状态更新、持续的原生事件(比如GPS位置更新、传感器数据流等),通过eventSink不断的将数据发送到Flutter端。

特性 MethodChannel EventChannel
通信类型 请求-响应(一次性请求和响应) 数据流(持续推送事件)
用途 用于请求单次操作并获取返回结果 用于实时、连续的事件流或数据推送
Flutter端 使用invokeMethod()发送请求,获取结果 使用receiveBroadcastStream()接收事件流
原生端 使用result.success(), result.error()返回结果 使用eventSink.success()推送数据流
使用场景 调用一次性操作,如获取设备信息、执行任务等 处理实时数据流,如GPS位置、传感器数据等

六、iOS和Flutter互相调用并传参MethodChannel

1、首先创建FlutterEngine

 lazy var flutterEngine = FlutterEngine(name: "io.flutter", project: nil)
    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
        // 初始化Flutter引擎
        flutterEngine.run()
        // 注册所有Flutter插件
        GeneratedPluginRegistrant.register(with: flutterEngine)        
        return true
    }

2、MethodChannel发送数据,创建MethodChannel对象

2.1 通过flutterEngine,创建初始化FlutterViewController

2.2 创建FlutterMethodChannel

2.3 通过methodChannel.invokeMethod向Flutter页面传参

2.4 通过methodChannel.setMethodCallHandler{ (call, result) in } 的block函数监听Flutter页面调用原生的事件名。 call.method == "closeFlutterPage"

2.5 通过result将数据返回到Flutter页面中。

  @objc func useFlutter() {
        // 初始化FlutterViewController并展示
        let flutterEngine = (UIApplication.shared.delegate as! AppDelegate).flutterEngine
        let flutterViewController = FlutterViewController(engine: flutterEngine, nibName: nil, bundle: nil)
        //新的方法,在初始化的时候直接使用
        let initialRoute = "/flutterPage" //需要跳转的路由界面
//        let flutterViewController = FlutterViewController(engine: flutterEngine!, nibName: nil, bundle: nil, initialRoute: initialRoute)  新版
//        flutterViewController.setInitialRoute("/flutterPage") // 方法已经废弃,但是还可以使用

        // 使用 MethodChannel 传递数据
        let methodChannel = FlutterMethodChannel(name: "com.example.myapp/channel", binaryMessenger: flutterViewController as! FlutterBinaryMessenger)
        // 监听方法调用
        methodChannel.setMethodCallHandler { (call, result) in
              if call.method == "closeFlutterPage" {
                  if let arguments = call.arguments as? [String: Any], let data = arguments["data"] as? String {
                    print("Received data: \(data)")
                    self.clickBtn.setTitle("flutter \(data)", for: .normal)

                    result("Data received successfully")  // 返回给 Flutter
                  } else {
                    result(FlutterError(code: "INVALID_ARGUMENT", message: "Missing 'data'", details: nil))
                  }
                  
                  
                  self.closeFlutterPage(controller: flutterViewController)
                  result("Flutter page closed successfully")
              }

        }
        
        // 向 Flutter 页面传递参数
        methodChannel.invokeMethod("receiveData", arguments: ["message": "Hello from iOS!"])
           
        present(flutterViewController, animated: true, completion: nil)
    }

3、在Flutter的对应页面中的init方法中监听调用,

static const platform = MethodChannel('com.example.myapp/channel');
void initState() { 
    super.initState();
     // 监听来自 iOS 的数据 
    platform.setMethodCallHandler((call) async {
         if (call.method == 'receiveData') { 
            setState(() { 
                param1 = call.arguments['param1']; 
                param2 = call.arguments['param2']; 
}); } }); }

tips:两种方法传参

1、使用 setInitialRoute 传递参数:你可以通过 URL 路由将参数传递给 Flutter 页面,这对于简单的参数传递很方便。

2、使用 MethodChannel 传递参数:如果你需要更复杂的数据传递或双向通信,可以使用 MethodChannel 在 iOS 和 Flutter 之间传递数据。

七、Flutter的FlutterEventChannel

1、在Flutter页面先创建 FlutterEventChannel,并指定通道名称。

import 'package:flutter/material.dart';
import 'package:flutter/services.dart';

void main() => runApp(MyApp());

class MyApp extends StatefulWidget {
  @override
  _MyAppState createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  // 创建EventChannel并指定通道名称
  static const eventChannel = EventChannel('com.example.eventchannel');

  String _nativeData = "No data received";

  @override
  void initState() {
    super.initState();
    _startListening();
  }

  // 监听来自原生平台的事件
  void _startListening() {
    eventChannel.receiveBroadcastStream().listen(
      (data) {
        setState(() {
          _nativeData = data.toString();
        });
      },
      onError: (error) {
        setState(() {
          _nativeData = "Error: $error";
        });
      },
    );
  }

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: Text("Flutter EventChannel Example"),
        ),
        body: Center(
          child: Text('Data from native: $_nativeData'),
        ),
      ),
    );
  }
}

2、在swift工程中

2.1 创建FlutterEventChannel。

2.2 通过协议方法eventSink不断的给Flutter发送数据。

import UIKit
import Flutter

@UIApplicationMain
@objc class AppDelegate: FlutterAppDelegate {
    private var eventSink: FlutterEventSink?

    override func application(
        _ application: UIApplication,
        didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
    ) -> Bool {
        let controller = window?.rootViewController as! FlutterViewController
        let eventChannel = FlutterEventChannel(name: "com.example.eventchannel",
                                               binaryMessenger: controller.binaryMessenger)

        eventChannel.setStreamHandler(self)

        return super.application(application, didFinishLaunchingWithOptions: launchOptions)
    }
}

// 实现 FlutterStreamHandler 协议
extension AppDelegate: FlutterStreamHandler {
    // 当 Flutter 开始监听数据流时
    func onListen(withArguments arguments: Any?, eventSink events: @escaping FlutterEventSink) -> FlutterError? {
        self.eventSink = events
        Timer.scheduledTimer(withTimeInterval: 2.0, repeats: true) { timer in
            self.eventSink?("Native Event Data: \(Date())")
        }
        return nil
    }

    // 当 Flutter 停止监听数据流时
    func onCancel(withArguments arguments: Any?) -> FlutterError? {
        eventSink = nil
        return nil
    }
}

七、Flutter中是怎么实现异步操作的?

三种方式:Future async、 isolate 和 compute 
使用 Future:当任务是异步的、I/O 密集型的(如网络请求、数据库操作、文件读取)时,使用 Future 是最合适的。
使用 Isolate:当你需要进行复杂的并行计算或 CPU 密集型任务时(如图像处理、视频转码等),使用 Isolate 可以在独立的线程中执行任务,避免主线程阻塞。
使用 compute:当你希望简化并发计算任务的处理,且任务相对轻量时,compute 是一个简化版的 Isolate,可以帮助你轻松地将任务放到后台执行。

优缺点:
1、虽然compute方法可以帮助我们轻易实现异步操作的要求,但是compute也有缺陷,由于compute是由flutter控制管理Isolate,当我们面临复杂场景,频繁使用compute会造成大量的Isolate创建和销毁。损耗性能比较大。
2、直接使用Isolate可以实现对之前创建的Isolate的复用。 Isolate之间的通信通过port端口实现。通过 ReceivePort 和 SendPort 进行通信传递异常

特性 Future Isolate compute
并发模型 单线程异步执行(事件循环) 多线程并行执行(独立内存空间) 封装了 Isolate,使用多线程并行执行计算
内存共享 主线程与 Future 共享内存 每个 Isolate 有独立的内存空间,不能共享内存 每个 Isolate 有独立内存,通过消息传递通信
执行场景 I/O 密集型任务,如网络请求、文件操作等 CPU 密集型任务,如复杂计算、大数据处理等 轻量级的 CPU 密集型任务,通常用于后台计算
性能开销 较低,适合轻量级的异步任务 较高,适合复杂并行任务 较低,适合需要轻量级计算的并发任务
错误处理 使用 try/catch 或 Future.catchError 处理异常 通过 ReceivePort 和 SendPort 进行通信传递异常 异常通过 Future 的机制处理
复杂性 简单,适用于 I/O 操作和简单的异步任务 较复杂,需要手动管理线程创建和通信 简化了 Isolate 的创建和管理,易于使用

八、Flutter 性能优化点

  • 使用 const 构造函数避免不必要的重建。
  • 使用 const 修饰数据类,可以让Dart编译器将他们优化为单利对象,避免实例化。
const Color MainClor = Colors.red
  • 使用 ValueListenableBuilder 或 StreamBuilder 来精细控制哪些部分需要更新。
  • 使用 RepaintBoundary 来避免不必要的绘制。使用它包裹的widget在父视图重绘的时候不会重绘。 Repaint:重绘 Boundary:范围,界限。
  • 使用状态管理工具(如 Provider, Riverpod)来控制更新粒度。Provider 时,如果某个组件只依赖于某一部分状态,你可以使用 Consumer 或 Selector 来精确控制哪些部分需要更新。
  • 通过 setState 和局部更新来优化组件性能。
  • 使用 GlobalKey 或 Key 来控制组件的重新创建。
  • 使用 AutomaticKeepAlive 保存状态:对于 ListView 或 PageView 等带有滚动特性的组件,可以使用 AutomaticKeepAlive 来保留离屏的 Widget 状态,从而避免重建。
class MyListItem extends StatefulWidget {
  @override
  _MyListItemState createState() => _MyListItemState();
}

class _MyListItemState extends State<MyListItem> with AutomaticKeepAliveClientMixin {
  @override
  bool get wantKeepAlive => true;

  @override
  Widget build(BuildContext context) {
    super.build(context); // 必须调用
    return Text('I am kept alive');
  }
}
  • ValueNotifier 和 ChangeNotifier 是轻量级的状态管理工具,可以减少 setState 的使用并精确更新 UI 中的部分。
class MyWidget extends StatelessWidget {
  final ValueNotifier<int> counter = ValueNotifier<int>(0);

  @override
  Widget build(BuildContext context) {
    return ValueListenableBuilder<int>(
      valueListenable: counter,
      builder: (context, value, child) {
        return Text('Counter: $value');
      },
    );
  }
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,294评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,493评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,790评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,595评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,718评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,906评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,053评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,797评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,250评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,570评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,711评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,388评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,018评论 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,796评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,023评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,461评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,595评论 2 350

推荐阅读更多精彩内容

  • Dart部分 String扩展一个方法 使用关键字extension ... on为String定义一个扩展类 在...
    渚清与沙白阅读 1,867评论 1 6
  • 0、Dart是值传递还是引用传递? Dart是值传递。每次调用函数,传递过去的都是对象的内存地址,不是对象的复制。...
    woniu阅读 3,243评论 1 22
  • Dart 相关 1、Dart 当中的 「..」表示什么意思? 级连操作符 “..” 和 “.” 不同:调用..后返...
    af06e7def7a7阅读 2,227评论 0 2
  • 一、flutter启动流程1.实例化WidgetsFlutterBinding类,2.创建组件树attachRoo...
    齐玉婷阅读 2,815评论 0 9
  • Flutter 架构 Flutter框架分三层Framework,Engine, Embedder Framewo...
    SimpleFunc阅读 4,385评论 3 7