flutter 產(chǎn)物集成

前言

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代碼參考地址

效果圖如下(藍(lán)色導(dǎo)航欄界面為flutter效果):

效果圖 I
效果圖 II
動(dòng)圖效果

動(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)題

  1. 一般flutter_module項(xiàng)目默認(rèn)最低版本是16豆茫,要求版本不得低于16,視具體情況而定
  2. 在集成的時(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)
  1. 首先要在Application的onCreate中注冊(cè)
FlutterMain.startInitialization(getApplicationContext());
  1. 創(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);
    }
}
  1. 跳轉(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);
    }
  1. 使用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.frameworkFlutter.framework到項(xiàng)目

App.framework在/.ios/Flutter目錄下摊册。我們自己寫(xiě)的flutter代碼就在這個(gè)framework里肤京,每次修改flutter代碼后,更新這個(gè)framework即可

Flutter.framework在/.ios/Flutter/engine目錄下茅特。flutter的引擎忘分,幾乎不需要更換,除非官方有更新白修。

添加 framework
選擇 Create folder references


  • 改造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)心!







最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末兵睛,一起剝皮案震驚了整個(gè)濱河市肯骇,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌祖很,老刑警劉巖笛丙,帶你破解...
    沈念sama閱讀 221,273評(píng)論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異假颇,居然都是意外死亡胚鸯,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,349評(píng)論 3 398
  • 文/潘曉璐 我一進(jìn)店門(mén)笨鸡,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)姜钳,“玉大人坦冠,你說(shuō)我怎么就攤上這事「缜牛” “怎么了辙浑?”我有些...
    開(kāi)封第一講書(shū)人閱讀 167,709評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)拟糕。 經(jīng)常有香客問(wèn)我判呕,道長(zhǎng),這世上最難降的妖魔是什么已卸? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 59,520評(píng)論 1 296
  • 正文 為了忘掉前任佛玄,我火速辦了婚禮,結(jié)果婚禮上累澡,老公的妹妹穿的比我還像新娘梦抢。我一直安慰自己,他們只是感情好愧哟,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,515評(píng)論 6 397
  • 文/花漫 我一把揭開(kāi)白布奥吩。 她就那樣靜靜地躺著,像睡著了一般蕊梧。 火紅的嫁衣襯著肌膚如雪霞赫。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 52,158評(píng)論 1 308
  • 那天肥矢,我揣著相機(jī)與錄音端衰,去河邊找鬼。 笑死甘改,一個(gè)胖子當(dāng)著我的面吹牛旅东,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播十艾,決...
    沈念sama閱讀 40,755評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼抵代,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了忘嫉?” 一聲冷哼從身側(cè)響起荤牍,我...
    開(kāi)封第一講書(shū)人閱讀 39,660評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎庆冕,沒(méi)想到半個(gè)月后康吵,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,203評(píng)論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡访递,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,287評(píng)論 3 340
  • 正文 我和宋清朗相戀三年晦嵌,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,427評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡耍铜,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出跌前,到底是詐尸還是另有隱情棕兼,我是刑警寧澤,帶...
    沈念sama閱讀 36,122評(píng)論 5 349
  • 正文 年R本政府宣布抵乓,位于F島的核電站伴挚,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏灾炭。R本人自食惡果不足惜茎芋,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,801評(píng)論 3 333
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望蜈出。 院中可真熱鬧田弥,春花似錦、人聲如沸铡原。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 32,272評(píng)論 0 23
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)燕刻。三九已至只泼,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間卵洗,已是汗流浹背请唱。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,393評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留过蹂,地道東北人十绑。 一個(gè)月前我還...
    沈念sama閱讀 48,808評(píng)論 3 376
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像榴啸,于是被迫代替她去往敵國(guó)和親孽惰。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,440評(píng)論 2 359