About
Flutter plugins 被註åçé åºï¼æå½±é¿å° plugin code å·è¡çé åºï¼é²èå¯è½æé æç¬¬ä¸æ¹ç»å ¥å¤±æï¼æç¡æ³æ£ç¢ºå°æ¶å° deep link çå¼å«ã Android å iOS ç Flutter ä¾èªåä¸å Engineï¼æä»¥åæ¨£é½æè¼å ¥é åºçåé¡ã æ¬ç¯æ³ç´éå顿¢è©¢çéç¨ï¼ä»¥åè§£æ±ºçæ¹æ³ã
Issue Intro
iOS ä¸ï¼å ¶ä¸ä¸åèçå¾å¤é¨å¼å« App ç callback function æ¯ application(_:open:options:)ã
常è¦ç實ä½å¦ä¸ï¼
// sample code
func application(_ app: UIApplication, open url: URL, options: [UIApplicationOpenURLOptionsKey: Any] = [:]) -> Bool {
if FacebookLogin.handle(url) {
return true // èç Facebook ç»å
¥
} else if LineLogin.handle(url) {
return true // èç LINE ç»å
¥
} else if FirebaseDynamicLinks.handle(url) {
return true // èç Firebase Dynamic Links
} else {
return AppRouter.handle(url) // å°éå deeplink urlï¼å¨ App å
§é¨ä½¿ç¨ webview æä½¿ç¨æ°ç native ç«é¢åç¾
}
}
ä¸åå¼å« App èµ·ä¾ç urlï¼åªæå±¬æ¼ä¸ç¨®æ æ³ãé常æå¨èçå®ç»å ¥åç¹æ®ç deeplink (ex: FirebaseDynamicLinks) ä¹å¾ï¼æä½¿ç¨ App å §ç Router ä¾åç¾éå urlã æ¤æ code çé åºæå ¶å¿ è¦ï¼æåç¾ url æ¾å¨æå¾ã
èå¨ Flutter ä¸ï¼plugin æå¯¦ä½ FlutterPlugin ç protocol registerWithRegistrar:registrarã
å¨å
¶ä¸ï¼plugin è· Flutter ä½¿ç¨ addApplicationDelegate: 註åï¼è²æèªå·±è¦èç UIApplicationDelegate çå¼å«ãæ¤æè¥å¤å plugin é½æè²æï¼åéåé åºæ¯å¦ä½è¢«æ±ºå®çå¢ï¼
Firebase Dynamic Links ç實ä½å¦ä¸
// https://github.com/FirebaseExtended/flutterfire/blob/firebase_dynamic_links-v0.5.0+11/packages/firebase_dynamic_links/ios/Classes/FLTFirebaseDynamicLinksPlugin.m#L52
+ (void)registerWithRegistrar:(NSObject<FlutterPluginRegistrar> *)registrar {
FlutterMethodChannel *channel =
[FlutterMethodChannel methodChannelWithName:@"plugins.flutter.io/firebase_dynamic_links"
binaryMessenger:[registrar messenger]];
FLTFirebaseDynamicLinksPlugin *instance =
[[FLTFirebaseDynamicLinksPlugin alloc] initWithChannel:channel];
[registrar addMethodCallDelegate:instance channel:channel];
[registrar addApplicationDelegate:instance];
// ...
}
- (BOOL)application:(UIApplication *)application
openURL:(NSURL *)url
options:(NSDictionary<UIApplicationOpenURLOptionsKey, id> *)options {
return [self checkForDynamicLink:url];
}
è uni_links éå幫å¿èç DeepLink å Universal Link ç pluginï¼æå¨ native æ url æ¥èµ·ä¾ï¼ç¶å¾å³çµ¦ Flutterï¼Flutter éç¼è
å°±å¯ä»¥å°å¿ä½¿ç¨ Stream ä¾èçé²ä¾ç urlã
// https://github.com/avioli/uni_links/blob/master/ios/Classes/UniLinksPlugin.m#L58
- (BOOL)application:(UIApplication *)application
openURL:(NSURL *)url
options:(NSDictionary<UIApplicationOpenURLOptionsKey, id> *)options {
self.latestLink = [url absoluteString];
return YES;
}
- (void)setLatestLink:(NSString *)latestLink {
static NSString *key = @"latestLink";
[self willChangeValueForKey:key];
_latestLink = [latestLink copy];
[self didChangeValueForKey:key];
if (_eventSink) _eventSink(_latestLink);
}
æ¤æï¼åææå¤å plugin è· flutter 註åè¦èç UIApplicationDelegateï¼æ¤æå°±æç¼çå·è¡é åºä¸çåé¡ã徿å¯è½ Firebase Dynamic Links æç¡æ³è¢«å¼å«å°ï¼è¢«å
¶ä» plugin å
ææªèµ°ï¼èæ²ææåè§£æ Dynamic Linksãåæè¨±æ¯ç»å
¥ç url è¢«ææªèµ°ï¼èé æç¡æ³ç»å
¥çåé¡ã
實éè·äºå¹¾æ¬¡å¾ç¼ç¾ï¼éåå·è¡é åºæ¯ä¸åºå®çã
é£è¦å¦ä½è½ä¿è Login 以å Firebase Dynamic Links çå·è¡é åºå¨ uni_links ä¹åå¢ï¼
Dive into Flutter Source Code
å¨ Flutter ç Objective-C++ code ä¸ï¼å°æ¼èç incoming link ç applicationURL:openURL:options èçå¦ä¸ï¼
仿尿æç _delegatesï¼éä¸å°è©¢åæ¯å¦è¦èç urlï¼å¦æå·²èç宿ï¼åææ© return è·³é¢ for loopã
// https://github.com/flutter/engine/blob/9f650edd14dd0d74acb3d6ad65eb794b1e4b27e3/shell/platform/darwin/ios/framework/Source/FlutterPluginAppLifeCycleDelegate.mm#L312
- (BOOL)application:(UIApplication*)application
openURL:(NSURL*)url
options:(NSDictionary<UIApplicationOpenURLOptionsKey, id>*)options {
for (NSObject<FlutterApplicationLifeCycleDelegate>* delegate in _delegates) {
if (!delegate) {
continue;
}
if ([delegate respondsToSelector:_cmd]) {
if ([delegate application:application openURL:url options:options]) {
return YES;
}
}
}
return NO;
}
èéå _delegate 忝å¨éå addDelegate: ä¸è¢«å å
¥çã_delegate ççµæ§åªæ¯ä¸å PointerArrayï¼ä»çé åºæ¯å¨ plugin 被 register æï¼å¼å« addDelegate: æåºå®ä¸çã
// https://github.com/flutter/engine/blob/9f650edd14dd0d74acb3d6ad65eb794b1e4b27e3/shell/platform/darwin/ios/framework/Source/FlutterPluginAppLifeCycleDelegate.mm#L98
@implementation FlutterPluginAppLifeCycleDelegate {
NSMutableArray* _notificationUnsubscribers;
UIBackgroundTaskIdentifier _debugBackgroundTask;
// Weak references to registered plugins.
NSPointerArray* _delegates;
}
...
- (void)addDelegate:(NSObject<FlutterApplicationLifeCycleDelegate>*)delegate {
[_delegates addPointer:(__bridge void*)delegate];
...
}
è Flutter plugin 註åçé åºåæ¯ç±èªåç¢ççæªæ¡ GeneratedPluginRegistrant.m ææ±ºå®çã
//
// Generated file. Do not edit.
//
...
+ (void)registerWithRegistry:(NSObject<FlutterPluginRegistry>*)registry {
[FlutterLineSdkPlugin registerWithRegistrar:[registry registrarForPlugin:@"FlutterLineSdkPlugin"]];
[UniLinksPlugin registerWithRegistrar:[registry registrarForPlugin:@"UniLinksPlugin"]];
[FLTFirebaseDynamicLinksPlugin registerWithRegistrar:[registry registrarForPlugin:@"FLTFirebaseDynamicLinksPlugin"]];
}
è Flutter æ¯å¦ä½ç¢çéå GeneratedPluginRegistrant.m å¢ï¼
// https://github.com/flutter/flutter/blob/39d7a019c150ca421b980426e85b254a0ec63ebd/packages/flutter_tools/gradle/flutter.gradle#L248
* The plugins are added to pubspec.yaml. Then, upon running `flutter pub get`,
* the tool generates a `.flutter-plugins` file, which contains a 1:1 map to each plugin location.
ç¶å·è¡äº flutter pub getï¼å»æå flutter plugin æï¼æç¢çä¸ä»½ .flutter-pluginsï¼è GeneratedPluginRegistrant.m 峿¯ä¾ç
§é份æåæªçé åºæç¢çã
// https://github.com/flutter/flutter/blob/fa4d31b31/packages/flutter_tools/lib/src/plugins.dart#L464
const String _objcPluginRegistryImplementationTemplate = '''//
// Generated file. Do not edit.
//
#import "GeneratedPluginRegistrant.h"
#import </.h>
@implementation GeneratedPluginRegistrant
+ (void)registerWithRegistry:(NSObject<FlutterPluginRegistry>*)registry {
[ registerWithRegistrar:[registry registrarForPlugin:@""]];
}
@end
''';
// called by `_writeIOSPluginRegistrant`
èéå .flutter-plugins çé åºï¼å çºå¨ findPlugins ä¸å° Map packages 使ç¨äº forEachï¼æä»¥æ plugins çé åºç¡æ³åºå®ã
List<Plugin> findPlugins(FlutterProject project) {
final List<Plugin> plugins = <Plugin>[];
Map<String, Uri> packages;
try {
final String packagesFile = fs.path.join(
project.directory.path,
PackageMap.globalPackagesPath,
);
packages = PackageMap(packagesFile).map;
} on FormatException catch (e) {
printTrace('Invalid .packages file: $e');
return plugins;
}
packages.forEach((String name, Uri uri) {
final Uri packageRoot = uri.resolve('..');
final Plugin plugin = _pluginFromPubspec(name, packageRoot);
if (plugin != null) {
plugins.add(plugin);
}
});
return plugins;
}
å çºç¡æ³åºå® plugin çé åºï¼æä»¥æ¯æ¬¡éæ° run projectï¼å¨ç¸åç input ä¸ï¼æå¯è½æå çºä¸åç plugin è¼å ¥é åºï¼èç¢çä¸åççµæã
Solution
- 寫äºä¸åç°¡çå° scriptï¼å¨ Xcode compile åï¼å¹«å¿æåºåæå®
GeneratedPluginRegistrant.m裡é¢ç plugin 註åé åºã - ç¨å¼ç¢¼å¨ githubï¼https://github.com/eJamesLin/FlutterPluginSort
- è·å®ä¹å¾ï¼æ
GeneratedPluginRegistrant.mä¹å å ¥gitversion-control 裡é¢å³å¯ - 妿宿¹å·²ææ´æ°ï¼æå使æ´å»ºè°çè§£æ³ï¼é常æ¡è¿æéæåï¼æè¬~