配置项目代码关联引擎源码
通过下载引擎源码可以进行分析以及动态调试
-
Flutter引擎编译成功之后,我们获取到模拟器x86架构下的Xcode工程(目录:/src/out/ios_debug_sim_unopt); - 在
/ios_debug_sim_unopt目录下会有一个Flutter.framework/Flutter引擎库,然后把我们的Flutter项目配置成这个framework,即配置自定义引擎;
下面新建工程flutter_engine_demo(注意iOS与Android平台的语言选择OC以及Java),让其加载上面编译好的自定义引擎
-
Flutter引擎源码最终编译成了Xcode工程,我们是基于Xcode进行动态调试的,因此这里要先在Xcode中进行配置,打开flutter_engine_demo工程中ios目录下的Runner工程

Generated.xcconfig是通用环境配置,我们在这个文件中进行配置

-
flutter_engine_demo工程在模拟器上面运行起来,关闭Android Studio,接下来我们在Runner工程中调试 查看工程执行的脚本


-
xcode_backend.sh脚本执行完成之后,还会执行xcode_backend.dart脚本文件

-
Generated.xcconfig文件中配置的变量,会在xcode_backend.dart文件中使用,比如FLUTTER_APPLICATION_PATH环境变量

这就是Android Studio执行了Flutter工程会调用Xcode,而Xcode又关联到Android Studio的过程;关联的过程都在脚本文件中处理好了,后面如果想配置持续集成进行打包,也需要配置相应的脚本文件。
-
Generated.xcconfig文件中进行配置,使其加载Flutter自定义引擎

-
Runner工程添加断点调试

通过断点调试,我们发现了touchesBegan的源码实现,这里的源码在FlutterViewController.mm文件中,即编译好的引擎中目录:/src/out/ios_debug_sim_unopt;
下面验证FlutterViewController.mm就在关联的引擎工程中


我们在Runner工程中进行调试,添加了注释;然后打开引擎工程发现注释存在,就证明了Runner与自定义引擎存在了关联。
- 检查
flutter_engine,进行编译

-
Runner工程添加断点,调试到引擎中的源码,在源码中添加日志打印

- 在源码中添加了
日志打印,需要重新编译Flutter引擎才会执行

重新运行Runner工程,点击屏幕查看日志打印

Runner成功与Flutter引擎进行关联,而且还可以修改引擎源码进行调试(每次修改源码都需要重新编译引擎)。
查看引擎源码目录
flutter_engine引擎源码中查看FlutterViewController.mm文件的目录,发现目录结构为/flutter/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm

我们发现引擎源码在flutter目录下,并不在out目录,说明源码只有一份,根据真机、模拟器不同平台,编译出不同的Flutter.framework库。
查看工程Flutter.framework/Flutter的哈希值是否相同?
-
Products/Runner.app->Show in Finder

- 查看哈希值,用于检测
Flutter引擎的二进制文件是否发生变化

// 查看哈希值命令
$ md5 Flutter
// Flutter.framework/Flutter 的哈希值
4a794a8d53a0fadebc0453fa16e56518
// Runner/Flutter.framework/Flutter 的哈希值
4a794a8d53a0fadebc0453fa16e56518
通过对比,哈希值相同,说明是一个产物。
- 现在我们把
Generated.xcconfig文件中加载自定义引擎的配置注释掉,再次编译工程,查看哈希值

通过对比我们发现自定义引擎与发布版本的引擎哈希值不相同。
-
Generated.xcconfig文件中再次加载自定义引擎,编译工程查看哈希值

我们的工程在每次编译生成Flutter.framework的时候,可能会添加一些其它内容,我们不能单纯的比对Flutter.framwork的哈希值来判断Framework是否发生更新。
疑问?
Runnder项目实际上获取到的是编译完成的Flutter.framework,而Flutter.framework/Flutter是如何定位到源代码的路径呢?跨工程是如何定位到的?
Flutter二进制文件中包含一些调试信息,使其进行关联。
检查二进制文件中是否包含调试信息
查看发布版本的二进制文件调试信息
-
Generated.xcconfig文件中加载自定义引擎的配置注释掉 Products/Runner.app -> Show in Finder



由打印信息可知,发布版本会把调试信息隐藏掉。
查看自定义引擎的二进制文件调试信息
-
Generated.xcconfig文件中的自定义引擎配置打开 Products/Runner.app -> Show in Finder


通过终端成功查看到自定义引擎的调试信息;说明Runner工程成功与自定义引擎关联到了一块,就可以直接调试源码。
检查⼆进制是否含有调试信息
- lipo命令
#可以查看包含的架构
$ lipo -info xxx
#拆分架构
$ lipo xxx -thin armv7 -output armv7_xxx
#合并多架构
$ lipo -create xxx.a xxx.a -output xxx.a
- LLDB检查是否含有调试信息
$ lldb --file Flutter_arm64
(lldb) target create "Flutter_arm64"
Current executable set to 'Flutter_arm64' (arm64)
// 查看有多少个编译单元,即.o文件
.(lldb) script lldb.target.module['Flutter_arm64'] .GetNumCompileUnits()
1
(lldb)
- 使⽤python列出模块的所有编译单元的完整路径
(lldb) target create "Flutter_arm64"
Current executable set to 'Flutter_arm64' (arm64).
(lldb) script
Python Interactive Interpreter. To exit, type 'quit()', 'exit()' or Ctrl-D.
// 获取到模型
>>> m = lldb.target.module['Flutter_arm64']
>>> for i in range(m.GetNumCompileUnits()):
... cu = m.GetCompileUnitAtIndex(i).file.fullpath
... print(cu)
...
None
>>>
调试引擎源码Channel底层实现
下面我们就通过Runner工程来调试Flutter引擎源码
-
flutter_engine_demo工程中添加给原生发送消息的代码
<!-- main.dart文件 -->
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : 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: const EnginePage(),
);
}
}
class EnginePage extends StatefulWidget {
const EnginePage({Key? key}) : super(key: key);
@override
_EnginePageState createState() => _EnginePageState();
}
class _EnginePageState extends State<EnginePage> {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('EnginePage'),
),
body: Center(
child: ElevatedButton(
onPressed: () {
// 给原生发送消息
const MethodChannel('engine_page')
.invokeMapMethod('method_channel');
},
child: const Icon(Icons.add),
),
),
);
}
}
-
Generated.xcconfig文件中进行自定义引擎相关配置(注意:如果引擎路径发生了变化,需要gn构建、ninja编译⼯程) -
Runner工程中AppDelegate.m添加如下代码
#import "AppDelegate.h"
#import "GeneratedPluginRegistrant.h"
@interface AppDelegate ()
@property(nonatomic, strong) FlutterEngine* flutterEngine;
@property(nonatomic, strong) FlutterViewController* flutterVc;
@property(nonatomic, strong) FlutterMethodChannel* methodChannel;
@end
@implementation AppDelegate
// 懒加载引擎,通过引擎获取VC
- (FlutterEngine *)flutterEngine {
if (!_flutterEngine) {
FlutterEngine * engine = [[FlutterEngine alloc] initWithName:@"hk"];
if (engine.run) {
_flutterEngine = engine;
}
}
return _flutterEngine;
}
- (BOOL)application:(UIApplication *)application
didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
[GeneratedPluginRegistrant registerWithRegistry:self];
self.flutterVc = [[FlutterViewController alloc] initWithEngine:self.flutterEngine nibName:nil bundle:nil];
// 通过VC获取channel
// MethodChannel:传递方法的调用
self.methodChannel = [FlutterMethodChannel methodChannelWithName:@"engine_page" binaryMessenger:self.flutterVc.binaryMessenger];
[self.methodChannel setMethodCallHandler:^(FlutterMethodCall * _Nonnull call, FlutterResult _Nonnull result) {
NSLog(@"收到了:%@",call.method);
}];
// BasicMessageChannel:传递字符串和半结构化信息
[FlutterBasicMessageChannel messageChannelWithName:@"123" binaryMessenger:self.flutterVc.binaryMessenger];
// EventChannel:传递数据流
[FlutterEventChannel eventChannelWithName:@"123" binaryMessenger:self.flutterVc.binaryMessenger];
// Override point for customization after application launch.
return [super application:application didFinishLaunchingWithOptions:launchOptions];
}
@end
- 添加断点进行调试



通过methodChannelWithName源码我们发现,如果不传解码器会有默认的解码器,而且是一个单例;eventChannelWithName方法也是一样的,会默认传一个单例解码器。

保存name、messenger、解码器。
- 断点调试
methodChannel接收Flutter发送过来的消息


如果有连接connection先清空,否则设置消息回调




用字典把name与handler进行保存

self.flutterVc.binaryMessenger与setMethodCallHandler方法中创建的messageHandler是同一个对象。
疑问?其实通讯的是数据,那么传递的数据底层是怎么交互的?推荐跟踪invokeMapMethod底层源码......
codec编解码器
数据是怎么解析最终变成二进制的?下面探索编解码器的底层实现...
前面我们学习的任何一种channel,内部都有一个编解码器,编解码器其实是一种通讯协议
-
flutter_engine源码中搜索MessageCodec,发现是一种协议,而且编解码都是对二进制进行

- 搜索
MethodCodec进行查看

FlutterEventChannel就是通过MethodCodec编解码的
- 查看
FlutterMethodCall类

在Flutter中,MessageCodec有多种实现:
-
FlutterStandardMessageCodec:是FlutterBasicMessageChannel中默认使用的编解码器。(底层使用FlutterStandardReaderWriter实现的)。用于数据类型和二进制数据之间的编解码。支持基础数据类型包(bool 、char 、double 、float 、int 、long 、short 、String 、Array 、Dictionary)以及二进制数据。 -
FlutterBinaryCodec:用于二进制数据和二进制数据之间的编解码,在实现上只是原封不动的将接收到的二进制数据返回。 -
FlutterStringCodec:用于字符串与二进制数据之间的编解码,对于字符串采用UTF-8编码格式。 -
FlutterJSONMessageCodec:用于数据类型与二进制数据之间的编解码,支持基础数据类型(bool 、char 、double 、float 、int 、long 、short 、String 、Array 、Dictionary)。在iOS端使用NSJSONSerialization作为序列化的工具。
查看FlutterStandardMessageCodec源码


查看FlutterStandardMethodCodec源码


编解码器底层都使用的FlutterStandardReaderWriter实现的,下面我们来分析FlutterStandardReaderWriter的源码



