美文网首页Flutter
Flutter引擎源码调试与Channel底层原理探索

Flutter引擎源码调试与Channel底层原理探索

作者: 浅墨入画 | 来源:发表于2021-12-26 18:41 被阅读0次

    配置项目代码关联引擎源码

    通过下载引擎源码可以进行分析以及动态调试

    • 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工程
    基于Debug与Release环境配置

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

    通用配置
    • flutter_engine_demo工程在模拟器上面运行起来,关闭Android Studio,接下来我们在Runner工程中调试
    • 查看工程执行的脚本
    查看脚本 脚本目录
    • xcode_backend.sh脚本执行完成之后,还会执行xcode_backend.dart脚本文件
    执行xcode_backend.dart脚本
    • Generated.xcconfig文件中配置的变量,会在xcode_backend.dart文件中使用,比如FLUTTER_APPLICATION_PATH环境变量
    用AS查看xcode_backend.dart脚本文件

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

    • Generated.xcconfig文件中进行配置,使其加载Flutter自定义引擎
    关联自定义引擎相关配置
    • Runner工程添加断点调试
    断点调试

    通过断点调试,我们发现了touchesBegan的源码实现,这里的源码在FlutterViewController.mm文件中,即编译好的引擎中目录:/src/out/ios_debug_sim_unopt

    下面验证FlutterViewController.mm就在关联的引擎工程中

    Runner看到的源码中添加注释 引擎工程

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

    • 检查flutter_engine,进行编译
    编译Flutter引擎
    • 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.framework
    • 查看哈希值,用于检测Flutter引擎的二进制文件是否发生变化
    查看哈希值
    // 查看哈希值命令
    $ md5 Flutter
    // Flutter.framework/Flutter 的哈希值
    4a794a8d53a0fadebc0453fa16e56518
    // Runner/Flutter.framework/Flutter 的哈希值
    4a794a8d53a0fadebc0453fa16e56518
    

    通过对比,哈希值相同,说明是一个产物。

    • 现在我们把Generated.xcconfig文件中加载自定义引擎的配置注释掉,再次编译工程,查看哈希值
    查看发布版本的Flutter引擎哈希值

    通过对比我们发现自定义引擎发布版本的引擎哈希值不相同。

    • Generated.xcconfig文件中再次加载自定义引擎,编译工程查看哈希值
    哈希值对比

    我们的工程在每次编译生成Flutter.framework的时候,可能会添加一些其它内容,我们不能单纯的比对Flutter.framwork的哈希值来判断Framework是否发生更新。

    疑问?
    Runnder项目实际上获取到的是编译完成的Flutter.framework,而Flutter.framework/Flutter是如何定位到源代码的路径呢?跨工程是如何定位到的?
    Flutter二进制文件中包含一些调试信息,使其进行关联。

    检查二进制文件中是否包含调试信息

    查看发布版本的二进制文件调试信息

    • Generated.xcconfig文件中加载自定义引擎的配置注释掉
    • Products/Runner.app -> Show in Finder
    Flutter二进制文件 查看Flutter中是否包含调试信息 查看发布版本的调试信息

    由打印信息可知,发布版本会把调试信息隐藏掉

    查看自定义引擎的二进制文件调试信息
    • Generated.xcconfig文件中的自定义引擎配置打开
    • Products/Runner.app -> Show in Finder
    查看Flutter中是否包含调试信息 查看自定义版本的调试信息

    通过终端成功查看到自定义引擎的调试信息;说明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
    
    • 添加断点进行调试
    image.png image.png image.png

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

    image.png

    保存namemessenger解码器

    • 断点调试methodChannel接收Flutter发送过来的消息
    image.png image.png

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

    image.png image.png image.png image.png

    用字典把namehandler进行保存

    image.png

    self.flutterVc.binaryMessengersetMethodCallHandler方法中创建的messageHandler是同一个对象。

    疑问?其实通讯的是数据,那么传递的数据底层是怎么交互的?推荐跟踪invokeMapMethod底层源码......

    codec编解码器

    数据是怎么解析最终变成二进制的?下面探索编解码器的底层实现...

    前面我们学习的任何一种channel,内部都有一个编解码器编解码器其实是一种通讯协议

    • flutter_engine源码中搜索MessageCodec,发现是一种协议,而且编解码都是对二进制进行
    image.png
    • 搜索MethodCodec进行查看
    image.png

    FlutterEventChannel就是通过MethodCodec编解码的

    • 查看FlutterMethodCall
    image.png
    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源码

    image.png image.png

    查看FlutterStandardMethodCodec源码

    image.png image.png

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

    image.png image.png 写数据 读数据

    相关文章

      网友评论

        本文标题:Flutter引擎源码调试与Channel底层原理探索

        本文链接:https://www.haomeiwen.com/subject/ndndqrtx.html