The logic used by Flutter on IOS is very similar to that on Android. The App mainly provides initialization and View to FlutterEngine for rendering. The rest of Flutter is handled by FlutterEngine for subsequent work. Before FlutterUI can be loaded?
1. IOSApp package structure
2. How is the FlutterEngine library loaded
3.FlutterEngine initialization process
4. Start FlutterEngine
Ipa package structure
Ios package Flutter related resource file is stored in the Frameworks/App. The framework/flutter_assets Bundle, Engine related file is stored in the Frameworks/Flutter. The framework preserved the Flutter in two places related code, in the App structure, we see the Flutter actually relevant code are separated in the App, there is not much associated, FlutterEngine is integrated with the App code when compiled, so that the FlutterEngine can be loaded at startup. (Can we manually load the Flutter engine libraries so that they are more independent from ios apps?
flutter build ios --release
Copy the code
Run the command above and wait for the packaging tool to generate the ios files. The structure is as follows. The pictures are deleted in the middle to focus on the key points of analysis.
➜ Runner. App tree-l 3. ├─ appframeworkInfo.plist........ ├ ─ ─ Assets. Car ├ ─ ─ Base. Lproj │ ├ ─ ─ LaunchScreen. Storyboardc │ │ ├ ─ ─ j - lp 01 - oVM - view - Ze5-6 b - 2 t3. Nib │ │ ├ ─ ─ Info. The plist │ │ ├─ ├─ ├─ ├─ ├─ im0. ├─ im0. ├─ im0. ├─ im0. ├─ im0. ├─ im0. ├─ im0. ├─ im0. ├─ im0. ├─ im0. ├─ im0. ├─ im0. ├─ im0. ├─ im0. ├─ im0. ├─ im0. ├─ im0. ├─ im0. ├─ im0. ├─ im0. ├─ im0. ├─ im0. ├─ im0. ├─ im0. ├─ im0 └ ─ ─ UIViewController BYZ - 38 - t0r. Nib ├ ─ ─ the Debug. Xcconfig ├ ─ ─ App. The framework │ ├ ─ ─ App │ ├ ─ ─ the Info. The plist │ ├ ─ ─ _CodeSignature │ │ └ ─ ─ CodeResources │ └ ─ ─ flutter_assets │ ├ ─ ─ AssetManifest. Json │ ├ ─ ─ FontManifest. Json │ ├ ─ ─ LICENSE │ ├ ─ ─ fonts │ ├ ─ ─ isolate_snapshot_data │ ├ ─ ─ kernel_blob. Bin │ ├ ─ ─ packages │ └ ─ ─ vm_snapshot_data ├ ─ ─ Flutter. The framework │ ├ ─ ─ Flutter │ ├ ─ ─ the Info. The plist │ ├ ─ ─ _CodeSignature │ │ └ ─ ─ CodeResources │ └ ─ ─ icudtl. Dat ├ ─ ─ libswiftCore. Dylib ├ ─ ─ LibswiftCoreFoundation. Dylib ├ ─ ─ libswiftCoreGraphics. Dylib ├ ─ ─ libswiftDarwin. Dylib ├ ─ ─ libswiftDispatch. Dylib ├ ─ ─ LibswiftFoundation. Dylib └ ─ ─ libswiftObjectiveC. Dylib ├ ─ ─ Info. The plist ├ ─ ─ PkgInfo ├ ─ ─ Runner ├ ─ ─ _CodeSignature │ └ ─ ─ CodeResources └ ─ ─ embedded. MobileprovisionCopy the code
How is the FlutterEngine library loaded
Configure the Xcode development environment
Write Link Map File XCode -> Project -> Build Settings -> Search Map -> Set Write Link Map File to YES, Note: Please restore to NO flutteriosconfig1.png before package release
Configuring this option allows Xcode to be packaged as a Flutter library in the Framework directory,
Note: There are no dynamic libraries set up in Linked Settings, which are opened via dlopen. Dynamic Libraries that are set in Link Framwokrs and Libraries will be loaded when the application starts.
Think of the dynamic library as a separate executable with no entry to the main function, and copy it directly to the Frameworks directory in the.app directory in the iOS package. Since it is an executable, the internal compilation of the connection process is complete, and the connection to be processed is automatically loaded + link by the operating system’s dyLD at load time.
flutteriosconfigure2.png
After compiling, go to the compilation directory to find the TXT File, the File name and Path is the above Path to Link Map File located in
This LinkMap shows the whole picture of the executable file, listing the information of each.o object file after compilation (including the static link library. A), as well as the code segment of each object file, data segment storage details.
LinkMap structure
1. List the target files first (file numbers in parentheses) :
➜ 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/GeneratedPluginR egistrant.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/Fr ameworks//Foundation.framework/Foundation.tbd [ 6] / Applications/Xcode. App/Contents/Developer/Platforms/iPhoneOS platform/Developer/SDKs/iPhoneOS13.0 SDK/usr/lib/libobjc. T bd [ 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/Fr ameworks//CoreFoundation.framework/CoreFoundation.tbd [ 9] /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/lib/swift/iphoneos/libswiftCompatibil ityDynamicReplacements.a(DynamicReplaceable.cpp.o) [ 10] /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/lib/swift/iphoneos/libswiftCompatibil ity50.a(Overrides.cpp.o) [ 11] / Applications/Xcode. App/Contents/Developer/Platforms/iPhoneOS platform/Developer/SDKs/iPhoneOS13.0 SDK/System/Library/Fr ameworks//UIKit.framework/UIKit.tbd [ 12] / Applications/Xcode. App/Contents/Developer/Platforms/iPhoneOS platform/Developer/SDKs/iPhoneOS13.0 SDK/usr/lib/swift/lib swiftObjectiveC.tbd [ 13] / Applications/Xcode. App/Contents/Developer/Platforms/iPhoneOS platform/Developer/SDKs/iPhoneOS13.0 SDK/usr/lib/swift/lib swiftFoundation.tbd [ 14] / Applications/Xcode. App/Contents/Developer/Platforms/iPhoneOS platform/Developer/SDKs/iPhoneOS13.0 SDK/usr/lib/swift/lib swiftCore.tbdCopy the code
2. This is followed by a segment table that describes the offset position and size of each segment in the final compiled executable, including the code segment (__TEXT, which holds the compiled machine code of the program code segment) and the data segment (__DATA, which holds variable values).
The first column is the offset position of the data in the file, the second column is the size of the segment, the third column is the segment type, code segment and data segment, and the fourth column is the segment name. For example, the address 0x10304FD9C of __stubs on the second line is the address 0x100005B00 plus the size 0x0304A29C of __text on the first line. Here you can clearly see the proportion of various types of data in the final executable. For example, __text represents compiled program execution statements, __data represents initialized global and local static variables, __bSS represents uninitialized global and local static variables, __cString represents string constants in the code, And so on.
# 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
Copy the code
3. Then list the location and space occupied by each corresponding field according to each file in the order of the above table
Similarly, the first column is the offset address of the data in the file, the second column is the occupied size, the third column is the number of the file, corresponding to the above Object Files list, and the last column is the name.
# 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
Copy the code
.
4. Obsolete & redundant duplicate fields
# 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_getOrigOfReplaceaCopy the code
Through the configuration of the above two steps, we can see the intermediate files of executable files that can be produced in the generated Runner. App. Here, we analyze the structure of The whole Flutter in detail and just find the loading entry of the Flutter. How does the whole Flutter framework work on Ios? Are we focusing on a particular point
Load process of Flutter library (Analysis of Mach-O files)
How to enter the normal startup process of IOSApp? There are too many analysis about the startup process on the website. It will be loaded when app starts. Will call in the AppDelegate didFinishLaunchingWithOptions parameters, the method of logical initialization began to Flutter, other App initialization logic has not changed.
Initialize FlutterAppDelegate
As soon as the App is launched, it starts to initialize the developer’s code in the AppDelegate class, AppDelegate inherited FlutterAppDelegate then we can start initialization, FlutterEngine IOS related source on flutter/shell/platform/Darwin/IOS directory
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)
}
}
Copy the code
FlutterAppDelegate realized in Engine Source directory/Engine/SRC/flutter/shell/platform/Darwin/ios/framework/Source/FlutterAppDelegate. Mm, FlutterAppDelegate inheritance:
FlutterUI is directly displayed on a UIView (currently all flutterUi-related logic is displayed on this UIView). IOS clearly needs to distribute related events to FlutterUI for processing when FlutterUI is displayed. The direct communication between FlutterUI and App is naturally through the FlutterPlugin, which includes system plug-in and user-defined Channel. Meanwhile, all operations of FlutterEngine still need to be synchronized with App. If the App falls back into the background, FlutterEngine needs to handle its own life cycle.
FLUTTER_EXPORT
@interface FlutterAppDelegate
: UIResponder <UIApplicationDelegate, FlutterPluginRegistry, FlutterAppLifeCycleProvider>
Copy the code
FlutterAppDelegate implementation class/engine/SRC/flutter/shell/platform/Darwin/ios/framework/Source/FlutterAppDelegate. Mm, Init is called when the FlutterAppDelegate is instantiated. The parent method is called first. If the initialization succeeds, The initialization FlutterPluginAppLifeCycleDelegate FlutterEngine and app a lifecycle, and registered plug-in management. In the initialization method of FlutterPluginAppLifeCycleDelegate initialization Flutter in the init file directory lookups
- (instancetype)init {
if (self = [super init]) {
_lifeCycleDelegate = [[FlutterPluginAppLifeCycleDelegate alloc] init];
}
return self;
}
Copy the code
FlutterPluginAppLifeCycleDelegate
In/Users/cuco/engine/SRC/flutter/shell/platform/Darwin/ios/framework/Source/FlutterPluginAppLifeCycleDelegate mm initialization is to call ini T instance method to initialize the flutter. During the initialization process, the cache directory is obtained. The image files and data files of flutter have been configured and the path has been set.
/// Cache directory
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;
}
Copy the code
FlutterCallbackCache
DartCallbackCache file path related categories: flutter/lib/UI/plugins/callback_cache. H is the following code to load a file directory initialization operation, the basic knowledge
+ (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
Copy the code
The FlutterPluginRegistry plug-in initialization process
The implementation code of FlutterPluginRegistry is implemented in FlutterAppDelegate. Mm. Please refer to the code for the specific implementation process
- (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;
}
Copy the code
After the above loading, FlutterEngine’s core code is initialized. The FlutterAppDelegate initialization process mainly does:
1. Load related plug-in information
2. Initialize the cache directory
3. Bind the life cycle of the FlutterEngine to the App
4. Call FlutterEngine for communication in different App life cycles
It’s basically doing initialization of global information
FlutterViewController initialization process
The initialization process of FlutterEngine is mainly to bind UI events in FlutterViewController and initialize logic in related life cycle. We first analyze key points and then share each point in detail in subsequent articles. All operations are invoked in the upper cycle method of the FlutterViewController
Flutter/shell/platform/Darwin/ios/framework/Source/FlutterEngine mm mainly implements the App layer and FlutterEngine an entry control logic
Flutter/shell/platform/Darwin/ios/framework/Source/FlutterView mm inheritance UIView provides a FlutterEngine engine for drawing operation
NotificationCenterFlutterEngine interact and App notification channel
1. - init: initialize FlutterEngine, FlutterView, setupNotificationCenterObservers register 2. AwakeFromNib: 3. LoadView: 4. ViewDidLoad: 5. ViewWillAppear: ViewWillLayoutSubviews: 8.viewDidLayOutSubViews: ViewDidAppear: Update Local information, update user Settings, update access status 10. ViewWillDisappear: 11. ViewDidDisappear: Update the associated FlutterUICopy the code
When the iosAPP is started, there is not much to do, mainly initialize the path of loading data, listen to the iosAPP life cycle, and register some event callback logic, then the App has been initialized, The FlutterViewController is then initialized, and when the FlutterViewController is initialized, The init method Flutter/shell/platform/Darwin/ios/framework/Source/FlutterViewController. Mm, in the init FlutterViewController initialization is called initialization function
- (instancetype)init {
return [self initWithProject:nil nibName:nil bundle:nil];
}
Copy the code
FlutterViewController initialization entry
1. Initialize the entire FlutterEngine framework initWithProject, save the FlutterViewController to a weak reference, initialize the FlutterEngine, and call the initWithName method for initialization
2. Initializing FlutterView is mainly the interface of IOSUI interaction, and mainly functions as a management logic class of FlutterEngine and IOSUI, which is provided to FlutterEngine for rendering processing
3. Start SplashScreenView
- CreateShell: FlutterEngine and uniform interface platform directly
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;
}
Copy the code
FlutterEngine
In FlutterEngine initialization, the relevant resource recruitment is mainly found, and the initialization operation is carried out by four threads and message queues. The initialization is mainly carried out inside FlutterEngine, and there is no business code logic being loaded
1.FlutterDartProject: initializes flutter_asset-related file path resolution during the entire FlutterEngine initialization process, parses command line parameters, and builds default process parameters
2. FlutterPlatformViewsController: the main function is to handle FlutterEngine side View logic flutter/shell/platform/Darwin/ios/framework/Source/Flut terPlatformViews.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;
}
Copy the code
FlutterDartProject
- (instancetype)initWithPrecompiledDartBundle:(NSBundle*)bundle {
self = [super init];
if (self) {
_precompiledDartBundle.reset([bundle retain]);
_settings = DefaultSettingsForProcess(bundle);
}
returnself; } to find the path of the Frameworks/App. Framework/flutter_assets + (nsstrings *) flutterAssetsName: (NSBundle *) bundle {nsstrings * flutterAssetsName = [bundle objectForInfoDictionaryKey:@"FLTAssetsPath"];
if (flutterAssetsName == nil) {
flutterAssetsName = @"Frameworks/App.framework/flutter_assets";
}
return flutterAssetsName;
}
Copy the code
DefaultSettingsForProcess
Set Flutter_asset load path, set FlutterEngine how to find executable file path during these processes. In the package structure of App above, we can see that Flutter related resource files, then when FlutterEngine starts, We can then load the associated Flutter code and the associated resource file path
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]];
boolhasExplicitBundle = 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;
}
Copy the code
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]; }Copy the code
setupChannels
Engine/SRC/flutter/shell/platform/Darwin/ios/framework/Source/FlutterEngine. Registered in mm FlutterUI layer and ios Plugin, between these plugins is a system level
- (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]]);
}
Copy the code
FlutterViewcontroller is bound to the flutterEngine.mm instance
- (void)setViewController:(FlutterViewController*)viewController {
FML_DCHECK(self.iosPlatformView);
_viewController = [viewController getWeakPtr];
self.iosPlatformView->SetOwnerViewController(_viewController);
[self maybeSetupPlatformViewChannels];
}
Copy the code
FlutterView
Then return to flutter/shell/platform/Darwin/ios/framework/Source/FlutterViewController mm initWithProject continue to analysis, initialize flutter/shell/p Latform Darwin/ios/framework/Source/FlutterView. Mm said initial 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;
}
Copy the code
FlutterEngine: createShell
This method is described in detail in the Android startup process and is not explained in more detail here
1. Initialize the main() function of the FlutterDart layer as the entry point
2. Create Rasterizer
3. Start message queue MessageLoop
4. Create platform, GPU, UI, and I/O
- (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; }Copy the code
In the previous call, we were most concerned with the Shell creation process. Flutter/shell/common/shell. Cc of this class is all platforms and FlutterPlatformView public entry point, after the call the following function, CreateShellOnPlatformThread create flutter related operating environment
// 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
);
Copy the code
The above part is initialized on the IOS side, and when the shell:: shell:: Create method is called, it truly enters the processing logic that is unified across all platforms of FlutterEngine
loadView
In the life cycle function of FlutterViewController, FlutterView was bound to the current FlutterViewController initialization operation View for display, and the SplashView initializing App startup was initialized for initialization. Several methods are provided for loading splashViews with different type definitions
- (void)loadView {
self.view = _flutterView.get();
self.view.multipleTouchEnabled = YES;
self.view.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
[self installSplashScreenViewIfNecessary];
}
Copy the code
FlutterEngine:launchEngine
Call FlutterEngine in the viewWillAppear lifecycle function for initialization
- (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];
}
Copy the code
FlutterEngine:Run
1. Load the configuration and find the entry point of the specified FlutterEngine, that is, the entry point of the startup function of the FlutterUI layer
2. Call FltuterEngine – > Run method start FlutterEngine loaded FltuterUI related code flutter/shell/common/engine. The 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."; }}})); }Copy the code
So far, the difference between IOS and Android code is the above part, after calling engine->Run function, it enters the core part of FlutteREngine, all Android and IOS running code logic is the same, please refer to the Android startup process
summary
The initialization operation of FlutterEngine on IOS feels much simpler than that on Android. The following is a summary of how FlutterEngine related logic is initialized during App startup. The above analysis and Android related parts have not been analyzed. Hell ::Shell::Create, FlutterEngine:Run, FlutterEngine core code logic, all platforms are basically consistent
1. When FlutterAppDelegateAPP is started, init is called to initialize globally related logic and lifecycle
2.FlutterViewController starts. Init starts to initialize FlutterEngine
3. Assign FlutterView to view of FlutterViewController in loadView
4. Call engine start in viewWillAppear method, load the code related to FlutterUI layer, actually find the main() related to FlutterUI layer to call you