Flutter 在IOS上的啟動流程-14

AidenCang發表於2019-12-10

Flutter在IOS上使用的邏輯和Android很相似,App主要是提供初始化和View給FlutterEngine做為渲染,其他的還是FlutterEngine在處理後續的工作,App啟動的時候是做了哪些工作,才能辦FlutterUI介面載入出來??

1.IOSApp包結構

2.FlutterEngine庫是怎麼載入的

3.FlutterEngine初始化過程

4.FlutterEngine啟動過程

Ipa包結構

ios包中Flutter相關的資原始檔是儲存在Frameworks/App.framework/flutter_assets Bundle中,引擎相關的檔案是儲存在Frameworks/Flutter.framework在兩個地方儲存了Flutter相關的程式碼,在App結構中,我們看到了其實Flutter相關的程式碼在App中是相互分離的,沒有多少相關連,FlutterEngine編譯的時候回和App的程式碼整合在一起,方便啟動的時候載入FlutterEngine(我們是否可以使用手動的方式來載入Flutter引擎庫,這樣就和ios的App更加獨立)後續再來處理這個問題

flutter build ios --release
複製程式碼

執行上面的命令,等待打包工具生成ios這些檔案,結構如下,中間刪除了一下圖片,關注分析的重點。

➜  Runner.app tree -L 3
.
├── AppFrameworkInfo.plist
........
├── Assets.car
├── Base.lproj
│   ├── LaunchScreen.storyboardc
│   │   ├── 01J-lp-oVM-view-Ze5-6b-2t3.nib
│   │   ├── Info.plist
│   │   └── UIViewController-01J-lp-oVM.nib
│   └── Main.storyboardc
│       ├── BYZ-38-t0r-view-8bC-Xf-vdC.nib
│       ├── Info.plist
│       └── UIViewController-BYZ-38-t0r.nib
├── Debug.xcconfig
├── App.framework
│   ├── App
│   ├── Info.plist
│   ├── _CodeSignature
│   │   └── CodeResources
│   └── flutter_assets
│       ├── AssetManifest.json
│       ├── FontManifest.json
│       ├── LICENSE
│       ├── fonts
│       ├── isolate_snapshot_data
│       ├── kernel_blob.bin
│       ├── packages
│       └── vm_snapshot_data
├── Flutter.framework
│   ├── Flutter
│   ├── Info.plist
│   ├── _CodeSignature
│   │   └── CodeResources
│   └── icudtl.dat
├── libswiftCore.dylib
├── libswiftCoreFoundation.dylib
├── libswiftCoreGraphics.dylib
├── libswiftDarwin.dylib
├── libswiftDispatch.dylib
├── libswiftFoundation.dylib
└── libswiftObjectiveC.dylib
├── Info.plist
├── PkgInfo
├── Runner
├── _CodeSignature
│   └── CodeResources
└── embedded.mobileprovision
複製程式碼

FlutterEngine庫是怎麼載入的

配置xcode開發環境

Code開啟編譯選項Write Link Map File XCode -> Project -> Build Settings -> 搜map -> 把Write Link Map File選項設為YES,並指定好linkMap的儲存位置 特別提醒:打包釋出前記得還原為NO flutteriosconfig1.png

Flutter 在IOS上的啟動流程-14

配置這個選項可以讓xcode在打包的時候說是framework目錄下的Flutter庫,

注意:沒有在在Linked的設定裡面設定的動態庫,通過dlopen的形式來開啟。如果動態庫在Link Framwokrs and Libraries中設定了會在應用啟動的時候就會被載入。

把動態庫看成一個獨立的沒有main函式入口的可執行檔案,在iOS打包中直接copy到應用程式.app目錄下的Frameworks目錄。既然是可執行檔案那麼內部編譯連線過程已經完成了,要處理的連線也只有在載入的時候由作業系統的dyld自動load + link。

flutteriosconfigure2.png

Flutter 在IOS上的啟動流程-14

編譯後,到編譯目錄裡找到該txt檔案,檔名和路徑就是上述的Path to Link Map File位於

這個LinkMap裡展示了整個可執行檔案的全貌,列出了編譯後的每一個.o目標檔案的資訊(包括靜態連結庫.a裡的),以及每一個目標檔案的程式碼段,資料段儲存詳情。

LinkMap結構

1.首先列出來的是目標檔案列表(中括號內為檔案編號):

  ➜  Runner.build cat Runner-LinkMap-normal-arm64.txt
  # Path: /Users/cuco/Desktop/flutter_app/build/ios/Debug-iphoneos/Runner.app/Runner
  # Arch: arm64
  # Object files:
  [  0] linker synthesized
  [  1] /Users/cuco/Desktop/flutter_app/build/ios/Runner.build/Debug-iphoneos/Runner.build/Objects-normal/arm64/GeneratedPluginRegistrant.o
  [  2] /Users/cuco/Desktop/flutter_app/build/ios/Runner.build/Debug-iphoneos/Runner.build/Objects-normal/arm64/AppDelegate.o
  [  3] /Users/cuco/Desktop/flutter_app/build/ios/Runner.build/Debug-iphoneos/Runner.build/Objects-normal/arm64/Runner_vers.o
  [  4] /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/lib/arc/libarclite_iphoneos.a(arclite.o)
  [  5] /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS13.0.sdk/System/Library/Frameworks//Foundation.framework/Foundation.tbd
  [  6] /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS13.0.sdk/usr/lib/libobjc.tbd
  [  7] /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS13.0.sdk/usr/lib/libSystem.tbd
  [  8] /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS13.0.sdk/System/Library/Frameworks//CoreFoundation.framework/CoreFoundation.tbd
  [  9] /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/lib/swift/iphoneos/libswiftCompatibilityDynamicReplacements.a(DynamicReplaceable.cpp.o)
  [ 10] /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/lib/swift/iphoneos/libswiftCompatibility50.a(Overrides.cpp.o)
  [ 11] /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS13.0.sdk/System/Library/Frameworks//UIKit.framework/UIKit.tbd
  [ 12] /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS13.0.sdk/usr/lib/swift/libswiftObjectiveC.tbd
  [ 13] /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS13.0.sdk/usr/lib/swift/libswiftFoundation.tbd
  [ 14] /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS13.0.sdk/usr/lib/swift/libswiftCore.tbd
複製程式碼

2.接著是一個段表,描述各個段在最後編譯成的可執行檔案中的偏移位置及大小,包括了程式碼段(__TEXT,儲存程式程式碼段編譯後的機器碼)和資料段(__DATA,儲存變數值)

首列是資料在檔案的偏移位置,第二列是這一段佔用大小,第三列是段型別,程式碼段和資料段,第四列是段名稱。 每一行的資料都緊跟在上一行後面,如第二行__stubs的地址0x10304FD9C就是第一行__text的地址0x100005B00加上大小0x0304A29C,整個可執行檔案大致資料分佈就是這樣。 這裡可以清楚看到各種型別的資料在最終可執行檔案裡佔的比例,例如__text表示編譯後的程式執行語句,__data表示已初始化的全域性變數和區域性靜態變數,__bss表示未初始化的全域性變數和區域性靜態變數,__cstring表示程式碼裡的字串常量,等等。

  # Sections:
  # Address	Size    	Segment	Section
  0x10000473C	0x000028F0	__TEXT	__text
  0x10000702C	0x0000036C	__TEXT	__stubs
  0x100007398	0x00000384	__TEXT	__stub_helper
  0x10000771C	0x00000043	__TEXT	__objc_classname
  0x10000775F	0x000001C8	__TEXT	__objc_methname
  0x100007927	0x00000028	__TEXT	__objc_methtype
  0x100007950	0x00000210	__TEXT	__cstring
  0x100007B60	0x0000026F	__TEXT	__const
  0x100007DD0	0x0000008E	__TEXT	__swift5_typeref
  0x100007E60	0x0000002C	__TEXT	__swift5_fieldmd
  0x100007E8C	0x00000014	__TEXT	__swift5_builtin
  0x100007EA0	0x00000023	__TEXT	__swift5_reflstr
  0x100007EC4	0x00000030	__TEXT	__swift5_assocty
  0x100007EF4	0x00000018	__TEXT	__swift5_proto
  0x100007F0C	0x00000008	__TEXT	__swift5_types
  0x100007F14	0x000000E4	__TEXT	__unwind_info
  0x100008000	0x000000A0	__DATA	__got
  0x1000080A0	0x00000248	__DATA	__la_symbol_ptr
  0x1000082E8	0x000000E0	__DATA	__const
  0x1000083C8	0x00000010	__DATA	__objc_classlist
  0x1000083D8	0x00000008	__DATA	__objc_nlclslist
  0x1000083E0	0x00000008	__DATA	__objc_protolist
  0x1000083E8	0x00000008	__DATA	__objc_imageinfo
  0x1000083F0	0x00000270	__DATA	__objc_const
  0x100008660	0x000000B8	__DATA	__objc_selrefs
  0x100008718	0x00000008	__DATA	__objc_protorefs
  0x100008720	0x00000008	__DATA	__objc_classrefs
  0x100008728	0x00000100	__DATA	__objc_data
  0x100008828	0x00000115	__DATA	__data
  0x100008940	0x000000B8	__DATA	__swift_hooks
  0x100008A00	0x000004A0	__DATA	__bss
複製程式碼

3.接著就是按上表順序,列出具體的按每個檔案列出每個對應欄位的位置和佔用空間

同樣首列是資料在檔案的偏移地址,第二列是佔用大小,第三列是所屬檔案序號,對應上述Object files列表,最後是名字。

  # Symbols:
  # Address	Size    	File  Name
  0x10000473C	0x00000040	[  1] +[GeneratedPluginRegistrant registerWithRegistry:]
  0x10000477C	0x00000204	[  2] _$s6Runner11AppDelegateC11application_29didFinishLaunchingWithOptionsSbSo13UIApplicationC_SDySo0j6LaunchI3KeyaypGSgtF
  0x100004980	0x00000064	[  2] _$s6Runner11AppDelegateCMa
  0x1000049E4	0x00000094	[  2] _$sSo29UIApplicationLaunchOptionsKeyaMa
  0x100004A78	0x00000070	[  2] _$sSo29UIApplicationLaunchOptionsKeyaABSHSCWl
  0x100004AE8	0x0000012C	[  2] _$s6Runner11AppDelegateC11application_29didFinishLaunchingWithOptionsSbSo13UIApplicationC_SDySo0j6LaunchI3KeyaypGSgtFTo
  0x100004C14	0x00000030	[  2] _$s6Runner11AppDelegateCACycfC
  0x100004C44	0x00000098	[  2] _$s6Runner11AppDelegateCACycfc
  0x100004CDC	0x0000002C	[  2] _$s6Runner11AppDelegateCACycfcTo
  0x100004D08	0x00000070	[  2] _$s6Runner11AppDelegateCfD
  0x100004D78	0x00000070	[  2] _main
  0x100004DE8	0x00000058	[  2] _$sSo29UIApplicationLaunchOptionsKeya8rawValueSSvg
  0x100004E40	0x00000070	[  2] _$sSo29UIApplicationLaunchOptionsKeya8rawValueABSS_tcfC
複製程式碼

.......................

4.已廢棄&多餘重複的欄位

  # Dead Stripped Symbols:
  #        	Size    	File  Name
  <<dead>> 	0x00000016	[  2] literal string: registerWithRegistry:
  <<dead>> 	0x00000005	[  4] literal string: init
  <<dead>> 	0x00000058	[  9] _swift_getFunctionReplacement50
  <<dead>> 	0x00000048	[  9] _swift_getOrigOfReplaceable50
  <<dead>> 	0x00000007	[  0] literal string: __TEXT
  <<dead>> 	0x00000000	[  7] _pthread_getspecific
  <<dead>> 	0x00000000	[  7] _pthread_setspecific
  <<dead>> 	0x00000000	[ 14] _swift_getFunctionReplacement
  <<dead>> 	0x00000000	[ 14] _swift_getOrigOfReplacea
複製程式碼

通過上面兩步的配置,我們可以看到在生成的Runner.app中可以生產的可執行檔案的中間檔案,這裡具體分析掙mach-O的結構,只是找到整個Flutter的載入入口,和前面的思想是一樣的,先把各個入口和關鍵點搞定,避免停了在概念階段,先搞請求整個Flutter框架在Ios上是怎麼執行起來的,我們在聚焦在某個點上專題分析

Flutter 庫載入過程(mach-o檔案分析)

在上面的配置中,已經配置了app在啟動時,自動載入連結Flutter庫,如何進入IOSApp正常啟動流程,啟動流程的分析網上太多了,不做具體的分析,在App啟動是會載入,在App啟動完成之後,會呼叫AppDelegate中的didFinishLaunchingWithOptions引數的方法,開始初始化Flutter相關的邏輯,其他App初始化的邏輯沒有改變。

初始化FlutterAppDelegate

在App啟動完成只用,就開始初始化開發者的程式碼,入口是在AppDelegate類,AppDelegate繼承了FlutterAppDelegate緊接著我們開始初始化,FlutterEngine和IOS平臺相關的原始碼放在flutter/shell/platform/darwin/ios目錄下

import UIKit
import Flutter

@UIApplicationMain
@objc class AppDelegate: FlutterAppDelegate {
  override func application(
    _ application: UIApplication,
    didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
  ) -> Bool {
    GeneratedPluginRegistrant.register(with: self)
    return super.application(application, didFinishLaunchingWithOptions: launchOptions)
  }
}
複製程式碼

FlutterAppDelegate的實現了在Engine原始碼目錄下/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterAppDelegate.mm,FlutterAppDelegate繼承:

FlutterUI是直接顯示在一個UIView上面(目前所有的FlutterUI相關的邏輯全部顯示的是在這個UIView上面),IOS在FlutterUI顯示的時候很顯然,需要把相關的事件分發到FlutterUI成進行處理,FlutterUI和App直接的通訊自然是通過FlutterPlugin外掛來進行通訊,FlutterPlugin外掛包括系統外掛和使用者自定義的Channel,同時FlutterEngine的所有操作還是需要和App儲存同步,如果App退到後臺,FlutterEngine要需要處理自己的生命週期。

FLUTTER_EXPORT
@interface FlutterAppDelegate
    : UIResponder <UIApplicationDelegate, FlutterPluginRegistry, FlutterAppLifeCycleProvider>
複製程式碼

FlutterAppDelegate的實現類/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterAppDelegate.mm,在FlutterAppDelegate例項化時會呼叫init方法,先呼叫父類方法,如果初始化成功,則初始化FlutterPluginAppLifeCycleDelegate在FlutterEngine和app一個生命週期、和註冊的外掛管理工作。在FlutterPluginAppLifeCycleDelegate的初始化方法中init中初始化Flutter檔案相關的查詢目錄

- (instancetype)init {
  if (self = [super init]) {
    _lifeCycleDelegate = [[FlutterPluginAppLifeCycleDelegate alloc] init];
  }
  return self;
}
複製程式碼

FlutterPluginAppLifeCycleDelegate

/Users/cuco/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterPluginAppLifeCycleDelegate.mm初始化是呼叫init例項方法進行初始化,初始化的過程中主要是獲取快取目錄,在後續載入flutter的映象檔案和資料檔案已經配置資料夾設定好路徑。

///快取目錄
static const char* kCallbackCacheSubDir = "Library/Caches/";
- (instancetype)init {
  if (self = [super init]) {
    std::string cachePath = fml::paths::JoinPaths({getenv("HOME"), kCallbackCacheSubDir});
    [FlutterCallbackCache setCachePath:[NSString stringWithUTF8String:cachePath.c_str()]];
    _pluginDelegates = [[NSPointerArray weakObjectsPointerArray] retain];
  }
  return self;
}
複製程式碼

FlutterCallbackCache

DartCallbackCache 檔案路徑相關的類:flutter/lib/ui/plugins/callback_cache.h 下面的程式碼就是一個檔案載入目錄初始化的操作,基礎知識

+ (void)setCachePath:(NSString*)path {
  assert(path != nil);
  blink::DartCallbackCache::SetCachePath([path UTF8String]);
  NSString* cache_path =
      [NSString stringWithUTF8String:blink::DartCallbackCache::GetCachePath().c_str()];
  // Set the "Do Not Backup" flag to ensure that the cache isn't moved off disk in
  // low-memory situations.
  if (![[NSFileManager defaultManager] fileExistsAtPath:cache_path]) {
    [[NSFileManager defaultManager] createFileAtPath:cache_path contents:nil attributes:nil];
    NSError* error = nil;
    NSURL* URL = [NSURL fileURLWithPath:cache_path];
    BOOL success = [URL setResourceValue:[NSNumber numberWithBool:YES]
                                  forKey:NSURLIsExcludedFromBackupKey
                                   error:&error];
    if (!success) {
      NSLog(@"Error excluding %@ from backup %@", [URL lastPathComponent], error);
    }
  }
}
@end

複製程式碼

FlutterPluginRegistry外掛初始化過程

FlutterPluginRegistry 的實現程式碼是在FlutterAppDelegate.mm中實現的,具體的參考實現過程請參考一下程式碼

- (NSObject<FlutterPluginRegistrar>*)registrarForPlugin:(NSString*)pluginKey {
  UIViewController* rootViewController = _window.rootViewController;
  if ([rootViewController isKindOfClass:[FlutterViewController class]]) {
    return
        [[(FlutterViewController*)rootViewController pluginRegistry] registrarForPlugin:pluginKey];
  }
  return nil;
}

- (BOOL)hasPlugin:(NSString*)pluginKey {
  UIViewController* rootViewController = _window.rootViewController;
  if ([rootViewController isKindOfClass:[FlutterViewController class]]) {
    return [[(FlutterViewController*)rootViewController pluginRegistry] hasPlugin:pluginKey];
  }
  return false;
}

- (NSObject*)valuePublishedByPlugin:(NSString*)pluginKey {
  UIViewController* rootViewController = _window.rootViewController;
  if ([rootViewController isKindOfClass:[FlutterViewController class]]) {
    return [[(FlutterViewController*)rootViewController pluginRegistry]
        valuePublishedByPlugin:pluginKey];
  }
  return nil;
}
複製程式碼

在上面的載入之後,就開始進入FlutterEngine的核心程式碼進行初始化執行,FlutterAppDelegate初始化過程主要做了:

1.載入相關的外掛資訊

2.初始化快取目錄

3.繫結FlutterEngine引擎和App的生命週期

4.在App的不同生命週期中呼叫FlutterEngine進行通訊

主要是在做全域性資訊的初始化操作

FlutterViewController 初始化過程

FlutterEngine初始化過程主要是在FlutterViewController中進行UI事件的繫結工作,在相關的生命週期中初始化邏輯,我們先分析關鍵點,後續的文章在對每一個點進行細緻的分享,所有的操作都是在FlutterViewController的上面週期方法進行呼叫的

flutter/shell/platform/darwin/ios/framework/Source/FlutterEngine.mm主要實現了App層和FlutterEngine的一個入口控制邏輯

flutter/shell/platform/darwin/ios/framework/Source/FlutterView.mm繼承UIView提供個FlutterEngine引擎進行繪製操作

NotificationCenterFlutterEngine和App進行互動的通知通道

1.- init:                 初始化FlutterEngine、FlutterView、setupNotificationCenterObservers註冊
2. awakeFromNib:
3. loadView:              FlutterView賦值給ViewController的view物件,初始化啟動屏
4. viewDidLoad:
5. viewWillAppear:        launchEngine、註冊生命週期
6. updateViewConstraints:
7. viewWillLayoutSubviews:
8. viewDidLayoutSubviews: 設定UI螢幕的大小、UIScreen mainScreen
9. viewDidAppear:         更新Local資訊、更新使用者設定、更新訪問狀態
10. viewWillDisappear:
11. viewDidDisappear:     更新相關的FlutterUI
複製程式碼

在iosAPP啟動的時候,並沒有做太多的操作,主要是初始化載入資料的路徑,監聽IOSApp生命週期,並且註冊一些事件回撥邏輯,接下來時App已經初始化完成,接著初始化FlutterViewController,在FlutterViewController初始化時,了init方法 flutter/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm,在FlutterViewController初始化是呼叫初始化函式init

- (instancetype)init {
  return [self initWithProject:nil nibName:nil bundle:nil];
}

複製程式碼

FlutterViewController 初始化入口

1.開始初始化整個FlutterEngine框架initWithProject,儲存FlutterViewController到一個弱引用中,開始初始化FlutterEngine,呼叫initWithName方法進行初始化

2.初始化FlutterView主要是IOSUI互動的介面,主要作用是FlutterEngine和IOSUI的一個管理邏輯類,提供給FlutterEngine進行繪製處理

3.啟動SplashScreenView

  1. createShell:FlutterEngine和不同平臺直接的統一介面

6.performCommonViewControllerInitialization

7.setupNotificationCenterObservers

- (instancetype)initWithProject:(FlutterDartProject*)projectOrNil
                        nibName:(NSString*)nibNameOrNil
                         bundle:(NSBundle*)nibBundleOrNil {
  self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
  if (self) {
    _viewOpaque = YES;
    _weakFactory = std::make_unique<fml::WeakPtrFactory<FlutterViewController>>(self);
    _engine.reset([[FlutterEngine alloc] initWithName:@"io.flutter"
                                              project:projectOrNil
                               allowHeadlessExecution:NO]);
    _flutterView.reset([[FlutterView alloc] initWithDelegate:_engine opaque:self.isViewOpaque]);
    [_engine.get() createShell:nil libraryURI:nil];
    _engineNeedsLaunch = YES;
    [self loadDefaultSplashScreenView];
    [self performCommonViewControllerInitialization];
  }

  return self;
}
複製程式碼

FlutterEngine

在FlutterEngine初始化是,主要是查詢到相關的資源錄用,四個執行緒、訊息佇列進行初始化操作,主要是FlutterEngine內部進行初始化,並沒有正在載入太作的業務程式碼邏輯

1.FlutterDartProject:主要初始化整個FlutterEngine初始化過程中Flutter_asset相關的檔案路徑解析,同時解析命令列引數,構建預設的程式引數

2.FlutterPlatformViewsController:主要的功能是處理FlutterEngine側的View相關的邏輯flutter/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews.mm

3.FlutterView

3.setupChannels


- (instancetype)initWithName:(NSString*)labelPrefix
                     project:(FlutterDartProject*)projectOrNil
      allowHeadlessExecution:(BOOL)allowHeadlessExecution {
  self = [super init];
  NSAssert(self, @"Super init cannot be nil");
  NSAssert(labelPrefix, @"labelPrefix is required");

  _allowHeadlessExecution = allowHeadlessExecution;
  _labelPrefix = [labelPrefix copy];

  _weakFactory = std::make_unique<fml::WeakPtrFactory<FlutterEngine>>(self);

  if (projectOrNil == nil)
    _dartProject.reset([[FlutterDartProject alloc] init]);
  else
    _dartProject.reset([projectOrNil retain]);

  _pluginPublications = [NSMutableDictionary new];
  _platformViewsController.reset(new shell::FlutterPlatformViewsController());

  [self setupChannels];

  return self;
}
複製程式碼

FlutterDartProject

- (instancetype)initWithPrecompiledDartBundle:(NSBundle*)bundle {
  self = [super init];

  if (self) {
    _precompiledDartBundle.reset([bundle retain]);
    _settings = DefaultSettingsForProcess(bundle);
  }

  return self;
}


查詢的路徑Frameworks/App.framework/flutter_assets

+ (NSString*)flutterAssetsName:(NSBundle*)bundle {
  NSString* flutterAssetsName = [bundle objectForInfoDictionaryKey:@"FLTAssetsPath"];
  if (flutterAssetsName == nil) {
    flutterAssetsName = @"Frameworks/App.framework/flutter_assets";
  }
  return flutterAssetsName;
}

複製程式碼

DefaultSettingsForProcess

設定Flutter_asset載入路徑,設定FlutterEngine在這些過程中,如何查詢可執行的檔案路徑,在上面的App的包結構中,我們可以看到,Flutter相關的資原始檔,那麼在FlutterEngine啟動的時候,我們就可以載入相關的Flutter程式碼和相關資原始檔路徑

static blink::Settings DefaultSettingsForProcess(NSBundle* bundle = nil) {
  auto command_line = shell::CommandLineFromNSProcessInfo();

  // Precedence:
  // 1. Settings from the specified NSBundle.
  // 2. Settings passed explicitly via command-line arguments.
  // 3. Settings from the NSBundle with the default bundle ID.
  // 4. Settings from the main NSBundle and default values.

  NSBundle* mainBundle = [NSBundle mainBundle];
  NSBundle* engineBundle = [NSBundle bundleForClass:[FlutterViewController class]];

  bool hasExplicitBundle = bundle != nil;
  if (bundle == nil) {
    bundle = [NSBundle bundleWithIdentifier:[FlutterDartProject defaultBundleIdentifier]];
  }
  if (bundle == nil) {
    bundle = mainBundle;
  }

  auto settings = shell::SettingsFromCommandLine(command_line);

  settings.task_observer_add = [](intptr_t key, fml::closure callback) {
    fml::MessageLoop::GetCurrent().AddTaskObserver(key, std::move(callback));
  };

  settings.task_observer_remove = [](intptr_t key) {
    fml::MessageLoop::GetCurrent().RemoveTaskObserver(key);
  };

  // The command line arguments may not always be complete. If they aren't, attempt to fill in
  // defaults.

  // Flutter ships the ICU data file in the the bundle of the engine. Look for it there.
  if (settings.icu_data_path.size() == 0) {
    NSString* icuDataPath = [engineBundle pathForResource:@"icudtl" ofType:@"dat"];
    if (icuDataPath.length > 0) {
      settings.icu_data_path = icuDataPath.UTF8String;
    }
  }

  if (blink::DartVM::IsRunningPrecompiledCode()) {
    if (hasExplicitBundle) {
      NSString* executablePath = bundle.executablePath;
      if ([[NSFileManager defaultManager] fileExistsAtPath:executablePath]) {
        settings.application_library_path = executablePath.UTF8String;
      }
    }

    // No application bundle specified.  Try a known location from the main bundle's Info.plist.
    if (settings.application_library_path.size() == 0) {
      NSString* libraryName = [mainBundle objectForInfoDictionaryKey:@"FLTLibraryPath"];
      NSString* libraryPath = [mainBundle pathForResource:libraryName ofType:@""];
      if (libraryPath.length > 0) {
        NSString* executablePath = [NSBundle bundleWithPath:libraryPath].executablePath;
        if (executablePath.length > 0) {
          settings.application_library_path = executablePath.UTF8String;
        }
      }
    }

    // In case the application bundle is still not specified, look for the App.framework in the
    // Frameworks directory.
    if (settings.application_library_path.size() == 0) {
      NSString* applicationFrameworkPath = [mainBundle pathForResource:@"Frameworks/App.framework"
                                                                ofType:@""];
      if (applicationFrameworkPath.length > 0) {
        NSString* executablePath =
            [NSBundle bundleWithPath:applicationFrameworkPath].executablePath;
        if (executablePath.length > 0) {
          settings.application_library_path = executablePath.UTF8String;
        }
      }
    }
  }

  // Checks to see if the flutter assets directory is already present.
  if (settings.assets_path.size() == 0) {
    NSString* assetsName = [FlutterDartProject flutterAssetsName:bundle];
    NSString* assetsPath = [bundle pathForResource:assetsName ofType:@""];

    if (assetsPath.length == 0) {
      assetsPath = [mainBundle pathForResource:assetsName ofType:@""];
    }

    if (assetsPath.length == 0) {
      NSLog(@"Failed to find assets path for \"%@\"", assetsName);
    } else {
      settings.assets_path = assetsPath.UTF8String;

      // Check if there is an application kernel snapshot in the assets directory we could
      // potentially use.  Looking for the snapshot makes sense only if we have a VM that can use
      // it.
      if (!blink::DartVM::IsRunningPrecompiledCode()) {
        NSURL* applicationKernelSnapshotURL =
            [NSURL URLWithString:@(kApplicationKernelSnapshotFileName)
                   relativeToURL:[NSURL fileURLWithPath:assetsPath]];
        if ([[NSFileManager defaultManager] fileExistsAtPath:applicationKernelSnapshotURL.path]) {
          settings.application_kernel_asset = applicationKernelSnapshotURL.path.UTF8String;
        } else {
          NSLog(@"Failed to find snapshot: %@", applicationKernelSnapshotURL.path);
        }
      }
    }
  }

#if FLUTTER_RUNTIME_MODE == FLUTTER_RUNTIME_MODE_DEBUG
  // There are no ownership concerns here as all mappings are owned by the
  // embedder and not the engine.
  auto make_mapping_callback = [](const uint8_t* mapping, size_t size) {
    return [mapping, size]() { return std::make_unique<fml::NonOwnedMapping>(mapping, size); };
  };

  settings.dart_library_sources_kernel =
      make_mapping_callback(kPlatformStrongDill, kPlatformStrongDillSize);
#endif  // FLUTTER_RUNTIME_MODE == FLUTTER_RUNTIME_MODE_DEBUG

  return settings;
}

複製程式碼

setupNotificationCenterObservers

- (void)setupNotificationCenterObservers {
  NSNotificationCenter* center = [NSNotificationCenter defaultCenter];
  [center addObserver:self
             selector:@selector(onOrientationPreferencesUpdated:)
                 name:@(shell::kOrientationUpdateNotificationName)
               object:nil];

  [center addObserver:self
             selector:@selector(onPreferredStatusBarStyleUpdated:)
                 name:@(shell::kOverlayStyleUpdateNotificationName)
               object:nil];

  [center addObserver:self
             selector:@selector(applicationBecameActive:)
                 name:UIApplicationDidBecomeActiveNotification
               object:nil];

  [center addObserver:self
             selector:@selector(applicationWillResignActive:)
                 name:UIApplicationWillResignActiveNotification
               object:nil];

  [center addObserver:self
             selector:@selector(applicationDidEnterBackground:)
                 name:UIApplicationDidEnterBackgroundNotification
               object:nil];

  [center addObserver:self
             selector:@selector(applicationWillEnterForeground:)
                 name:UIApplicationWillEnterForegroundNotification
               object:nil];

  [center addObserver:self
             selector:@selector(keyboardWillChangeFrame:)
                 name:UIKeyboardWillChangeFrameNotification
               object:nil];

  [center addObserver:self
             selector:@selector(keyboardWillBeHidden:)
                 name:UIKeyboardWillHideNotification
               object:nil];

  [center addObserver:self
             selector:@selector(onLocaleUpdated:)
                 name:NSCurrentLocaleDidChangeNotification
               object:nil];

  [center addObserver:self
             selector:@selector(onAccessibilityStatusChanged:)
                 name:UIAccessibilityVoiceOverStatusChanged
               object:nil];

  [center addObserver:self
             selector:@selector(onAccessibilityStatusChanged:)
                 name:UIAccessibilitySwitchControlStatusDidChangeNotification
               object:nil];

  [center addObserver:self
             selector:@selector(onAccessibilityStatusChanged:)
                 name:UIAccessibilitySpeakScreenStatusDidChangeNotification
               object:nil];

  [center addObserver:self
             selector:@selector(onAccessibilityStatusChanged:)
                 name:UIAccessibilityInvertColorsStatusDidChangeNotification
               object:nil];

  [center addObserver:self
             selector:@selector(onAccessibilityStatusChanged:)
                 name:UIAccessibilityReduceMotionStatusDidChangeNotification
               object:nil];

  [center addObserver:self
             selector:@selector(onAccessibilityStatusChanged:)
                 name:UIAccessibilityBoldTextStatusDidChangeNotification
               object:nil];

  [center addObserver:self
             selector:@selector(onMemoryWarning:)
                 name:UIApplicationDidReceiveMemoryWarningNotification
               object:nil];

  [center addObserver:self
             selector:@selector(onUserSettingsChanged:)
                 name:UIContentSizeCategoryDidChangeNotification
               object:nil];
}

複製程式碼

setupChannels

engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterEngine.mm中註冊FlutterUI層和IOS層之間的Plugin,這些Plugin是系統級別的

- (void)setupChannels {
  _localizationChannel.reset([[FlutterMethodChannel alloc]
         initWithName:@"flutter/localization"
      binaryMessenger:self
                codec:[FlutterJSONMethodCodec sharedInstance]]);

  _navigationChannel.reset([[FlutterMethodChannel alloc]
         initWithName:@"flutter/navigation"
      binaryMessenger:self
                codec:[FlutterJSONMethodCodec sharedInstance]]);

  _platformChannel.reset([[FlutterMethodChannel alloc]
         initWithName:@"flutter/platform"
      binaryMessenger:self
                codec:[FlutterJSONMethodCodec sharedInstance]]);

  _platformViewsChannel.reset([[FlutterMethodChannel alloc]
         initWithName:@"flutter/platform_views"
      binaryMessenger:self
                codec:[FlutterStandardMethodCodec sharedInstance]]);

  _textInputChannel.reset([[FlutterMethodChannel alloc]
         initWithName:@"flutter/textinput"
      binaryMessenger:self
                codec:[FlutterJSONMethodCodec sharedInstance]]);

  _lifecycleChannel.reset([[FlutterBasicMessageChannel alloc]
         initWithName:@"flutter/lifecycle"
      binaryMessenger:self
                codec:[FlutterStringCodec sharedInstance]]);

  _systemChannel.reset([[FlutterBasicMessageChannel alloc]
         initWithName:@"flutter/system"
      binaryMessenger:self
                codec:[FlutterJSONMessageCodec sharedInstance]]);

  _settingsChannel.reset([[FlutterBasicMessageChannel alloc]
         initWithName:@"flutter/settings"
      binaryMessenger:self
                codec:[FlutterJSONMessageCodec sharedInstance]]);

  _textInputPlugin.reset([[FlutterTextInputPlugin alloc] init]);
  _textInputPlugin.get().textInputDelegate = self;

  _platformPlugin.reset([[FlutterPlatformPlugin alloc] initWithEngine:[self getWeakPtr]]);
}
複製程式碼

FlutterViewcontroller和FlutterEngine.mm例項進行繫結

- (void)setViewController:(FlutterViewController*)viewController {
  FML_DCHECK(self.iosPlatformView);
  _viewController = [viewController getWeakPtr];
  self.iosPlatformView->SetOwnerViewController(_viewController);
  [self maybeSetupPlatformViewChannels];
}
複製程式碼

FlutterView

接著回到flutter/shell/platform/darwin/ios/framework/Source/FlutterViewController.mminitWithProject繼續往下分析,初始化flutter/shell/platform/darwin/ios/framework/Source/FlutterView.mm可說初始化Frame

- (instancetype)initWithDelegate:(id<FlutterViewEngineDelegate>)delegate opaque:(BOOL)opaque {
  FML_DCHECK(delegate) << "Delegate must not be nil.";
  self = [super initWithFrame:CGRectNull];

  if (self) {
    _delegate = delegate;
    self.layer.opaque = opaque;
  }

  return self;
}
複製程式碼

FlutterEngine: createShell

這個方法在Android啟動流程中有詳細介紹過,這裡不做更加詳細的說明

1.初始化FlutterDart層的main()函式作為入口點

2.建立柵格化類Rasterizer

3.啟動訊息佇列MessageLoop

4.建立platform、gpu、ui、io

- (BOOL)createShell:(NSString*)entrypoint libraryURI:(NSString*)libraryURI {
  if (_shell != nullptr) {
    FML_LOG(WARNING) << "This FlutterEngine was already invoked.";
    return NO;
  }

  static size_t shellCount = 1;

  auto settings = [_dartProject.get() settings];

  if (libraryURI) {
    FML_DCHECK(entrypoint) << "Must specify entrypoint if specifying library";
    settings.advisory_script_entrypoint = entrypoint.UTF8String;
    settings.advisory_script_uri = libraryURI.UTF8String;
  } else if (entrypoint) {
    settings.advisory_script_entrypoint = entrypoint.UTF8String;
    settings.advisory_script_uri = std::string("main.dart");
  } else {
    settings.advisory_script_entrypoint = std::string("main");
    settings.advisory_script_uri = std::string("main.dart");
  }

  const auto threadLabel = [NSString stringWithFormat:@"%@.%zu", _labelPrefix, shellCount++];
  FML_DLOG(INFO) << "Creating threadHost for " << threadLabel.UTF8String;
  // The current thread will be used as the platform thread. Ensure that the message loop is
  // initialized.
  fml::MessageLoop::EnsureInitializedForCurrentThread();

  _threadHost = {
      threadLabel.UTF8String,  // label
      shell::ThreadHost::Type::UI | shell::ThreadHost::Type::GPU | shell::ThreadHost::Type::IO};

  // Lambda captures by pointers to ObjC objects are fine here because the
  // create call is
  // synchronous.
  shell::Shell::CreateCallback<shell::PlatformView> on_create_platform_view =
      [](shell::Shell& shell) {
        return std::make_unique<shell::PlatformViewIOS>(shell, shell.GetTaskRunners());
      };

  shell::Shell::CreateCallback<shell::Rasterizer> on_create_rasterizer = [](shell::Shell& shell) {
    return std::make_unique<shell::Rasterizer>(shell.GetTaskRunners());
  };

  if (shell::IsIosEmbeddedViewsPreviewEnabled()) {
    // Embedded views requires the gpu and the platform views to be the same.
    // The plan is to eventually dynamically merge the threads when there's a
    // platform view in the layer tree.
    // For now we run in a single threaded configuration.
    // TODO(amirh/chinmaygarde): merge only the gpu and platform threads.
    // https://github.com/flutter/flutter/issues/23974
    // TODO(amirh/chinmaygarde): remove this, and dynamically change the thread configuration.
    // https://github.com/flutter/flutter/issues/23975
    blink::TaskRunners task_runners(threadLabel.UTF8String,                          // label
                                    fml::MessageLoop::GetCurrent().GetTaskRunner(),  // platform
                                    fml::MessageLoop::GetCurrent().GetTaskRunner(),  // gpu
                                    fml::MessageLoop::GetCurrent().GetTaskRunner(),  // ui
                                    fml::MessageLoop::GetCurrent().GetTaskRunner()   // io
    );
    // Create the shell. This is a blocking operation.
    _shell = shell::Shell::Create(std::move(task_runners),  // task runners
                                  std::move(settings),      // settings
                                  on_create_platform_view,  // platform view creation
                                  on_create_rasterizer      // rasterzier creation
    );
  } else {
    blink::TaskRunners task_runners(threadLabel.UTF8String,                          // label
                                    fml::MessageLoop::GetCurrent().GetTaskRunner(),  // platform
                                    _threadHost.gpu_thread->GetTaskRunner(),         // gpu
                                    _threadHost.ui_thread->GetTaskRunner(),          // ui
                                    _threadHost.io_thread->GetTaskRunner()           // io
    );
    // Create the shell. This is a blocking operation.
    _shell = shell::Shell::Create(std::move(task_runners),  // task runners
                                  std::move(settings),      // settings
                                  on_create_platform_view,  // platform view creation
                                  on_create_rasterizer      // rasterzier creation
    );
  }

  if (_shell == nullptr) {
    FML_LOG(ERROR) << "Could not start a shell FlutterEngine with entrypoint: "
                   << entrypoint.UTF8String;
  } else {
    [self setupChannels];
    if (!_platformViewsController) {
      _platformViewsController.reset(new shell::FlutterPlatformViewsController());
    }
    _publisher.reset([[FlutterObservatoryPublisher alloc] init]);
    [self maybeSetupPlatformViewChannels];
  }

  return _shell != nullptr;
}
複製程式碼

在上一步的呼叫過程中,我們最關心的是:Shell的建立過程。flutter/shell/common/shell.cc這個類是所有平臺和FlutterPlatformView公用的入口點,在呼叫下列函式之後,CreateShellOnPlatformThread建立Flutter相關的執行環境

// Create the shell. This is a blocking operation.
_shell = shell::Shell::Create(std::move(task_runners),  // task runners
                              std::move(settings),      // settings
                              on_create_platform_view,  // platform view creation
                              on_create_rasterizer      // rasterzier creation
);
複製程式碼

上面的部分是在IOS端進行初始化,當呼叫shell::Shell::Create方法時,就真正進入了FlutterEngine的所有平臺統一的處理邏輯

loadView

FlutterViewController的生命週期函式中繫結了FlutterView賦值給當前的FlutterViewController進行初始化操作View進行顯示,同時初始化App啟動的SplashView進行初始化,提供了幾個方法,用於載入不同型別定義的SplashView

- (void)loadView {
  self.view = _flutterView.get();
  self.view.multipleTouchEnabled = YES;
  self.view.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;

  [self installSplashScreenViewIfNecessary];
}
複製程式碼

FlutterEngine:launchEngine

viewWillAppear生命週期函式中呼叫FlutterEngine進行初始化操作

- (void)viewWillAppear:(BOOL)animated {
  TRACE_EVENT0("flutter", "viewWillAppear");

  if (_engineNeedsLaunch) {
    [_engine.get() launchEngine:nil libraryURI:nil];
    [_engine.get() setViewController:self];
    _engineNeedsLaunch = NO;
  }

  // Only recreate surface on subsequent appearances when viewport metrics are known.
  // First time surface creation is done on viewDidLayoutSubviews.
  if (_viewportMetrics.physical_width)
    [self surfaceUpdated:YES];
  [[_engine.get() lifecycleChannel] sendMessage:@"AppLifecycleState.inactive"];

  [super viewWillAppear:animated];
}
複製程式碼

FlutterEngine:Run

1.載入配置,查詢到指定的FlutterEngine的入口點,也就是指點的FlutterUI層的啟動函式的入口

2.呼叫FltuterEngine->Run方法啟動FlutterEngine進行載入FltuterUI相關的程式碼flutter/shell/common/engine.cc

- (void)launchEngine:(NSString*)entrypoint libraryURI:(NSString*)libraryOrNil {
  // Launch the Dart application with the inferred run configuration.
  self.shell.GetTaskRunners().GetUITaskRunner()->PostTask(fml::MakeCopyable(
      [engine = _shell->GetEngine(),
       config = [_dartProject.get() runConfigurationForEntrypoint:entrypoint
                                                     libraryOrNil:libraryOrNil]  //
  ]() mutable {
        if (engine) {
          auto result = engine->Run(std::move(config));
          if (result == shell::Engine::RunStatus::Failure) {
            FML_LOG(ERROR) << "Could not launch engine with configuration.";
          }
        }
      }));
}

複製程式碼

在目前為止,IOS和Android端有差異的程式碼就是上面的部分,在呼叫engine->Run函式之後,就進入FlutteREngine的核心部分,所有的Android和IOS執行的程式碼邏輯都是一樣的了,請參考Android的啟動流程

小結

FlutterEngine在IOS上的初始化操作比在Android上的操作感覺邏輯簡單了不少,彙總中一下在App啟動過程中,是怎麼初始化FlutterEngine相關的邏輯的,上面的分析和Android相關的部分已經沒有進行分析,具體的請看一下Android端相關的分享hell::Shell::Create呼叫之後,啟動過程FlutterEngine:Run之後的程式碼都是到了FlutterEngine核心程式碼邏輯,所有的平臺基本一致

1.在AppDelegate中繼承了FlutterAppDelegateAPP啟動的時候,呼叫init方法,初始化全域性相關的邏輯和生命週期

2.FlutterViewController啟動,在init中開始初始化FlutterEngine

3.在loadView把FlutterView賦值個FlutterViewController的view進行顯示

4.viewWillAppear方法中呼叫引擎啟動,載入FlutterUI層相關的程式碼,其實就是查詢FlutterUI層相關的main()進行呼叫你

相關文章