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
配置這個選項可以讓xcode在打包的時候說是framework目錄下的Flutter庫,
注意:沒有在在Linked的設定裡面設定的動態庫,通過dlopen的形式來開啟。如果動態庫在Link Framwokrs and Libraries中設定了會在應用啟動的時候就會被載入。
把動態庫看成一個獨立的沒有main函式入口的可執行檔案,在iOS打包中直接copy到應用程式.app目錄下的Frameworks目錄。既然是可執行檔案那麼內部編譯連線過程已經完成了,要處理的連線也只有在載入的時候由作業系統的dyld自動load + link。
flutteriosconfigure2.png
編譯後,到編譯目錄裡找到該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引擎進行繪製操作
NotificationCenter
FlutterEngine和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
- 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.mm
的initWithProject
繼續往下分析,初始化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中繼承了FlutterAppDelegate
APP啟動的時候,呼叫init
方法,初始化全域性相關的邏輯和生命週期
2.FlutterViewController啟動,在init
中開始初始化FlutterEngine
3.在loadView
把FlutterView賦值個FlutterViewController的view進行顯示
4.viewWillAppear
方法中呼叫引擎啟動,載入FlutterUI層相關的程式碼,其實就是查詢FlutterUI層相關的main()
進行呼叫你