FlutterAndroidFlutter

混合开发与Flutter引擎

2021-12-25  本文已影响0人  浅墨入画

原生嵌入Flutter

原生要想嵌入FlutterFlutter就不能是一个独立的App,新建工程的时候要选择Flutter Module类型创建,如下图所示

工程类型

下面新建flutter_module工程(纯Flutter工程),其代码还是在lib目录下编写

使用Xcode新建iOS工程NativeDemo,与flutter_module工程放入同一目录下。使用cocoapods使两个工程进行关联

$ cd /Users/wn/Documents/Flutter学习/NativeDemo
$ pod init 
<!-- Podfile文件内容 -->
// flutter_module工程的路径
flutter_application_path = '../flutter_module'  
// 把flutter工程加载到iOS工程中
load File.join(flutter_application_path,'.iOS','Flutter','podhelper.rb')

platform :ios, '9.0'

target 'NativeDemo' do
  // 把flutter依赖的一些库加载进来
  install_all_flutter_pods(flutter_application_path)
  use_frameworks!

  # Pods for NativeDemo

end
$ pod install
// 如果能成功导入,就说明iOS工程NativeDemo与Flutter工程flutter_module已经进行了关联
#import <Flutter/Flutter.h>
// Main.storyboard文件中添加按钮,并关联点击事件
- (IBAction)pushFlutter:(id)sender {
    // 跳转flutter页面
    FlutterViewController *vc = [[FlutterViewController alloc] init];
    [self presentViewController:vc animated:true completion:nil]; 
}
运行NativeDemo工程跳转Flutter页面

注意:如果Flutter代码有更新,直接打开iOS工程,是无法展示Flutter最新代码的,解决方案如下:

查看NativeDemo工程内存以及App包内容
打开Flutter页面内存暴增 NativeDemo包的体积增大

运行NativeDemo发现的几个问题?

后面针对以上问题进行优化......

显示对应的Flutter页面

原生跳转不同的Flutter页面
<!-- ViewController.m文件 -->
/*
 * 标记页面的方式
 * 1. 使用路由标记
 * 2. 使用FlutterMethodChannel通信标记
 */
- (IBAction)pushFlutter:(id)sender {
    FlutterViewController *vc = [[FlutterViewController alloc] init];
    // 使用路由标记页面
    [vc setInitialRoute:@"one"];
    [self presentViewController:vc animated:true completion:nil];
}
- (IBAction)pushFlutterTwo:(id)sender {
    FlutterViewController *vc = [[FlutterViewController alloc] init];
    // 使用路由标记页面
    [vc setInitialRoute:@"two"];
    [self presentViewController:vc animated:true completion:nil];
}

<!-- 查看setInitialRoute方法介绍 -->
/**
 * 不建议使用的API
 * Deprecated API to set initial route.
 *
 * Attempts to set the first route that the Flutter app shows if the Flutter
 * 默认路由名称是 /
 * runtime hasn't yet started. The default is "/".
 * 初始化页面的时候,又创建了一个Flutter引擎
 * effect when using `initWithEngine` if the `FlutterEngine` has already been
 * run.
 *
 * @param route The name of the first route to show.
 */
- (void)setInitialRoute:(NSString*)route
    FLUTTER_DEPRECATED("Use FlutterViewController initializer to specify initial route");
<!-- main.dart文件 -->
import 'dart:ui';
import 'package:flutter/material.dart';

void main() => runApp(MyApp(
      // flutter获取原生传入的路由名称
      pageIndex: window.defaultRouteName,
    ));

class MyApp extends StatelessWidget {
  final String pageIndex;

  const MyApp({Key? key, required this.pageIndex}) : super(key: key);

  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: _rootPage(pageIndex),
    );
  }

  //根据pageIndex来返回页面!
  Widget _rootPage(String pageIndex) {
    switch (pageIndex) {
      case 'one':
        return Scaffold(
          appBar: AppBar(title: Text(pageIndex)),
          body: Center(child: Text(pageIndex)),
        );
      case 'two':
        return Scaffold(
          appBar: AppBar(title: Text(pageIndex)),
          body: Center(child: Text(pageIndex)),
        );
      default:
        return Scaffold(
          appBar: AppBar(title: Text(pageIndex)),
          body: Center(child: Text(pageIndex)),
        );
    }
  }
}
按钮一跳转Flutter第一个页面 按钮二跳转Flutter第二个页面
设置跳转模式
- (IBAction)pushFlutter:(id)sender {
    FlutterViewController *vc = [[FlutterViewController alloc] init];
    // 使用路由标记页面
    [vc setInitialRoute:@"one"];
    vc.modalPresentationStyle = UIModalPresentationFullScreen;
    [self presentViewController:vc animated:true completion:nil];
}
全屏模式跳转

我们发现跳转到Flutter页面之后无法返回去了,这个时候就需要Flutter添加事件告诉原生返回去。

退回原生页面

现在对Flutter页面进行包装,使其能够返回原生页面

//根据pageIndex来返回页面!
  Widget _rootPage(String pageIndex) {
    switch (pageIndex) {
      case 'one':
        return Scaffold(
          appBar: AppBar(title: Text(pageIndex)),
          body: Center(
            child: ElevatedButton(
              onPressed: () {
                // 点击按钮的时候给原生发送消息,退出Flutter页面
                const MethodChannel('one_page').invokeMapMethod('exit');
              },
              child: Text(pageIndex),
            ),
          ),
        );
      case 'two':
        return Scaffold(
          appBar: AppBar(title: Text(pageIndex)),
          body: Center(
            child: ElevatedButton(
              onPressed: () {
                const MethodChannel('two_page').invokeMapMethod('exit');
              },
              child: Text(pageIndex),
            ),
          ),
        );
      default:
        return Scaffold(
          appBar: AppBar(title: Text(pageIndex)),
          body: Center(
            child: ElevatedButton(
              onPressed: () {
                const MethodChannel('default_page').invokeMapMethod('exit');
              },
              child: Text(pageIndex),
            ),
          ),
        );
    }
  }
#import "ViewController.h"
#import <Flutter/Flutter.h>

@interface ViewController ()
@property(nonatomic, strong) FlutterEngine* flutterEngine;
@end

@implementation ViewController

// 懒加载创建Flutter引擎,不用每次初始化页面的时候都创建引擎,这样的话内存使用会降下来
-(FlutterEngine *)flutterEngine
{
    if (!_flutterEngine) {
        FlutterEngine * engine = [[FlutterEngine alloc] initWithName:@"hk"];
        if (engine.run) {
            // Flutter引擎运行起来的时候再赋值
            _flutterEngine = engine;
        }
    }
    return _flutterEngine;
}

- (IBAction)pushFlutter:(id)sender {
    FlutterViewController *vc = [[FlutterViewController alloc] initWithEngine:self.flutterEngine nibName:nil bundle:nil];
    // 直接用self.flutterEngine初始化vc,下面设置路由名称就会失效
    [vc setInitialRoute:@"one"];
    [self presentViewController:vc animated:true completion:nil];
}

- (IBAction)pushFlutterTwo:(id)sender {
    FlutterViewController *vc = [[FlutterViewController alloc] initWithEngine:self.flutterEngine nibName:nil bundle:nil];
    // 使用路由标记页面
    [vc setInitialRoute:@"two"];
    [self presentViewController:vc animated:true completion:nil];
}

- (void)viewDidLoad {
    [super viewDidLoad];
    // 调用一次引擎,使其run起来,防止第一次跳转flutter页面卡顿
    self.flutterEngine;
}

@end
查看内存使用情况

直接运行NativeDemo原生工程,内存就会增加到90MB,原因是viewDidLoad方法中加载了Flutter引擎。接着跳转Flutter页面,退出Flutter页面再次进来,内存基本稳定在94.8MB,成功解决了多次跳转Flutter页面,内存成倍增加的问题。

@interface ViewController ()
@property(nonatomic, strong) FlutterEngine* flutterEngine;
// 不用每次跳转Flutter页面都创建FlutterViewController
@property(nonatomic, strong) FlutterViewController* flutterVc;
@end

@implementation ViewController

-(FlutterEngine *)flutterEngine
{
    if (!_flutterEngine) {
        FlutterEngine * engine = [[FlutterEngine alloc] initWithName:@"hk"];
        if (engine.run) {
            _flutterEngine = engine;
        }
    }
    return _flutterEngine;
}

- (IBAction)pushFlutter:(id)sender {
    self.flutterVc.modalPresentationStyle = UIModalPresentationFullScreen;

    //创建channel
    FlutterMethodChannel * methodChannel = [FlutterMethodChannel methodChannelWithName:@"one_page" binaryMessenger:self.flutterVc.binaryMessenger];
    //告诉Flutter对应的页面
    [methodChannel invokeMethod:@"one" arguments:nil];
    
    //弹出页面
    [self presentViewController:self.flutterVc animated:YES completion:nil];
    
    //监听退出
    [methodChannel setMethodCallHandler:^(FlutterMethodCall * _Nonnull call, FlutterResult  _Nonnull result) {
        //如果是exit我就退出页面!
        if ([call.method isEqualToString:@"exit"]) {
            [self.flutterVc dismissViewControllerAnimated:YES completion:nil];
        }
    }];
}

- (void)viewDidLoad {
    [super viewDidLoad];
    self.flutterVc = [[FlutterViewController alloc] initWithEngine:self.flutterEngine nibName:nil bundle:nil];
}

这个时候跳转Flutter页面,点击Flutter页面中间按钮,就能成功返回原生页面。

下面把Flutter页面中的MyApp改成有状态的小部件

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

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

class MyApp extends StatefulWidget {
  const MyApp({Key? key}) : super(key: key);

  @override
  _MyAppState createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  final MethodChannel _oneChannel = const MethodChannel('one_page');
  final MethodChannel _twoChannel = const MethodChannel('two_page');

  String pageIndex = 'one';

  @override
  void initState() {
    super.initState();
    // flutter设置监听
    _oneChannel.setMethodCallHandler((call) {
      pageIndex = call.method;
      print(call.method);
      setState(() {});
      return Future(() {});
    });
    _twoChannel.setMethodCallHandler((call) {
      pageIndex = call.method;
      print(call.method);
      setState(() {});
      return Future(() {});
    });
  }

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: _rootPage(pageIndex),
    );
  }

  //根据pageIndex来返回页面!
  Widget _rootPage(String pageIndex) {
    switch (pageIndex) {
      case 'one':
        return Scaffold(
          appBar: AppBar(title: Text(pageIndex)),
          body: Center(
            child: ElevatedButton(
              onPressed: () {
                _oneChannel.invokeMapMethod('exit');
              },
              child: Text(pageIndex),
            ),
          ),
        );
      case 'two':
        return Scaffold(
          appBar: AppBar(title: Text(pageIndex)),
          body: Center(
            child: ElevatedButton(
              onPressed: () {
                _twoChannel.invokeMapMethod('exit');
              },
              child: Text(pageIndex),
            ),
          ),
        );
      default:
        return Scaffold(
          appBar: AppBar(title: Text(pageIndex)),
          body: Center(
            child: ElevatedButton(
              onPressed: () {
                const MethodChannel('default_page').invokeMapMethod('exit');
              },
              child: Text(pageIndex),
            ),
          ),
        );
    }
  }
}
返回原生页面

点击Flutter页面中间按钮,成功返回原生页面。

Flutter和原生通信

Flutter与原生通信的Channel类型

下面来练习下BasicMessageChannel通信

// 初始化BasicMessageChannel
class _MyAppState extends State<MyApp> {
  // BasicMessageChannel有一个编解码器
  final BasicMessageChannel _messageChannel =
  const BasicMessageChannel('messageChannel', StandardMessageCodec());
......

// 接收原生发送的消息
 @override
  void initState() {
    super.initState();
    _messageChannel.setMessageHandler((message) {
      print('收到来自iOS的$message');
      return Future(() {});
    });
......

// 给原生发送消息
case 'one':
  return Scaffold(
    appBar: AppBar(title: Text(pageIndex)),
    body: Column(
      mainAxisAlignment: MainAxisAlignment.center,
      children: [
        ElevatedButton(
          onPressed: () {
            _oneChannel.invokeMapMethod('exit');
          },
          child: Text(pageIndex),
        ),
        TextField(
          onChanged: (String str) {
            // 给原生发送消息
            _messageChannel.send(str);
          },
        )
      ],
    ),
  );
// 声明FlutterBasicMessageChannel
@interface ViewController ()
@property(nonatomic, strong) FlutterBasicMessageChannel * msgChannel;
@end

// 接收Flutter发送的消息
- (void)viewDidLoad {
    [super viewDidLoad];
    self.msgChannel = [FlutterBasicMessageChannel messageChannelWithName:@"messageChannel" binaryMessenger:self.flutterVc.binaryMessenger];
    [self.msgChannel setMessageHandler:^(id  _Nullable message, FlutterReply  _Nonnull callback) {
        NSLog(@"收到Flutter的:%@",message);
    }];
}

// 给Flutter发送消息
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
    static int a = 0;
    // 给Flutter发送消息
    [self.msgChannel sendMessage:[NSString stringWithFormat:@"%d",a++]];
}

下载Flutter引擎源码

查看Flutter引擎版本号

$ flutter doctor -v
查看Flutter引擎版本

查看Flutter Channel版本

$ flutter channel
Flutter channels:
  master  //主分支,一般是github上面的分支
  dev  //开发分支,正在开发还没有完成的分支
  beta  //新特性分支
* stable //channel稳定分支,一般用的是这个分支

// 切换channel版本,切换到master版本
$ flutter channel master

Flutter引擎开源源码

默认是mian分支

查看Flutter引擎完整版本号

$ cat $FLUTTER/bin/internal/engine.version
241c87ad800beeab545ab867354d4683d5bfb6ce
下载引擎代码

⼯具准备

$ git clone https://chromium.googlesource.com/chromium/tools/depot_tools.git
$ vi ~/.zshrc
配置环境变量
$ brew install ant

安装Homebrew

// 查看有没有安装 ant,列表中有的话就表示已经安装
$ brew list 
$ cd Desktop
$ mkdir engine
$ cd engine
$ touch .gclient
solutions = [
    {
        "managed":  False,
        "name":  "src/flutter",
        // 6bc433c6b6b5b98dcf4cc11aff31cdee90849f32就是CommitID
        "url":  "git@github.com:flutter/engine.git@6bc433c6b6b5b98dcf4cc11aff31cdee90849f32",
        "custom_deps":  {},
        "deps_file":  "DEPS",
        "safesync_url":  "",
    },
]

CommitID~/flutter/bin/internal/engine.version文件中

$ gclient sync
Flutter引擎目录

注意:引擎下载是断点续传的,如果网络断掉,重新执行该命令即可。

引擎升级相关

当我们升级了Flutter的SDK,我们想要升级引擎代码。直接更新.gclient⽂件。

$ cat $FLUTTER/bin/internal/engine.version
241c87ad800beeab545ab867354d4683d5bfb6ce
solutions = [
    {
        "managed":  False,
        "name":  "src/flutter",
        // 更新CommitID
        "url":  "git@github.com:flutter/engine.git@241c87ad800beeab545ab867354d4683d5bfb6ce",
        "custom_deps":  {},
        "deps_file":  "DEPS",
        "safesync_url":  "",
    },
]
$ git pull
$ git reset --hard commitID
$ gclient sync --with_branch_heads --with_tags --verbose

编译引擎源码

image.png
#构建iOS设备使⽤的引擎
#真机debug版本
$ ./gn --ios --unoptimized
#真机release版本(⽇常开发使⽤,如果我们要⾃定义引擎)
$ ./gn --ios --unoptimized --runtime-mode=release
#模拟器版本
$ ./gn --ios --simulator --unoptimized
#主机端(Mac)构建
$ ./gn --unoptimized

构建完成会有四个Xcode⼯程

image.png
$ ninja -C host_debug_unopt && ninja -C ios_debug_sim_unopt && ninja -C ios_debug_unopt && ninja -C ios_release_unopt

编译完成之后文件大概是26.79G

上一篇下一篇

猜你喜欢

热点阅读