前言
flutter是一個(gè)真正實(shí)現(xiàn)了一次編碼,多端運(yùn)行的技術(shù)袍辞,在面對(duì)兩個(gè)平臺(tái)需要快速開(kāi)發(fā)迭代鞋仍,或節(jié)省人力成本的情況下,flutter是個(gè)很好的選擇搅吁,特別是初創(chuàng)公司威创。至于flutter和其他技術(shù)的效率對(duì)比我不在這里多贅述落午,因?yàn)檫@篇文章重點(diǎn)不在于此。
但是針對(duì)市場(chǎng)上很多公司已有項(xiàng)目那婉,有多少公司敢于將項(xiàng)目重新翻一次flutter板甘,踩坑成本和學(xué)習(xí)成本都成為不可避免的代價(jià)!但我們可以選擇部分使用flutter详炬,于是乎盐类,我開(kāi)始尋找現(xiàn)有項(xiàng)目中加入flutter的解決方案。
想法很好呛谜,開(kāi)始著手研究在跳。我發(fā)現(xiàn)大體分為兩種:一種是flutter官網(wǎng)的項(xiàng)目依賴(lài),第二種是集成flutter產(chǎn)物隐岛。由于項(xiàng)目不一定是同一撥人開(kāi)發(fā)native代碼和flutter代碼猫妙,那就有可能出現(xiàn)項(xiàng)目之間的依賴(lài)問(wèn)題,以及進(jìn)度聚凹,代碼管理提交問(wèn)題割坠。所以我更想去了解后者,產(chǎn)物方式集成妒牙。這樣可以實(shí)現(xiàn)兩撥開(kāi)發(fā)人員的獨(dú)立性彼哼,也可以將產(chǎn)物發(fā)布到服務(wù)器,使用gradle來(lái)集成湘今,那就是后話(huà)了敢朱。
然而,我一頓搜索摩瞎,在我查找了官網(wǎng)拴签、百度、簡(jiǎn)書(shū)旗们、stackoverflow等等可以想到的搜索途徑后蚓哩,我發(fā)現(xiàn)沒(méi)有一篇文章比較直觀和簡(jiǎn)潔,總有一些文章會(huì)丟失一些關(guān)鍵性信息上渴,或者含糊不清岸梨,或者實(shí)現(xiàn)方式比較繁瑣、麻煩驰贷。我終于忍不住要自己整理一篇文章,來(lái)記錄洛巢,也希望能幫到其他朋友括袒。
值得注意的是,我采用了flutter module的方式集成稿茉,而不是flutter app锹锰,請(qǐng)大家注意芥炭!你可以選擇使用Android Studio來(lái)創(chuàng)建flutter項(xiàng)目的時(shí)候選擇 flutter module,也可以使用命令行flutter create -t module flutter_module
來(lái)創(chuàng)建恃慧。
先附上github代碼參考地址
- flutter代碼:http://github.com/Andy888888/flutter_aplus
- native代碼:http://github.com/Andy888888/flutter_aplus_native
效果圖如下(藍(lán)色導(dǎo)航欄界面為flutter效果):
動(dòng)圖被壓縮的比較嚴(yán)重园蝠,犧牲了畫(huà)質(zhì)。因?yàn)橛信笥颜f(shuō)flutter出來(lái)的效果比較卡頓痢士,所以主要給大家感受下流暢度(前提是不要拿debug模式來(lái)比較彪薛,我集成的是release模式產(chǎn)物)。
好了怠蹂,廢話(huà)完了善延,開(kāi)整!
Android平臺(tái)
-
生成aar文件
創(chuàng)建一個(gè)"flutter module"項(xiàng)目城侧,直接點(diǎn)擊運(yùn)行會(huì)在.android/Flutter/build/outputs/
目錄下生成 flutter-debug.aar
文件易遣。但是,我們需要的是release文件嫌佑,使用下面的命令可以生成fltter-release.aar
文件
flutter build apk
我們將這個(gè)flutter-release.aar
當(dāng)作正常aar文件集成到我們的項(xiàng)目里即可
-
可能遇到的問(wèn)題
- 一般flutter_module項(xiàng)目默認(rèn)最低版本是16豆茫,要求版本不得低于16,視具體情況而定
- 在集成的時(shí)候屋摇,需要加入以下代碼到gradle揩魂,否則報(bào)--min-O 最小版本必須大于Level26
compileOptions {
sourceCompatibility 1.8
targetCompatibility 1.8
}
-
Android Native 項(xiàng)目中測(cè)試aar
-
作為視圖組件插入Activity中
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
flutterModule();
}
private void flutterModule() {
View flutterView = Flutter.createView(MainActivity.this, getLifecycle(), "route2");
FrameLayout.LayoutParams layoutParams = new FrameLayout.LayoutParams(600, 800);
layoutParams.leftMargin = 100;
layoutParams.topMargin = 400;
addContentView(flutterView, layoutParams);
}
}
-
作為Activity跳轉(zhuǎn)
- 首先要在Application的onCreate中注冊(cè)
FlutterMain.startInitialization(getApplicationContext());
- 創(chuàng)建一個(gè)FlutterModuleActivity
/**
* 描述:FlutterModuleActivity.
* <p>
*
* @author yanwenqiang.
* @date 2019/4/15
*/
public class FlutterModuleActivity extends FlutterActivity implements ActivityParams {
@Override
public FlutterView createFlutterView(Context context) {
String route = getIntent().getStringExtra(ROUTE);
Log.e("路由:",route);
WindowManager.LayoutParams matchParent = new WindowManager.LayoutParams(-1, -1);
FlutterNativeView nativeView = this.createFlutterNativeView();
FlutterView flutterView = new FlutterView(this, null, nativeView);
flutterView.setInitialRoute(route);
flutterView.setLayoutParams(matchParent);
this.setContentView(flutterView);
return flutterView;
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
GeneratedPluginRegistrant.registerWith(this);
}
}
- 跳轉(zhuǎn)到FlutterModuleActivity
private void flutterActivity() {
Intent intent = new Intent(this, FlutterModuleActivity.class);
intent.putExtra(FlutterModuleActivity.ROUTE,
"edit-property?{\"keyId\":\"123\",\"trustType\":2}");
startActivity(intent);
}
- 使用MethodChannel實(shí)現(xiàn)Flutter調(diào)用Native Code
public class FlutterModuleActivity extends FlutterActivity implements ActivityParams {
// channel path
private static final String CHANNEL = "flutter.io/lifecycle";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
GeneratedPluginRegistrant.registerWith(this);
new MethodChannel(getFlutterView(), CHANNEL).setMethodCallHandler(
(call, result) -> {
if (call.method.equals("back")) {
finish();
}
});
}
}
flutter 代碼
// channel path
const platform = const MethodChannel('flutter.io/lifecycle');
Future<void> _back() async {
try {
await platform.invokeMethod('back');
} on PlatformException catch (e) {
}
}
值得注意的是兩端的【channel path】一定要保持一致。
iOS平臺(tái)
-
flutter module項(xiàng)目生成iOS release版本 framework
flutter build ios --release
-
提取并添加
App.framework
和Flutter.framework
到項(xiàng)目
App.framework
在/.ios/Flutter目錄下摊册。我們自己寫(xiě)的flutter代碼就在這個(gè)framework里肤京,每次修改flutter代碼后,更新這個(gè)framework即可
Flutter.framework
在/.ios/Flutter/engine目錄下茅特。flutter的引擎忘分,幾乎不需要更換,除非官方有更新白修。
-
改造AppDelegate
//
// AppDelegate.h
// TestFlutter
//
// Created by 燕文強(qiáng) on 2019/4/17.
// Copyright ? 2019 燕文強(qiáng). All rights reserved.
//
#import <UIKit/UIKit.h>
#import <Flutter/Flutter.h>
@interface AppDelegate : FlutterAppDelegate <UIApplicationDelegate, FlutterAppLifeCycleProvider>
@end
//
// AppDelegate.m
// TestFlutter
//
// Created by 燕文強(qiáng) on 2019/4/17.
// Copyright ? 2019 燕文強(qiáng). All rights reserved.
//
#import "AppDelegate.h"
@interface AppDelegate ()
@end
@implementation AppDelegate
{
FlutterPluginAppLifeCycleDelegate *_lifeCycleDelegate;
}
- (instancetype)init {
if (self = [super init]) {
_lifeCycleDelegate = [[FlutterPluginAppLifeCycleDelegate alloc] init];
}
return self;
}
- (BOOL)application:(UIApplication*)application
didFinishLaunchingWithOptions:(NSDictionary*)launchOptions {
return [_lifeCycleDelegate application:application didFinishLaunchingWithOptions:launchOptions];
}
- (void)applicationDidEnterBackground:(UIApplication*)application {
[_lifeCycleDelegate applicationDidEnterBackground:application];
}
- (void)applicationWillEnterForeground:(UIApplication*)application {
[_lifeCycleDelegate applicationWillEnterForeground:application];
}
- (void)applicationWillResignActive:(UIApplication*)application {
[_lifeCycleDelegate applicationWillResignActive:application];
}
- (void)applicationDidBecomeActive:(UIApplication*)application {
[_lifeCycleDelegate applicationDidBecomeActive:application];
}
- (void)applicationWillTerminate:(UIApplication*)application {
[_lifeCycleDelegate applicationWillTerminate:application];
}
- (void)application:(UIApplication*)application
didRegisterUserNotificationSettings:(UIUserNotificationSettings*)notificationSettings {
[_lifeCycleDelegate application:application
didRegisterUserNotificationSettings:notificationSettings];
}
- (void)application:(UIApplication*)application
didRegisterForRemoteNotificationsWithDeviceToken:(NSData*)deviceToken {
[_lifeCycleDelegate application:application
didRegisterForRemoteNotificationsWithDeviceToken:deviceToken];
}
- (void)application:(UIApplication*)application
didReceiveRemoteNotification:(NSDictionary*)userInfo
fetchCompletionHandler:(void (^)(UIBackgroundFetchResult result))completionHandler {
[_lifeCycleDelegate application:application
didReceiveRemoteNotification:userInfo
fetchCompletionHandler:completionHandler];
}
- (BOOL)application:(UIApplication*)application
openURL:(NSURL*)url
options:(NSDictionary<UIApplicationOpenURLOptionsKey, id>*)options {
return [_lifeCycleDelegate application:application openURL:url options:options];
}
- (BOOL)application:(UIApplication*)application handleOpenURL:(NSURL*)url {
return [_lifeCycleDelegate application:application handleOpenURL:url];
}
- (BOOL)application:(UIApplication*)application
openURL:(NSURL*)url
sourceApplication:(NSString*)sourceApplication
annotation:(id)annotation {
return [_lifeCycleDelegate application:application
openURL:url
sourceApplication:sourceApplication
annotation:annotation];
}
- (void)application:(UIApplication*)application
performActionForShortcutItem:(UIApplicationShortcutItem*)shortcutItem
completionHandler:(void (^)(BOOL succeeded))completionHandler NS_AVAILABLE_IOS(9_0) {
[_lifeCycleDelegate application:application
performActionForShortcutItem:shortcutItem
completionHandler:completionHandler];
}
- (void)application:(UIApplication*)application
handleEventsForBackgroundURLSession:(nonnull NSString*)identifier
completionHandler:(nonnull void (^)(void))completionHandler {
[_lifeCycleDelegate application:application
handleEventsForBackgroundURLSession:identifier
completionHandler:completionHandler];
}
- (void)application:(UIApplication*)application
performFetchWithCompletionHandler:(void (^)(UIBackgroundFetchResult result))completionHandler {
[_lifeCycleDelegate application:application performFetchWithCompletionHandler:completionHandler];
}
- (void)addApplicationLifeCycleDelegate:(NSObject*)delegate {
[_lifeCycleDelegate addDelegate:delegate];
}
@end
-
創(chuàng)建FlutterModuleViewController
//
// FlutterModuleViewController.h
// TestFlutter
//
// Created by 燕文強(qiáng) on 2019/4/17.
// Copyright ? 2019 燕文強(qiáng). All rights reserved.
//
#import <UIKit/UIKit.h>
#import <Flutter/Flutter.h>
@interface FlutterModuleViewController : FlutterViewController
@end
//
// FlutterModuleViewController.m
// TestFlutter
//
// Created by 燕文強(qiáng) on 2019/4/17.
// Copyright ? 2019 燕文強(qiáng). All rights reserved.
//
#import "FlutterModuleViewController.h"
@interface FlutterModuleViewController ()
@end
@implementation FlutterModuleViewController
- (void)viewWillAppear:(BOOL)animated{
// 頁(yè)面進(jìn)入后隱藏自帶導(dǎo)航欄
[self.navigationController setNavigationBarHidden:YES animated:YES];
[super viewWillAppear:animated];
// 支持側(cè)滑返回
self.navigationController.interactivePopGestureRecognizer.delegate = (id)self;
// flutter與Native交互
FlutterMethodChannel *lifecycleChannel = [FlutterMethodChannel
methodChannelWithName:@"flutter.io/lifecycle"
binaryMessenger:self];
[lifecycleChannel setMethodCallHandler:^(FlutterMethodCall * _Nonnull call, FlutterResult _Nonnull result) {
if([call.method isEqualToString:@"back"]){
[self.navigationController popViewControllerAnimated:YES];
}
}];
}
-(void) viewWillDisappear:(BOOL)animated{
[self.navigationController setNavigationBarHidden:NO animated:YES];
[super viewWillDisappear:animated];
}
- (UIView *)splashScreenView{
# warning 重寫(xiě)splashScreenView來(lái)處理Native跳轉(zhuǎn)到Flutter頁(yè)面時(shí)妒峦,會(huì)出現(xiàn)LaunchScreen
CGRect rect = [UIScreen mainScreen].applicationFrame;
UIView *view = [[UIView alloc]initWithFrame:rect];
view.backgroundColor=UIColor.redColor;
return view;
}
@end
-
跳轉(zhuǎn)到FlutterModuleViewController
- (IBAction)click:(id)sender {
FlutterModuleViewController *flutterViewController = [[FlutterModuleViewController alloc] initWithProject:nil nibName:nil bundle:nil];
[flutterViewController setInitialRoute:@"edit-property?{\"keyId\":\"123\",\"trustType\":2}"];
[self.navigationController pushViewController:flutterViewController animated:YES];
}
-
運(yùn)行時(shí)記得關(guān)閉BitCode
祝你flutter玩的開(kāi)心!