Flutter混合开发项目搭建

官方提供的集成步骤详见:
https://github.com/flutter/flutter/wiki/Add-Flutter-to-existing-apps
目前混合开发的集成方案还是preview版本,所以实际使用过程中,容易碰到各种问题,还不够稳定。
本文配置基于:
1.7.8-hotfix-4
官方文档只能用于master分支,所以会有一些细节差异。

创建Flutter工程

假设当前目录结构如下,ios和android工程在同一目录下:

some/path/
  - android_project/
  - ios_project/

进入some/path/目录,创建Flutter module:

flutter create -t module --org com.example my_flutter

执行完后,目录结构如下:

some/path/
  - android_project/
  - ios_project/
  - my_flutter

my_flutter会生成隐藏的.android和.ios文件夹,注意尽量不要在这两个隐藏文件夹里面添加自己的代码,在pubspec.yaml更新时,这两个文件夹会被重新覆盖,导致后来添加的文件丢失

配置Android工程

修改原生工程android_project下的app module中的build.gradle,在android{}配置中添加一下内容

android {
  //...
  compileOptions {
    sourceCompatibility 1.8
    targetCompatibility 1.8
  }
}

依赖Flutter Module工程的方式有两种:依赖aar与直接依赖源码

依赖aar

$ cd some/path/my_flutter
$ flutter build aar

注意:build aar命令到1.7.8-hotfix-4还不支持,master分支已经更新。
在flutter module工程,使用flutter build命令构建出一个aar,然后android_project就去配置依赖这个aar,这样做的优点是android_project项目编译的时候不需要去依赖Flutter SDK,
直接引用aar即可,缺点是开发的时候,没法一键运行。

依赖源码

依赖源码,可以一键运行,不过android_project项目编译的时候需要去依赖Flutter SDK。
android_project项目在setting.gradle添加以下代码

include ':app'                                     // assumed existing content
setBinding(new Binding([gradle: this]))                                 // new
evaluate(new File(                                                      // new
  settingsDir.parentFile,                                               // new
  'my_flutter/.android/include_flutter.groovy'                          // new
))     

目录结构如下:


0bc3eb99-face-4ab8-af0b-de3f3a4028e3.png

Flutter工程以及在pubspec.yaml引用的library,就能作为一个project被引入,最后在build.gradle添加依赖Flutter工程:

dependencies {
  implementation project(':flutter')
}

调起Flutter界面

Android端

1、使用FlutterView

// MyApp/app/src/main/java/some/package/MainActivity.java
View flutterView = Flutter.createView(
        MainActivity.this,
        getLifecycle(),
        "route1"
);
FrameLayout.LayoutParams layout = new FrameLayout.LayoutParams(600, 800);
layout.leftMargin = 100;
layout.topMargin = 200;
addContentView(flutterView, layout);

2、使用FlutterFragment

// MyApp/app/src/main/java/some/package/MainActivity.java
FragmentTransaction tx = getSupportFragmentManager().beginTransaction();
tx.replace(R.id.someContainer, Flutter.createFragment("route1"));
tx.commit();

3、使用FlutterActivity

public class MainActivity extends FlutterActivity {
  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    GeneratedPluginRegistrant.registerWith(this);
  }
}

FlutterActivity可以通过intent指定routeName

intent.putExtra("route", "initRoute");

Flutter端

import 'dart:ui';
import 'package:flutter/material.dart';

void main() => runApp(_widgetForRoute(window.defaultRouteName));

Widget _widgetForRoute(String route) {
  switch (route) {
    case 'route1':
      return SomeWidget(...);
    case 'route2':
      return SomeOtherWidget(...);
    default:
      return Center(
        child: Text('Unknown route: $route', textDirection: TextDirection.ltr),
      );
  }
}

创建FlutterView或者FlutterFragment时,传入了一个initialRoute,也就是window.defaultRouteName的值,这样我们就能够根据routeName来决定跳转到哪个Flutter页面。

配置ios工程

集成Flutter module工程到flutter_host_ios工程需要Cocoapods依赖项管理器,请确保本地安装了cocoapods,如果未安装,可以参考:cocoapods.org/
1、如果ios_project工程中已经使用了cocoapods,将下列配置添加到工程的Podfile文件中:

flutter_application_path = 'path/to/my_flutter/'
load File.join(flutter_application_path, '.ios', 'Flutter', 'podhelper.rb')

2、找到项目TARGETS的Build Phases,点击左上角+号选择New Run Script Phase添加Run Script,在Shell字段下添加下面两行脚本

"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh" build
"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh" embed

这段跟官方文档有差异,如果是master分支,参考官方文档。

2、执行pod install
每次pubspec.yaml依赖有更新时,都需要重新执行pod install

3、因为Flutter现在不支持bitcode,需要禁用项目TARGETS的Build Settings-> Build Options-> Enable Bitcode部分中的ENABLE_BITCODE标志。

调起Flutter界面

在ios_project中,将AppDelegate改为继承FlutterAppDelegate。
AppDelegate.h

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

@interface AppDelegate : FlutterAppDelegate
@property (nonatomic,strong) FlutterEngine *flutterEngine;
@end

AppDelegate.m

#import <FlutterPluginRegistrant/GeneratedPluginRegistrant.h> // Only if you have Flutter Plugins

#include "AppDelegate.h"

@implementation AppDelegate

// This override can be omitted if you do not have any Flutter Plugins.
- (BOOL)application:(UIApplication *)application
    didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
  self.flutterEngine = [[FlutterEngine alloc] initWithName:@"io.flutter" project:nil];
  [self.flutterEngine runWithEntrypoint:nil];
  [GeneratedPluginRegistrant registerWithRegistry:self.flutterEngine];
  return [super application:application didFinishLaunchingWithOptions:launchOptions];
}

@end

ViewController.m

#import <Flutter/Flutter.h>
#import "AppDelegate.h"
#import "ViewController.h"

@implementation ViewController
- (void)viewDidLoad {
    [super viewDidLoad];
    UIButton *button = [UIButton buttonWithType:UIButtonTypeCustom];
    [button addTarget:self
               action:@selector(handleButtonAction)
     forControlEvents:UIControlEventTouchUpInside];
    [button setTitle:@"Press me" forState:UIControlStateNormal];
    [button setBackgroundColor:[UIColor blueColor]];
    button.frame = CGRectMake(80.0, 210.0, 160.0, 40.0);
    [self.view addSubview:button];
}

- (void)handleButtonAction {
    FlutterEngine *flutterEngine = [(AppDelegate *)[[UIApplication sharedApplication] delegate] flutterEngine];
    FlutterViewController *flutterViewController = [[FlutterViewController alloc] initWithEngine:flutterEngine nibName:nil bundle:nil];
    [self presentViewController:flutterViewController animated:false completion:nil];
}
@end

与Android一样,FlutterViewController也可以设置routeName

[flutterViewController setInitialRoute:@"route1"];

但是,这个api目前基本是无法派上用场,上述例子共用了FlutterEngine,在runWithEntrypoint执行之后,main.dart中runApp代码已经执行,后面再去setInitialRoute已经没有效果,FlutterViewController使用initWithNibName等其他方式初始化,不共用FlutterEngine,main.dart还没执行,这种方式setInitialRoute能够起作用,不过跳转到Flutter页面时,会展示launch页面,体验不好。

热重载

在宿主app进程运行以后,进入my_flutter,执行:

$ cd some/path/my_flutter
$ flutter attach
Waiting for a connection from Flutter on Nexus 5X...

attach成功之后,就可以随时修改flutter代码,进行热重载更新。

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

推荐阅读更多精彩内容