flutter 产物集成

前言

flutter是一个真正实现了一次编码,多端运行的技术,在面对两个平台需要快速开发迭代,或节省人力成本的情况下,flutter是个很好的选择,特别是初创公司。至于flutter和其他技术的效率对比我不在这里多赘述,因为这篇文章重点不在于此。

但是针对市场上很多公司已有项目,有多少公司敢于将项目重新翻一次flutter,踩坑成本和学习成本都成为不可避免的代价!但我们可以选择部分使用flutter,于是乎,我开始寻找现有项目中加入flutter的解决方案。

想法很好,开始着手研究。我发现大体分为两种:一种是flutter官网的项目依赖,第二种是集成flutter产物。由于项目不一定是同一拨人开发native代码和flutter代码,那就有可能出现项目之间的依赖问题,以及进度,代码管理提交问题。所以我更想去了解后者,产物方式集成。这样可以实现两拨开发人员的独立性,也可以将产物发布到服务器,使用gradle来集成,那就是后话了。

然而,我一顿搜索,在我查找了官网、百度、简书、stackoverflow等等可以想到的搜索途径后,我发现没有一篇文章比较直观和简洁,总有一些文章会丢失一些关键性信息,或者含糊不清,或者实现方式比较繁琐、麻烦。我终于忍不住要自己整理一篇文章,来记录,也希望能帮到其他朋友。

值得注意的是,我采用了flutter module的方式集成,而不是flutter app,请大家注意!你可以选择使用Android Studio来创建flutter项目的时候选择 flutter module,也可以使用命令行flutter create -t module flutter_module来创建。

先附上github代码参考地址

效果图如下(蓝色导航栏界面为flutter效果):

效果图 I
效果图 II
动图效果

动图被压缩的比较严重,牺牲了画质。因为有朋友说flutter出来的效果比较卡顿,所以主要给大家感受下流畅度(前提是不要拿debug模式来比较,我集成的是release模式产物)。

好了,废话完了,开整!



Android平台

  • 生成aar文件

创建一个"flutter module"项目,直接点击运行会在.android/Flutter/build/outputs/目录下生成 flutter-debug.aar文件。但是,我们需要的是release文件,使用下面的命令可以生成fltter-release.aar文件

flutter build apk

我们将这个flutter-release.aar当作正常aar文件集成到我们的项目里即可


  • 可能遇到的问题

  1. 一般flutter_module项目默认最低版本是16,要求版本不得低于16,视具体情况而定
  2. 在集成的时候,需要加入以下代码到gradle,否则报--min-O 最小版本必须大于Level26
compileOptions {
    sourceCompatibility 1.8
    targetCompatibility 1.8
}


  • Android Native 项目中测试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跳转
  1. 首先要在Application的onCreate中注册
FlutterMain.startInitialization(getApplicationContext());
  1. 创建一个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. 跳转到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实现Flutter调用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平台

  • flutter module项目生成iOS release版本 framework

flutter build ios --release

  • 提取并添加App.frameworkFlutter.framework到项目

App.framework在/.ios/Flutter目录下。我们自己写的flutter代码就在这个framework里,每次修改flutter代码后,更新这个framework即可

Flutter.framework在/.ios/Flutter/engine目录下。flutter的引擎,几乎不需要更换,除非官方有更新。

添加 framework
选择 Create folder references


  • 改造AppDelegate

//
//  AppDelegate.h
//  TestFlutter
//
//  Created by 燕文强 on 2019/4/17.
//  Copyright © 2019 燕文强. All rights reserved.
//

#import <UIKit/UIKit.h>
#import <Flutter/Flutter.h>

@interface AppDelegate : FlutterAppDelegate <UIApplicationDelegate, FlutterAppLifeCycleProvider>

@end

//
//  AppDelegate.m
//  TestFlutter
//
//  Created by 燕文强 on 2019/4/17.
//  Copyright © 2019 燕文强. 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
  • 创建FlutterModuleViewController

//
//  FlutterModuleViewController.h
//  TestFlutter
//
//  Created by 燕文强 on 2019/4/17.
//  Copyright © 2019 燕文强. All rights reserved.
//

#import <UIKit/UIKit.h>
#import <Flutter/Flutter.h>

@interface FlutterModuleViewController : FlutterViewController

@end

//
//  FlutterModuleViewController.m
//  TestFlutter
//
//  Created by 燕文强 on 2019/4/17.
//  Copyright © 2019 燕文强. All rights reserved.
//

#import "FlutterModuleViewController.h"

@interface FlutterModuleViewController ()

@end

@implementation FlutterModuleViewController

- (void)viewWillAppear:(BOOL)animated{
    // 页面进入后隐藏自带导航栏
    [self.navigationController setNavigationBarHidden:YES animated:YES];
    [super viewWillAppear:animated];
    
    // 支持侧滑返回
    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 重写splashScreenView来处理Native跳转到Flutter页面时,会出现LaunchScreen
    CGRect rect = [UIScreen mainScreen].applicationFrame;
    UIView *view = [[UIView alloc]initWithFrame:rect];
    view.backgroundColor=UIColor.redColor;
    
    return view;
}

@end
  • 跳转到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];
}


  • 运行时记得关闭BitCode


祝你flutter玩的开心!







最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,222评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,455评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,720评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,568评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,696评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,879评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,028评论 3 409
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,773评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,220评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,550评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,697评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,360评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,002评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,782评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,010评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,433评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,587评论 2 350