Flutter與原生交互側(cè)滑
原生跳轉(zhuǎn)flutter,如果原生不做處理,flutter內(nèi)部頁(yè)面支持側(cè)滑,但從flutter到原生不支持側(cè)滑。
監(jiān)聽flutter首頁(yè),當(dāng)首頁(yè)出現(xiàn)時(shí),原生ViewController打開側(cè)滑代理事件;跳轉(zhuǎn)到flutter二級(jí)頁(yè)面關(guān)閉原生側(cè)滑事件。
具體實(shí)現(xiàn):
@interface BLFlutterViewController : FBFlutterViewContainer
@end
@implementation BLFlutterViewController
- (void)setIsSideslip:(BOOL)isSideslip{
_isSideslip = isSideslip;
RTRootNavigationController * navi = self.rt_navigationController;
if (isSideslip) {
navi.interactivePopGestureRecognizer.delaysTouchesBegan = YES;
navi.interactivePopGestureRecognizer.delegate = self;
navi.interactivePopGestureRecognizer.enabled = YES;
} else {
navi.interactivePopGestureRecognizer.delegate = nil;
navi.interactivePopGestureRecognizer.enabled = NO;
}
}
[self.methodChannel setMethodCallHandler:^(FlutterMethodCall* call,
FlutterResult result) {
if([call.method isEqualToString:@"supportedSideSlip"]){//左滑
BOOL side = [call.arguments boolValue];
self.isSideslip = side;
}
}];
@end
//主頁(yè)
class HomePage extends StatefulWidget {
@override
_HomePageState createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
@override
void dispose() {
super.dispose();
PageVisibilityBinding.instance.removeObserver(this);
}
@override
void didChangeDependencies() {
super.didChangeDependencies();
///注冊(cè)監(jiān)聽器
PageVisibilityBinding.instance.addObserver(this, ModalRoute.of(context));
}
@override
void onPageHide() {
super.onPageHide();
print("LifecycleTestPage - onPageHide");
methodChannel.invokeMethod(channel_supportedSideSlip, false);
}
@override
void onPageShow() {
super.onPageShow();
print("LifecycleTestPage - onPageShow");
methodChannel.invokeMethod(channel_supportedSideSlip, true);
}
}
Flutter 編譯模式debug和release判斷
第一種、通過斷言識(shí)別
assert((){
// Do something for debug
print('這是asset下的輸出內(nèi)容');
return true;
}());
第二種、通過編譯常數(shù)識(shí)別
if (kReleaseMode){ //
//release
}else {
//debug
}
解決Flutter真機(jī)debug拔線后閃退
Flutter - debug/release切換優(yōu)化
iOS高版本,debug在斷開連接時(shí),進(jìn)入flutter模塊會(huì)閃退;使用flutter_boast在啟動(dòng)時(shí)就閃退。
可設(shè)置在debug也能運(yùn)行release


Release問題怎么排查
有時(shí)debug正常,但release可能卡死、閃退。沒有辦法聯(lián)調(diào),只能通過日志觀察,分析有問題的代碼。
FlutterError.onError = (FlutterErrorDetails details) async {
// 轉(zhuǎn)發(fā)至 Zone 中
Zone.current.handleUncaughtError(details.exception, details.stack);
};
runZoned<Future<Null>>(() async {
runApp(MyApp());
}, onError: (error, stackTrace) async {
//Do sth for error
String message =
"error = ${error.toString()}" + "\nstackTrace ${stackTrace.toString()}";
debugPrint(message);
});
BuildContext問題導(dǎo)致退出到首頁(yè)失敗
統(tǒng)一封裝了退到首頁(yè)的方法:
void popRoot(BuildContext context) {
// ignore: unnecessary_statements
Navigator.popUntil(context, (route) {
//跳到根目錄
String name = route.settings.name;
if (name != "/") {
return false;
}
return true;
});
}
如果在_MyAppState中收到消息,直接調(diào)用popRoot(context)會(huì)失效。
需要獲取頂層的context。
- 定義navigatorKey
final GlobalKey<NavigatorState> navigatorKey = GlobalKey<NavigatorState>();
- 注冊(cè)navigatorKey
MaterialApp(
navigatorKey:navigatorKey
...)
- 獲取當(dāng)前的context
Future.delayed(Duration(seconds: 0)).then((value) {
BuildContext curContext = navigatorKey.currentState.overlay.context;
popRoot(curContext);
});
FlutterViewController內(nèi)存沒有釋放
FlutterMethodChannel強(qiáng)引用self,導(dǎo)致內(nèi)存泄露
self.methodChannel = [FlutterMethodChannel
methodChannelWithName:@"cn.percent.online_document"
binaryMessenger:self];
解決方案
self.methodChannel = [FlutterMethodChannel
methodChannelWithName:@"cn.percent.online_document"
binaryMessenger:[BLWeakProxy proxyWithTarget:self]];
@interface BLWeakProxy : NSObject
@property (weak,nonatomic,readonly)id target;
+ (instancetype)proxyWithTarget:(id)target;
- (instancetype)initWithTarget:(id)target;
@end
@implementation BLWeakProxy
- (instancetype)initWithTarget:(id)target{
_target = target;
return self;
}
+ (instancetype)proxyWithTarget:(id)target{
return [[self alloc] initWithTarget:target];
}
- (void)forwardInvocation:(NSInvocation *)invocation{
SEL sel = [invocation selector];
if ([self.target respondsToSelector:sel]) {
[invocation invokeWithTarget:self.target];
}
}
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{
return [self.target methodSignatureForSelector:aSelector];
}
- (BOOL)respondsToSelector:(SEL)aSelector{
return [self.target respondsToSelector:aSelector];
}
@end
attach聯(lián)調(diào)時(shí)出現(xiàn)下面錯(cuò)誤
There are multiple observatory ports available.
Rerun this command with one of the following passed in as the appId:
flutter attach --app-id bundleId
flutter attach --app-id bundleId (2)
Exited (1)
XCode運(yùn)行,flutter直接停止,再次啟動(dòng)會(huì)出現(xiàn)上述錯(cuò)誤。
可以重新運(yùn)行XCode工程,暴力解法直接關(guān)掉模擬器,重新attch。
打了斷點(diǎn)卻不走
- 檢查下新加的代碼,有可能在斷點(diǎn)前面的代碼拋異常了。try...cache前面的代碼,看報(bào)錯(cuò)原因?;蛘哒译x的近的代碼,一行一行代碼調(diào)試,看最后在哪里突然消失。
- 重命名類大小寫問題:如果文件僅修改了大小寫,可能定位在老的文件。
跳轉(zhuǎn)flutter頁(yè)面,白屏問題
- 增加白屏的加載效果
UIView * splashScreenView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT)];
UIActivityIndicatorView * loadingView = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleGray];
[loadingView startAnimating];
loadingView.center = splashScreenView.center;
loadingView.size = CGSizeMake(40, 40);
[splashScreenView addSubview:loadingView];
- 增加緩存
@import Flutter;
@interface AppDelegate : FlutterAppDelegate <UIApplicationDelegate>
@property (nonatomic,strong) FlutterEngine *flutterEngine;
@end
@implementation AppDelegate
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
self.flutterEngine = [[FlutterEngine alloc] initWithName:@"my flutter engine"];
[self.flutterEngine run];
[GeneratedPluginRegistrant registerWithRegistry:self.flutterEngine];
}
@end
//初始化viewController
FlutterEngine *flutterEngine =
((AppDelegate *)UIApplication.sharedApplication.delegate).flutterEngine;
BLFlutterViewController *viewController = [[BLFlutterViewController alloc] initWithEngine:flutterEngine nibName:nil bundle:nil];
viewController.splashScreenView = splashScreenView;
[self.navigationController pushViewController:viewController animated:YES];
flutter 卡死閃退
flutter的UI線程在iOS中是常駐線程。如果出現(xiàn)閃退不會(huì)立刻崩潰,而是界面卡死,過段時(shí)間后閃退。
有點(diǎn)時(shí)候debug調(diào)試時(shí),并沒注意控制臺(tái)的報(bào)錯(cuò)信息,直接熱更了。而到了release情況,會(huì)引起程序卡死、閃退。
flutter: error = Null check operator used on a null value
stackTrace #0 State.setState (package:flutter/src/widgets/framework.dart:1108)
1 _MySpaceState.loadFiltrate (package:online_document/Section/myspace/MySpace.dart:126)
檢查數(shù)據(jù)是否有越界,空的情況。有時(shí)候debug情況沒有注意,或者測(cè)試的時(shí)候沒有復(fù)現(xiàn)。到release會(huì)一直打印信息,直到閃退。
flutter 開啟了4個(gè)常駐線程,崩潰時(shí)不會(huì)立刻崩潰,而是一直在瘋狂跑CPU,界面卡住。然后閃退。
Widget body() {
...
return list.pullCustomListViewHeader(headerWidget,
(BuildContext context, int index) {
if (index >= list.datas.length) {
return SizedBox();
}
return cell(context, list.datas[index]);
});
}
多次跳轉(zhuǎn)flutter頁(yè)面,出現(xiàn)白屏
快速點(diǎn)擊時(shí),小概率出現(xiàn)多次跳到同一個(gè)頁(yè)面。其中有些頁(yè)面沒刷新出來,出現(xiàn)白屏。
模擬測(cè)試:
for (int i = 0; i<3; i++) {
[self.navigationController pushViewController:[BLFlutterViewController flutterVC] animated:YES];
}
解決方案:
防止按鈕快速點(diǎn)擊。
Flutter Intl插件不生效
flutter_intl:
enabled: true # Required. Must be set to true to activate the plugin. Default: false
arb_dir: lib/l10n # Optional. Sets the directory of your ARB resource files. Provided value should be a valid path on your system. Default: lib/l10n
output_dir: lib/generated # Optional. Sets the directory of generated localization files. Provided value should be a valid path on your system. Default: lib/generated
use_deferred_loading: false
有時(shí)候發(fā)現(xiàn)arb更改后,插件沒有自動(dòng)更新。主要原因有兩個(gè):
- 各個(gè)語(yǔ)言中的key沒對(duì)上;比如中文中有hello word,而其他語(yǔ)言少了
- 不同語(yǔ)言相同key但取的變量名不一致,如:
"doc_version_title":"第{v1}版"
"doc_version_title" : "Edition: {v2}"