MK
摩柯社区 - 一个极简的技术知识社区
AI 面试

Flutter嵌入层:实现跨平台应用的关键技术

2022-11-232.9k 阅读

Flutter 嵌入层基础概念

Flutter 跨平台架构简述

Flutter 作为一款流行的跨平台移动应用开发框架,其核心优势在于能够以一套代码库构建高性能、美观的原生应用。Flutter 的架构主要分为三层:Framework 层、Engine 层以及嵌入层。Framework 层提供了丰富的 UI 组件、动画库、状态管理等功能,开发者主要在这一层进行应用逻辑的编写。Engine 层则负责渲染、图形处理、文本排版等底层操作,它是 Flutter 高性能的关键支撑。而嵌入层,是连接 Flutter 应用与宿主平台(如 Android、iOS)的桥梁,使得 Flutter 应用能够无缝地融入不同的原生环境。

嵌入层的关键作用

  1. 平台交互:通过嵌入层,Flutter 应用可以调用宿主平台的原生功能,如访问设备传感器(摄像头、加速度计等)、使用原生的系统 UI 组件(如 Android 的通知栏、iOS 的 Share Sheet)。同时,宿主平台也能够与 Flutter 应用进行双向通信,例如在 Android 中,通过嵌入层可以将 Java 或 Kotlin 的数据传递给 Flutter 界面,并接收 Flutter 界面返回的处理结果。
  2. 应用生命周期管理:嵌入层负责管理 Flutter 应用在宿主平台上的生命周期。它确保 Flutter 应用能够正确响应宿主平台的生命周期事件,如应用的启动、暂停、恢复和销毁。以 iOS 为例,当用户按下 Home 键时,宿主平台通过嵌入层通知 Flutter 应用进入暂停状态,Flutter 应用可以在此状态下进行资源释放或数据保存等操作。
  3. 渲染集成:嵌入层将 Flutter Engine 的渲染结果与宿主平台的视图系统进行集成。在 Android 上,它会将 Flutter 的渲染输出嵌入到 Android 的 View 体系中;在 iOS 上,则是嵌入到 UIView 层级中。这种集成使得 Flutter 应用能够与原生应用的其他视图共存,实现更灵活的界面布局。

Android 平台上的 Flutter 嵌入层实现

创建 Flutter 模块

  1. 初始化 Flutter 项目:首先,使用 Flutter 命令行工具创建一个新的 Flutter 项目。打开终端,运行以下命令:
flutter create my_flutter_module

这将创建一个名为 my_flutter_module 的 Flutter 项目。 2. 配置 Flutter 模块:进入 my_flutter_module 目录,在 pubspec.yaml 文件中配置项目所需的依赖。例如,如果项目需要使用网络请求,可以添加 http 依赖:

dependencies:
  flutter:
    sdk: flutter
  http: ^0.13.4

然后运行 flutter pub get 命令获取依赖。

将 Flutter 模块嵌入 Android 项目

  1. 创建 Android 项目:使用 Android Studio 创建一个新的 Android 项目。在创建过程中,选择合适的项目模板和配置。
  2. 添加 Flutter 模块依赖:在 Android 项目的 settings.gradle 文件中添加 Flutter 模块的路径。假设 Flutter 模块位于与 Android 项目同级目录下,添加如下内容:
include ':flutter_module'
project(':flutter_module').projectDir = new File('../my_flutter_module')
  1. 在 Android 代码中加载 Flutter 视图:在 Android 项目的 Activity 或 Fragment 中加载 Flutter 视图。以下是在 Activity 中加载 Flutter 视图的示例代码:
import io.flutter.embedding.android.FlutterFragment;
import android.os.Bundle;
import androidx.appcompat.app.AppCompatActivity;

public class MainActivity extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        getSupportFragmentManager()
              .beginTransaction()
              .replace(R.id.flutter_container, FlutterFragment.createDefault())
              .commit();
    }
}

上述代码中,R.id.flutter_container 是布局文件 activity_main.xml 中定义的一个 FrameLayout,用于承载 Flutter 视图。

与 Flutter 进行通信

  1. 从 Android 传递数据到 Flutter:在 Android 中,可以通过 MethodChannel 向 Flutter 发送数据。首先,在 Flutter 端定义接收数据的方法:
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';

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

class MyApp extends StatelessWidget {
  static const platform = MethodChannel('samples.flutter.dev/battery');

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: Text('Android to Flutter Communication'),
        ),
        body: Center(
          child: FutureBuilder<String>(
            future: platform.invokeMethod('getBatteryLevel'),
            builder: (BuildContext context, AsyncSnapshot<String> snapshot) {
              if (snapshot.hasData) {
                return Text('Battery level: ${snapshot.data}%');
              } else if (snapshot.hasError) {
                return Text('Error: ${snapshot.error}');
              }
              return CircularProgressIndicator();
            },
          ),
        ),
      ),
    );
  }
}

在 Android 端实现 getBatteryLevel 方法:

import io.flutter.embedding.android.FlutterActivity;
import io.flutter.embedding.engine.FlutterEngine;
import io.flutter.plugin.common.MethodChannel;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.BatteryManager;
import android.os.Bundle;

public class MainActivity extends FlutterActivity {
    private static final String CHANNEL = "samples.flutter.dev/battery";

    @Override
    public void configureFlutterEngine(FlutterEngine flutterEngine) {
        super.configureFlutterEngine(flutterEngine);
        new MethodChannel(flutterEngine.getDartExecutor().getBinaryMessenger(), CHANNEL)
              .setMethodCallHandler(
                    (call, result) -> {
                        if (call.method.equals("getBatteryLevel")) {
                            int batteryLevel = getBatteryLevel();
                            if (batteryLevel != -1) {
                                result.success(batteryLevel);
                            } else {
                                result.error("UNAVAILABLE", "Battery level not available.", null);
                            }
                        } else {
                            result.notImplemented();
                        }
                    }
            );
    }

    private int getBatteryLevel() {
        IntentFilter ifilter = new IntentFilter(Intent.ACTION_BATTERY_CHANGED);
        Intent batteryStatus = getApplicationContext().registerReceiver(null, ifilter);
        int level = batteryStatus.getIntExtra(BatteryManager.EXTRA_LEVEL, -1);
        int scale = batteryStatus.getIntExtra(BatteryManager.EXTRA_SCALE, -1);
        return (level * 100) / scale;
    }
}
  1. 从 Flutter 传递数据到 Android:在 Flutter 端定义发送数据的方法:
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';

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

class MyApp extends StatelessWidget {
  static const platform = MethodChannel('samples.flutter.dev/returnData');

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: Text('Flutter to Android Communication'),
        ),
        body: Center(
          child: RaisedButton(
            child: Text('Send data to Android'),
            onPressed: () async {
              try {
                final String result = await platform.invokeMethod('sendDataToAndroid', {'message': 'Hello from Flutter'});
                print('Result from Android: $result');
              } on PlatformException catch (e) {
                print('Failed to send data: ${e.message}');
              }
            },
          ),
        ),
      ),
    );
  }
}

在 Android 端接收数据并处理:

import io.flutter.embedding.android.FlutterActivity;
import io.flutter.embedding.engine.FlutterEngine;
import io.flutter.plugin.common.MethodChannel;
import android.os.Bundle;

public class MainActivity extends FlutterActivity {
    private static final String CHANNEL = "samples.flutter.dev/returnData";

    @Override
    public void configureFlutterEngine(FlutterEngine flutterEngine) {
        super.configureFlutterEngine(flutterEngine);
        new MethodChannel(flutterEngine.getDartExecutor().getBinaryMessenger(), CHANNEL)
              .setMethodCallHandler(
                    (call, result) -> {
                        if (call.method.equals("sendDataToAndroid")) {
                            String message = call.argument("message");
                            // 处理接收到的消息
                            String response = "Message received: $message";
                            result.success(response);
                        } else {
                            result.notImplemented();
                        }
                    }
            );
    }
}

iOS 平台上的 Flutter 嵌入层实现

创建 Flutter 模块

  1. 初始化 Flutter 项目:与 Android 平台类似,使用 Flutter 命令行工具创建新的 Flutter 项目:
flutter create my_flutter_module
  1. 配置 Flutter 模块:进入 my_flutter_module 目录,在 pubspec.yaml 文件中配置项目依赖,然后运行 flutter pub get 获取依赖。

将 Flutter 模块嵌入 iOS 项目

  1. 创建 iOS 项目:使用 Xcode 创建一个新的 iOS 项目。选择合适的项目模板,如 Single View App。
  2. 添加 Flutter 模块依赖:在 iOS 项目的根目录下,创建一个 Flutter 文件夹,并将 Flutter 项目的 ios 目录中的 Flutter 文件夹和 Pods 文件夹复制到新建的 Flutter 文件夹中。
  3. 在 iOS 代码中加载 Flutter 视图:在 iOS 项目的 AppDelegate.m 文件中加载 Flutter 视图。以下是示例代码:
#import "AppDelegate.h"
#import <Flutter/Flutter.h>

@interface AppDelegate () <FlutterPluginRegistryDelegate>

@end

@implementation AppDelegate

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    FlutterViewController *flutterViewController = [[FlutterViewController alloc] initWithProject:nil nibName:nil bundle:nil];
    UIViewController *rootViewController = self.window.rootViewController;
    UIView *flutterView = flutterViewController.view;
    flutterView.frame = CGRectMake(0, 0, self.window.bounds.size.width, self.window.bounds.size.height);
    [rootViewController.view addSubview:flutterView];
    return YES;
}

@end

上述代码将 Flutter 视图添加到 iOS 应用的主视图中。

与 Flutter 进行通信

  1. 从 iOS 传递数据到 Flutter:在 Flutter 端定义接收数据的方法:
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';

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

class MyApp extends StatelessWidget {
  static const platform = MethodChannel('samples.flutter.dev/iosData');

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: Text('iOS to Flutter Communication'),
        ),
        body: Center(
          child: FutureBuilder<String>(
            future: platform.invokeMethod('getDeviceName'),
            builder: (BuildContext context, AsyncSnapshot<String> snapshot) {
              if (snapshot.hasData) {
                return Text('Device name: ${snapshot.data}');
              } else if (snapshot.hasError) {
                return Text('Error: ${snapshot.error}');
              }
              return CircularProgressIndicator();
            },
          ),
        ),
      ),
    );
  }
}

在 iOS 端实现 getDeviceName 方法:

#import "AppDelegate.h"
#import <Flutter/Flutter.h>

@interface AppDelegate () <FlutterPluginRegistryDelegate>

@end

@implementation AppDelegate

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    FlutterViewController *flutterViewController = [[FlutterViewController alloc] initWithProject:nil nibName:nil bundle:nil];
    FlutterMethodChannel *channel = [FlutterMethodChannel methodChannelWithName:@"samples.flutter.dev/iosData" binaryMessenger:flutterViewController.binaryMessenger];
    [channel setMethodCallHandler:^(FlutterMethodCall * _Nonnull call, FlutterResult  _Nonnull result) {
        if ([@"getDeviceName" isEqualToString:call.method]) {
            NSString *deviceName = UIDevice.currentDevice.name;
            result(deviceName);
        } else {
            result(FlutterMethodNotImplemented);
        }
    }];
    UIViewController *rootViewController = self.window.rootViewController;
    UIView *flutterView = flutterViewController.view;
    flutterView.frame = CGRectMake(0, 0, self.window.bounds.size.width, self.window.bounds.size.height);
    [rootViewController.view addSubview:flutterView];
    return YES;
}

@end
  1. 从 Flutter 传递数据到 iOS:在 Flutter 端定义发送数据的方法:
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';

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

class MyApp extends StatelessWidget {
  static const platform = MethodChannel('samples.flutter.dev/flutterToIos');

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: Text('Flutter to iOS Communication'),
        ),
        body: Center(
          child: RaisedButton(
            child: Text('Send data to iOS'),
            onPressed: () async {
              try {
                final String result = await platform.invokeMethod('sendMessageToIos', {'message': 'Hello from Flutter'});
                print('Result from iOS: $result');
              } on PlatformException catch (e) {
                print('Failed to send data: ${e.message}');
              }
            },
          ),
        ),
      ),
    );
  }
}

在 iOS 端接收数据并处理:

#import "AppDelegate.h"
#import <Flutter/Flutter.h>

@interface AppDelegate () <FlutterPluginRegistryDelegate>

@end

@implementation AppDelegate

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    FlutterViewController *flutterViewController = [[FlutterViewController alloc] initWithProject:nil nibName:nil bundle:nil];
    FlutterMethodChannel *channel = [FlutterMethodChannel methodChannelWithName:@"samples.flutter.dev/flutterToIos" binaryMessenger:flutterViewController.binaryMessenger];
    [channel setMethodCallHandler:^(FlutterMethodCall * _Nonnull call, FlutterResult  _Nonnull result) {
        if ([@"sendMessageToIos" isEqualToString:call.method]) {
            NSDictionary *arguments = call.arguments;
            NSString *message = arguments[@"message"];
            // 处理接收到的消息
            NSString *response = [NSString stringWithFormat:@"Message received: %@", message];
            result(response);
        } else {
            result(FlutterMethodNotImplemented);
        }
    }];
    UIViewController *rootViewController = self.window.rootViewController;
    UIView *flutterView = flutterViewController.view;
    flutterView.frame = CGRectMake(0, 0, self.window.bounds.size.width, self.window.bounds.size.height);
    [rootViewController.view addSubview:flutterView];
    return YES;
}

@end

深入理解 Flutter 嵌入层的渲染机制

Flutter 渲染流程概述

  1. 创建场景:Flutter 应用在启动时,会根据 Widget 树构建一个场景。Widget 树描述了应用的 UI 结构,包括各种 UI 组件及其属性。例如,一个简单的 Flutter 应用可能有一个 Scaffold Widget 作为根节点,包含 AppBarCenter Widget,Center Widget 中又包含一个 Text Widget。Flutter 根据这些 Widget 的布局和绘制信息,创建一个场景对象,这个场景对象包含了所有需要渲染的图形元素。
  2. 光栅化:场景创建完成后,Flutter Engine 将场景进行光栅化处理。光栅化是将矢量图形转换为位图的过程,以便在屏幕上显示。在这个过程中,Flutter Engine 使用 Skia 图形库来进行高效的图形绘制。Skia 能够处理复杂的图形操作,如路径绘制、文本渲染、图像合成等。例如,对于一个带有渐变背景和阴影效果的按钮,Skia 会根据相应的图形指令进行精确的绘制。
  3. 提交渲染:光栅化后的位图数据被提交给嵌入层。嵌入层负责将这些位图数据与宿主平台的视图系统进行集成。在 Android 上,嵌入层会将位图数据传递给 Android 的 Surface,并通过 SurfaceViewTextureView 进行显示;在 iOS 上,嵌入层会将位图数据传递给 CAMetalLayerCAEAGLLayer,利用 Metal 或 OpenGL ES 进行显示。

嵌入层与渲染的协同工作

  1. Android 平台:在 Android 平台上,Flutter 使用 FlutterViewFlutterFragment 来承载 Flutter 应用的渲染输出。FlutterView 是一个自定义的 View,它与 Flutter Engine 进行交互,接收渲染结果并将其显示在 Android 的视图层级中。当 Flutter 应用的场景发生变化(例如 Widget 树更新)时,Flutter Engine 会重新光栅化场景,并将新的位图数据传递给 FlutterViewFlutterView 则通过 Surface 机制将位图数据绘制到屏幕上。如果 Android 应用的布局发生变化,例如旋转屏幕,FlutterView 会通知 Flutter Engine 进行相应的布局调整和重新渲染。
  2. iOS 平台:在 iOS 平台上,Flutter 使用 FlutterViewController 来管理 Flutter 应用的渲染。FlutterViewController 包含一个 FlutterEngine 实例,负责处理 Flutter 应用的渲染逻辑。当 Flutter 应用需要渲染时,FlutterEngine 生成的位图数据会通过 Metal 或 OpenGL ES 相关的机制传递给 CAMetalLayerCAEAGLLayer,进而显示在屏幕上。如果 iOS 应用发生设备方向变化等事件,FlutterViewController 会通知 FlutterEngine 进行相应的处理,以确保 Flutter 应用的界面能够正确地适应变化。

Flutter 嵌入层的性能优化

减少通信开销

  1. 批量处理数据:在 Flutter 与宿主平台进行通信时,尽量批量处理数据,而不是频繁地进行单次通信。例如,在从 Android 向 Flutter 传递多个传感器数据时,可以将这些数据封装成一个对象或数组,通过一次 MethodChannel 调用传递给 Flutter,而不是每个传感器数据都进行一次单独的调用。这样可以减少通信的次数,提高性能。
  2. 优化数据结构:确保在通信过程中传递的数据结构简单、紧凑。避免传递过于复杂或庞大的对象,因为这会增加数据序列化和反序列化的时间。例如,在传递用户信息时,只传递必要的字段,如用户名、用户 ID 等,而不是整个包含大量冗余信息的用户对象。

合理管理资源

  1. 内存管理:在 Flutter 嵌入层中,要注意内存的合理使用。在 Android 上,确保 FlutterViewFlutterFragment 在不再使用时及时释放相关资源,避免内存泄漏。例如,当一个包含 Flutter 视图的 Activity 被销毁时,要确保 Flutter Engine 及其相关资源也被正确释放。在 iOS 上,同样要注意 FlutterViewController 及其相关资源的释放,特别是在视图控制器被弹出或销毁时。
  2. 图形资源管理:对于 Flutter 渲染过程中使用的图形资源,如纹理、缓冲区等,要进行合理的管理。在 Android 上,避免创建过多不必要的 Surface 对象,因为每个 Surface 都占用一定的系统资源。在 iOS 上,合理使用 Metal 或 OpenGL ES 的资源,避免资源的过度占用和浪费。例如,当 Flutter 应用切换到后台时,可以适当释放一些暂时不需要的图形资源,待应用回到前台时再重新加载。

优化渲染性能

  1. 减少重绘:在 Flutter 应用开发中,尽量减少不必要的 Widget 重建和重绘。通过合理使用 StatefulWidgetStatelessWidget,以及正确管理状态,可以避免在状态变化时整个 Widget 树的不必要重建。例如,如果一个 Widget 的某些属性变化不会影响其外观,可以将这些属性放在 StatelessWidget 中,这样当这些属性变化时不会触发整个 Widget 的重建。
  2. 优化布局:优化 Flutter 应用的布局,避免复杂的嵌套布局。复杂的嵌套布局会增加布局计算的时间,从而影响渲染性能。尽量使用简单、扁平的布局结构,例如使用 Flex 布局或 Stack 布局来替代过多的嵌套 Container 布局。同时,合理设置 constraints,避免过度约束导致的布局问题和性能损耗。在 Android 和 iOS 平台上,也要确保 Flutter 视图与原生视图的布局协同工作良好,避免因为布局冲突导致的性能下降。