Flutter引擎源碼調(diào)試與Channel底層原理探索

配置項(xiàng)目代碼關(guān)聯(lián)引擎源碼

通過(guò)下載引擎源碼可以進(jìn)行分析以及動(dòng)態(tài)調(diào)試

  • Flutter引擎編譯成功之后鞭莽,我們獲取到模擬器x86架構(gòu)下的Xcode工程(目錄:/src/out/ios_debug_sim_unopt)坊秸;
  • /ios_debug_sim_unopt目錄下會(huì)有一個(gè)Flutter.framework/Flutter引擎庫(kù),然后把我們的Flutter項(xiàng)目配置成這個(gè)framework澎怒,即配置自定義引擎褒搔;

下面新建工程flutter_engine_demo(注意iOS與Android平臺(tái)的語(yǔ)言選擇OC以及Java),讓其加載上面編譯好的自定義引擎

  • Flutter引擎源碼最終編譯成了Xcode工程喷面,我們是基于Xcode進(jìn)行動(dòng)態(tài)調(diào)試的星瘾,因此這里要先在Xcode中進(jìn)行配置,打開(kāi)flutter_engine_demo工程中ios目錄下的Runner工程
基于Debug與Release環(huán)境配置

Generated.xcconfig是通用環(huán)境配置惧辈,我們?cè)谶@個(gè)文件中進(jìn)行配置

通用配置
  • flutter_engine_demo工程在模擬器上面運(yùn)行起來(lái)琳状,關(guān)閉Android Studio,接下來(lái)我們?cè)?code>Runner工程中調(diào)試
  • 查看工程執(zhí)行的腳本
查看腳本
腳本目錄
  • xcode_backend.sh腳本執(zhí)行完成之后盒齿,還會(huì)執(zhí)行xcode_backend.dart腳本文件
執(zhí)行xcode_backend.dart腳本
  • Generated.xcconfig文件中配置的變量算撮,會(huì)在xcode_backend.dart文件中使用,比如FLUTTER_APPLICATION_PATH環(huán)境變量
用AS查看xcode_backend.dart腳本文件

這就是Android Studio執(zhí)行了Flutter工程會(huì)調(diào)用Xcode县昂,而Xcode又關(guān)聯(lián)到Android Studio的過(guò)程肮柜;關(guān)聯(lián)的過(guò)程都在腳本文件中處理好了,后面如果想配置持續(xù)集成進(jìn)行打包倒彰,也需要配置相應(yīng)的腳本文件审洞。

  • Generated.xcconfig文件中進(jìn)行配置,使其加載Flutter自定義引擎
關(guān)聯(lián)自定義引擎相關(guān)配置
  • Runner工程添加斷點(diǎn)調(diào)試
斷點(diǎn)調(diào)試

通過(guò)斷點(diǎn)調(diào)試,我們發(fā)現(xiàn)了touchesBegan的源碼實(shí)現(xiàn)芒澜,這里的源碼在FlutterViewController.mm文件中仰剿,即編譯好的引擎中目錄:/src/out/ios_debug_sim_unopt

下面驗(yàn)證FlutterViewController.mm就在關(guān)聯(lián)的引擎工程中

Runner看到的源碼中添加注釋
引擎工程

我們?cè)?code>Runner工程中進(jìn)行調(diào)試痴晦,添加了注釋南吮;然后打開(kāi)引擎工程發(fā)現(xiàn)注釋存在,就證明了Runner自定義引擎存在了關(guān)聯(lián)誊酌。

  • 檢查flutter_engine部凑,進(jìn)行編譯
編譯Flutter引擎
  • Runner工程添加斷點(diǎn),調(diào)試到引擎中的源碼碧浊,在源碼中添加日志打印
斷點(diǎn)調(diào)試
  • 在源碼中添加了日志打印涂邀,需要重新編譯Flutter引擎才會(huì)執(zhí)行
編譯引擎

重新運(yùn)行Runner工程,點(diǎn)擊屏幕查看日志打印

點(diǎn)擊屏幕打印日志

Runner成功與Flutter引擎進(jìn)行關(guān)聯(lián)箱锐,而且還可以修改引擎源碼進(jìn)行調(diào)試(每次修改源碼都需要重新編譯引擎)比勉。

查看引擎源碼目錄

flutter_engine引擎源碼中查看FlutterViewController.mm文件的目錄,發(fā)現(xiàn)目錄結(jié)構(gòu)為/flutter/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm

目錄結(jié)構(gòu)

我們發(fā)現(xiàn)引擎源碼flutter目錄下驹止,并不在out目錄浩聋,說(shuō)明源碼只有一份,根據(jù)真機(jī)臊恋、模擬器不同平臺(tái)衣洁,編譯出不同的Flutter.framework庫(kù)。

查看工程Flutter.framework/Flutter的哈希值是否相同捞镰?
  • Products/Runner.app -> Show in Finder
查看Flutter.framework
  • 查看哈希值闸与,用于檢測(cè)Flutter引擎的二進(jìn)制文件是否發(fā)生變化
查看哈希值
// 查看哈希值命令
$ md5 Flutter
// Flutter.framework/Flutter 的哈希值
4a794a8d53a0fadebc0453fa16e56518
// Runner/Flutter.framework/Flutter 的哈希值
4a794a8d53a0fadebc0453fa16e56518

通過(guò)對(duì)比毙替,哈希值相同岸售,說(shuō)明是一個(gè)產(chǎn)物。

  • 現(xiàn)在我們把Generated.xcconfig文件中加載自定義引擎的配置注釋掉厂画,再次編譯工程凸丸,查看哈希值
查看發(fā)布版本的Flutter引擎哈希值

通過(guò)對(duì)比我們發(fā)現(xiàn)自定義引擎發(fā)布版本的引擎哈希值不相同。

  • Generated.xcconfig文件中再次加載自定義引擎袱院,編譯工程查看哈希值
哈希值對(duì)比

我們的工程在每次編譯生成Flutter.framework的時(shí)候屎慢,可能會(huì)添加一些其它內(nèi)容,我們不能單純的比對(duì)Flutter.framwork的哈希值來(lái)判斷Framework是否發(fā)生更新忽洛。

疑問(wèn)腻惠?
Runnder項(xiàng)目實(shí)際上獲取到的是編譯完成的Flutter.framework,而Flutter.framework/Flutter是如何定位到源代碼的路徑呢欲虚?跨工程是如何定位到的集灌?
Flutter二進(jìn)制文件中包含一些調(diào)試信息,使其進(jìn)行關(guān)聯(lián)复哆。

檢查二進(jìn)制文件中是否包含調(diào)試信息

查看發(fā)布版本的二進(jìn)制文件調(diào)試信息

  • Generated.xcconfig文件中加載自定義引擎的配置注釋掉
  • Products/Runner.app -> Show in Finder
Flutter二進(jìn)制文件
查看Flutter中是否包含調(diào)試信息
查看發(fā)布版本的調(diào)試信息

由打印信息可知欣喧,發(fā)布版本會(huì)把調(diào)試信息隱藏掉腌零。

查看自定義引擎的二進(jìn)制文件調(diào)試信息
  • Generated.xcconfig文件中的自定義引擎配置打開(kāi)
  • Products/Runner.app -> Show in Finder
查看Flutter中是否包含調(diào)試信息
查看自定義版本的調(diào)試信息

通過(guò)終端成功查看到自定義引擎的調(diào)試信息;說(shuō)明Runner工程成功與自定義引擎關(guān)聯(lián)到了一塊唆阿,就可以直接調(diào)試源碼益涧。

檢查?進(jìn)制是否含有調(diào)試信息
  • lipo命令
#可以查看包含的架構(gòu)
$ lipo -info xxx
#拆分架構(gòu)
$ lipo xxx -thin armv7 -output armv7_xxx
#合并多架構(gòu)
$ lipo -create xxx.a xxx.a -output xxx.a
  • LLDB檢查是否含有調(diào)試信息
$ lldb --file Flutter_arm64
(lldb) target create "Flutter_arm64" 
Current executable set to 'Flutter_arm64' (arm64)
// 查看有多少個(gè)編譯單元,即.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
>>>

調(diào)試引擎源碼Channel底層實(shí)現(xiàn)

下面我們就通過(guò)Runner工程來(lái)調(diào)試Flutter引擎源碼

  • flutter_engine_demo工程中添加給原生發(fā)送消息的代碼
<!-- 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: () {
            // 給原生發(fā)送消息
            const MethodChannel('engine_page')
                .invokeMapMethod('method_channel');
          },
          child: const Icon(Icons.add),
        ),
      ),
    );
  }
}
  • Generated.xcconfig文件中進(jìn)行自定義引擎相關(guān)配置(注意:如果引擎路徑發(fā)生了變化驯鳖,需要gn構(gòu)建闲询、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
// 懶加載引擎,通過(guò)引擎獲取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];

    // 通過(guò)VC獲取channel
    // MethodChannel:傳遞方法的調(diào)用
    self.methodChannel = [FlutterMethodChannel methodChannelWithName:@"engine_page" binaryMessenger:self.flutterVc.binaryMessenger];

    [self.methodChannel setMethodCallHandler:^(FlutterMethodCall * _Nonnull call, FlutterResult _Nonnull result) {
             NSLog(@"收到了:%@",call.method);
     }];

    // BasicMessageChannel:傳遞字符串和半結(jié)構(gòu)化信息
    [FlutterBasicMessageChannel messageChannelWithName:@"123" binaryMessenger:self.flutterVc.binaryMessenger];

    // EventChannel:傳遞數(shù)據(jù)流
    [FlutterEventChannel eventChannelWithName:@"123" binaryMessenger:self.flutterVc.binaryMessenger];

    // Override point for customization after application launch.
    return [super application:application didFinishLaunchingWithOptions:launchOptions];
}
@end
  • 添加斷點(diǎn)進(jìn)行調(diào)試
image.png
image.png
image.png

通過(guò)methodChannelWithName源碼我們發(fā)現(xiàn)臼隔,如果不傳解碼器會(huì)有默認(rèn)的解碼器嘹裂,而且是一個(gè)單例eventChannelWithName方法也是一樣的摔握,會(huì)默認(rèn)傳一個(gè)單例解碼器寄狼。

image.png

保存namemessenger氨淌、解碼器泊愧。

  • 斷點(diǎn)調(diào)試methodChannel接收Flutter發(fā)送過(guò)來(lái)的消息
image.png
image.png

如果有連接connection先清空,否則設(shè)置消息回調(diào)

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

用字典把namehandler進(jìn)行保存

image.png

self.flutterVc.binaryMessengersetMethodCallHandler方法中創(chuàng)建的messageHandler是同一個(gè)對(duì)象盛正。

疑問(wèn)删咱?其實(shí)通訊的是數(shù)據(jù),那么傳遞的數(shù)據(jù)底層是怎么交互的豪筝?推薦跟蹤invokeMapMethod底層源碼......

codec編解碼器

數(shù)據(jù)是怎么解析最終變成二進(jìn)制的痰滋?下面探索編解碼器的底層實(shí)現(xiàn)...

前面我們學(xué)習(xí)的任何一種channel,內(nèi)部都有一個(gè)編解碼器续崖,編解碼器其實(shí)是一種通訊協(xié)議

  • flutter_engine源碼中搜索MessageCodec敲街,發(fā)現(xiàn)是一種協(xié)議,而且編解碼都是對(duì)二進(jìn)制進(jìn)行
image.png
  • 搜索MethodCodec進(jìn)行查看
image.png

FlutterEventChannel就是通過(guò)MethodCodec編解碼的

  • 查看FlutterMethodCall
image.png
Flutter中严望,MessageCodec有多種實(shí)現(xiàn):
  • FlutterStandardMessageCodec:是FlutterBasicMessageChannel中默認(rèn)使用的編解碼器多艇。(底層使用FlutterStandardReaderWriter實(shí)現(xiàn)的)。用于數(shù)據(jù)類型和二進(jìn)制數(shù)據(jù)之間的編解碼像吻。支持基礎(chǔ)數(shù)據(jù)類型包(bool 峻黍、char 、double 拨匆、float 姆涩、int 、long 惭每、short 骨饿、String 、Array 、Dictionary)以及二進(jìn)制數(shù)據(jù)样刷。
  • FlutterBinaryCodec:用于二進(jìn)制數(shù)據(jù)和二進(jìn)制數(shù)據(jù)之間的編解碼仑扑,在實(shí)現(xiàn)上只是原封不動(dòng)的將接收到的二進(jìn)制數(shù)據(jù)返回。
  • FlutterStringCodec:用于字符串與二進(jìn)制數(shù)據(jù)之間的編解碼置鼻,對(duì)于字符串采用UTF-8編碼格式镇饮。
  • FlutterJSONMessageCodec:用于數(shù)據(jù)類型與二進(jìn)制數(shù)據(jù)之間的編解碼,支持基礎(chǔ)數(shù)據(jù)類型(bool 箕母、char 储藐、double 、float 嘶是、int 钙勃、long 、short 聂喇、String 辖源、Array 、Dictionary)希太。在iOS端使用NSJSONSerialization作為序列化的工具克饶。

查看FlutterStandardMessageCodec源碼

image.png
image.png

查看FlutterStandardMethodCodec源碼

image.png
image.png

編解碼器底層都使用的FlutterStandardReaderWriter實(shí)現(xiàn)的,下面我們來(lái)分析FlutterStandardReaderWriter的源碼

image.png
image.png
寫(xiě)數(shù)據(jù)
讀數(shù)據(jù)
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末誊辉,一起剝皮案震驚了整個(gè)濱河市矾湃,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌堕澄,老刑警劉巖邀跃,帶你破解...
    沈念sama閱讀 218,755評(píng)論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異蛙紫,居然都是意外死亡拍屑,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,305評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門惊来,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)丽涩,“玉大人棺滞,你說(shuō)我怎么就攤上這事裁蚁。” “怎么了继准?”我有些...
    開(kāi)封第一講書(shū)人閱讀 165,138評(píng)論 0 355
  • 文/不壞的土叔 我叫張陵枉证,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我移必,道長(zhǎng)室谚,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,791評(píng)論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮秒赤,結(jié)果婚禮上猪瞬,老公的妹妹穿的比我還像新娘。我一直安慰自己入篮,他們只是感情好陈瘦,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,794評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著潮售,像睡著了一般痊项。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上酥诽,一...
    開(kāi)封第一講書(shū)人閱讀 51,631評(píng)論 1 305
  • 那天鞍泉,我揣著相機(jī)與錄音,去河邊找鬼肮帐。 笑死咖驮,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的训枢。 我是一名探鬼主播游沿,決...
    沈念sama閱讀 40,362評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼肮砾!你這毒婦竟也來(lái)了诀黍?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 39,264評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤仗处,失蹤者是張志新(化名)和其女友劉穎眯勾,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體婆誓,經(jīng)...
    沈念sama閱讀 45,724評(píng)論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡吃环,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,900評(píng)論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了洋幻。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片郁轻。...
    茶點(diǎn)故事閱讀 40,040評(píng)論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖文留,靈堂內(nèi)的尸體忽然破棺而出好唯,到底是詐尸還是另有隱情,我是刑警寧澤燥翅,帶...
    沈念sama閱讀 35,742評(píng)論 5 346
  • 正文 年R本政府宣布蹲堂,位于F島的核電站吩跋,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,364評(píng)論 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦脏榆、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,944評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至典唇,卻和暖如春镊折,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背介衔。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,060評(píng)論 1 270
  • 我被黑心中介騙來(lái)泰國(guó)打工恨胚, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人炎咖。 一個(gè)月前我還...
    沈念sama閱讀 48,247評(píng)論 3 371
  • 正文 我出身青樓赃泡,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親乘盼。 傳聞我的和親對(duì)象是個(gè)殘疾皇子升熊,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,979評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容