Flutter集成步骤
- 创建Flutter module
- 添加Flutter module依赖
- 在Java/Objective-c中调用Flutter module
- 编写Dart代码
- 运行项目
- 热重启/热加载
- 调试Dart代码
- 发布应用
提示:为了能够顺利的进行Flutter混合开发,请确认你的项目结构是这样子的:
- flutter_hybrid
- flutter_module
- FlutterHybridAndroid
- FlutterHybridiOS
flutter_hybrid下面分别是flutter模块,原生Android模块,与原生iOS模块,并且这三个模块时并列结构。
1. 创建Flutter module
在做混合开发之前我们首先需要创建一个Flutter module。
假如你的Native项目是这样的:xxx/flutter_hybrid/Native项目:
1
2
3
4
5
|
$ cd xxx /flutter_hybrid/
// 创建支持AndroidX的flutter_module
$ flutter create --androidx -t module flutter_module
// 创建不支持AndroidX的flutter_module
$ flutter create -t module flutter_module
|
注意上面命令中添加了 --androidx参数,该参数的作用是创建一个支持AndroidX的flutter模块
所以说,在创建flutter模块前首先要确定你的Android项目是不是支持Android X,通常是由最新Android Studio创建的Android项目都是默认支持Android X的,所以命令中需要添加--androidx参数
上面代码会切换到你的Android/iOS项目的上一级目录,并创建一个flutter模块:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
//flutter_module/
.android
.gitignore
.idea
.ios
.metadata
.packages
build
flutter_module_android.iml
flutter_module.iml
lib
pubspec.lock
pubspec.yaml
README.md
test
|
上面是flutter_module中的文件结构,你会发现它里面包含.android与.ios,这两个文件夹是隐藏文件,也是这个flutter_module宿主工程:
-
.android - flutter_module的Android宿主工程;
-
.ios - flutter_module的iOS宿主工程;
-
lib - flutter_module的Dart部分的代码;
-
pubspec.yaml - flutter_module的项目依赖配置文件;
-
因为宿主工程的存在,我们这个flutter_module在不加额外的配置的情况下是可以独立运行的,通过安装了Flutter与Dart插件的AndroidStudio打开这个flutter_module项目,通过运行按钮是可以直接运行它的。
构建flutter aar(非必须)
如果你需要的话,可以通过如下命令来构建flutter aar:
1
2
|
$ cd .android/
$ . /gradlew flutter:assembleDebug
|
这会在.android/Flutter/build/outputs/aar/中生成一个flutter-debug.aar归档文件。
tag: part1 end
为已存在的Android应用添加Flutter module依赖
接下来我们需要配置我们Android项目的Flutter module依赖,打开我们Android项目的settings.gradle添加如下代码:
1
2
3
4
5
6
7
|
// FlutterHybridAndroid/settings.gradle
include ':app' // 已存在的内容
setBinding( new Binding([gradle: this ])) // new
evaluate( new File( // new
settingsDir.parentFile, // new
'my_flutter/.android/include_flutter.groovy' // new
)) // new
|
setBinding与evaluate允许Flutter模块包括它自己在内的任何Flutter插件,在settings.gradle中以类似: :flutter、package_info、:video_player的方式存在;
添加:flutter依赖
1
2
3
4
5
6
|
//FlutterHybridAndroid/app/build .gradle
...
dependencies {
implementation project( ':flutter' )
...
}
|
在Java中调用Flutter module
至此,我们已经为我们的Android项目添加了Flutter所必须的依赖,接下来我们来看如何在Java中调用Flutter模块:
在Java中调用Flutter模块有两种方式:
直接启动一个FlutterActivity的方式
第一步:AndroidManifest.xml中声明FlutterActivity,添加如下代码:
1
2
3
4
5
6
7
|
//AndroidManifest.xml
<activity
android:name= "io.flutter.embedding.android.FlutterActivity"
android:configChanges= "orientation|keyboardHidden|keyboard|screenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
android:hardwareAccelerated= "true"
android:windowSoftInputMode= "adjustResize"
/>
|
第二步:通过FlutterActivity开启Flutter页面:
1
2
3
4
5
6
7
8
9
10
11
|
findViewById(R.id.jump).setOnClickListener( new View.OnClickListener() {
@Override
public void onClick(View view) {
startActivity(
FlutterActivity
.withNewEngine()
.initialRoute( "route1" )
.build(MainActivity. this )
);
}
});
|
使用复写FlutterActivity 的方式(推荐)
也可以创建一个Actvity然后继承FlutterActivity:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
|
package org.devio.flutter.hybrid;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.util.Log;
import androidx.annotation.NonNull;
import io.flutter.embedding.android.FlutterActivity;
public class FlutterAppActivity extends FlutterActivity{
public final static String INIT_PARAMS = "initParams" ;
private String initParams;
public static void start(Context context, String initParams) {
Intent intent = new Intent(context, FlutterAppActivity. class );
intent.putExtra(INIT_PARAMS, initParams);
context.startActivity(intent);
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super .onCreate(savedInstanceState);
initParams = getIntent().getStringExtra(INIT_PARAMS);
}
/**
* 重载该方法来传递初始化参数
*
* @return
*/
@NonNull
@Override
public String getInitialRoute() {
return initParams == null ? super .getInitialRoute() : initParams;
}
}
|
上面我们使用字符串“initParams”来告诉Dart代码在Flutter视图中显示哪个小部件。 Flutter模块项目的lib/main.dart文件需要通过window.defaultRouteName来获取Native指定要显示的路由名,以确定要创建哪个窗口小部件并传递给runApp:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
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),
);
}
}
|
调用Flutter module时传递数据
在上文中,我们无论是通过第一种方式还是通过第二种的方式,都允许我们在加载Flutter module时传递一个String类型的initialRoute参数,从参数名字它是用作路由名的,但是既然Flutter给我们开了这个口子,那我们是不是可以搞点事情啊,传递点我们想传的其他参数呢,比如:
1
2
3
4
5
6
|
startActivity(
FlutterActivity
.withNewEngine()
.initialRoute( "{name:'devio',dataList:['aa','bb',''cc]}" )
.build(MainActivity. this )
);
|
然后在Flutter module通过如下方式获取:
1
2
3
4
5
|
import 'dart:ui' ; // 要使用window对象必须引入
String initParams=window.defaultRouteName;
// 序列化成Dart obj 敢你想干的
...
|
通过上述方案的讲解是不是给大家分享了一个新的思路呢。
优化打开Flutter时的启动速度:
新版Flutter支持通过预加载Flutter引擎的方式来提升Flutter模块的打开速度,怎么做呢?
第一步:创建一个Application然后添加:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
public class MyApplication extends Application {
public static final String CACHED_ENGINE_ID = "MY_CACHED_ENGINE_ID" ;
@Override
public void onCreate() {
super .onCreate();
//在MyApplication中预先初始化Flutter引擎以提升Flutter页面打开速度
FlutterEngine flutterEngine = new FlutterEngine( this );
// Start executing Dart code to pre-warm the FlutterEngine.
flutterEngine.getDartExecutor().executeDartEntrypoint(DartExecutor.DartEntrypoint.createDefault());
// Cache the FlutterEngine to be used by FlutterActivity.
FlutterEngineCache.getInstance().put(CACHED_ENGINE_ID, flutterEngine);
}
}
|
上述代码在MyApplication中初始化了一个id为CACHED_ENGINE_ID 的FlutterEngine,然后我们在需要打开Flutter模块时告诉Flutter我们缓存的id即可:
方式一:
1
2
3
4
5
|
startActivity(
FlutterActivity
.withCachedEngine( "my_engine_id" )
.build(currentActivity)
);
|
方式二:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
|
package org.devio.flutter.hybrid;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import androidx.annotation.NonNull;
import io.flutter.embedding.android.FlutterActivity;
import static org.devio.flutter.hybrid.MyApplication.CACHED_ENGINE_ID;
public class FlutterAppActivity extends FlutterActivity {
public final static String INIT_PARAMS = "initParams" ;
private String initParams;
public static void start(Context context, String initParams) {
Intent intent = new Intent(context, FlutterAppActivity. class );
intent.putExtra(INIT_PARAMS, initParams);
context.startActivity(intent);
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super .onCreate(savedInstanceState);
initParams = getIntent().getStringExtra(INIT_PARAMS);
}
//使用在MyApplication预先初始化好的Flutter引擎以提升Flutter页面打开速度,注意:在这种模式下回导致getInitialRoute 不被调用所以无法设置初始化参数
@Override
public String getCachedEngineId() {
return CACHED_ENGINE_ID;
}
/**
* 重载该方法来传递初始化参数
* @return
*/
@NonNull
@Override
public String getInitialRoute() {
return initParams == null ? super .getInitialRoute() : initParams;
}
}
|
编写Dart代码
接下来就是在编写Flutter module中的lib下编写Dart带了,快去Enjoy Coding吧!!!
运行项目
接下来,我们就可以运行它了,经过上述步骤,我们就可以以运行普通Android项目的方式来通过AndroidStudio运行一个集成了Flutter的Android项目了。

热重启/重新加载:

大家知道我们在做Flutter开发的时候,它带有热重启/重新加载的功能,但是你可能会发现,混合开发中在Android项目中集成了Flutter项目,Flutter的热重启/重新加载功能好像失效了,那怎么启用混合开发汇总Flutter的热重启/重新加载呢:
1
2
3
|
$ cd flutter_hybrid /flutter_module
$ flutter attach
Waiting for a connection from Flutter on Nexus 5X...
|
注意如果,你同时有多个模拟器或连接的设备,运行flutter attach会提示你选择一个设备:
1
2
|
Android SDK built for x86 • emulator-5554 • android-x86 • Android 8.1.0 (API 27) (emulator)
iPhone X • 3E3FA943-715F-482F-B003-D46F5902C56C • ios • iOS 12.1 (simulator)
|
接下来我们需要flutter attach -d来指定一个设备:
1
|
flutter attach -d 'emulator-5554'
|
注意-d后面跟的设备ID。
运行APP,然后你会看到:
1
2
3
4
5
6
7
8
9
10
11
12
13
|
$ flutter attach
More than one device connected; please specify a device with the '-d <deviceId>' flag, or use '-d all' to act on all devices.
Android SDK built for x86 • emulator-5554 • android-x86 • Android 8.1.0 (API 27) (emulator)
iPhone X • 3E3FA943-715F-482F-B003-D46F5902C56C • ios • iOS 12.1 (simulator)
jphdeMacBook-Pro:flutter_module jph$ flutter attach -d 'emulator-5554'
Waiting for a connection from Flutter on Android SDK built for x86...
Done.
Syncing files to device Android SDK built for x86... 1,744ms
? To hot reload changes while running, press "r" . To hot restart (and rebuild state), press "R" .
An Observatory debugger and profiler on Android SDK built for x86 is available at: http: //127 .0.0.1:60324/
For a more detailed help message, press "h" . To detach, press "d" ; to quit, press "q" .
|
说明连接成功了,接下来就可以通过上面的提示来进行热加载/热重启了,在终端输入:
-
r : 热加载;
-
R : 热重启;
-
h : 获取帮助;
-
d : 断开连接;
调试Dart代码
混合开发的模式下,如何更好更高效的调试我们的代码呢,接下来我就跟大家分享一种混合开发模式下高效调试代码的方式:

接下来就可以像调试普通Flutter项目一样来调试混合开发的模式下的Dart代码了。
除了以上步骤不同之外,接下来的调试和我们之前课程中的Flutter调试技巧都是通用的,需要的同学可以学习下我们前面的课程;
还有一点需要注意:
大家在运行Android工程时一定要在Android模式下的AndroidStudio中运行,因为Flutter模式下的AndroidStudio运行的是Flutter module下的.android中的Android工程。
发布应用
打包
用Flutter开发好APP之后,如何将APP发布以供用户使用呢?一款APP的发布流程无外乎:签名打包—>发布到各store这两大步骤。下面的课程将向大家分享如何签名打包一款Flutter APP。
众所周知,Android要求所有的APP都需要进行数字签名后,才能够被安装到相应的设备上。签名打包一个Android APP已经是每一位Android开发者的家常便饭了。
那么如何签名打包一款用Flutter开发的APP呢?
如果你已经有签名证书可以绕过此步骤。
签名APK需要一个证书用于为APP签名,生成签名证书可以Android Studio以可视化的方式生成,也可以使用终端采用命令行的方式生成,需要的可以自行Google这里不再敖述。
将你的签名证书copy到 android/app目录下。
编辑~/.gradle/gradle.properties或../android/gradle.properties(一个是全局gradle.properties,一个是项目中的gradle.properties,大家可以根据需要进行修改) ,加入如下代码:
1
2
3
4
|
MYAPP_RELEASE_STORE_FILE=your keystore filename
MYAPP_RELEASE_KEY_ALIAS=your keystore alias
MYAPP_RELEASE_STORE_PASSWORD=*****
MYAPP_RELEASE_KEY_PASSWORD=*****
|
提示:用正确的证书密码、alias以及key密码替换掉 *****。
编辑 android/app/build.gradle文件添加如下代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
...
android {
...
defaultConfig { ... }
signingConfigs {
release {
storeFile file (MYAPP_RELEASE_STORE_FILE)
storePassword MYAPP_RELEASE_STORE_PASSWORD
keyAlias MYAPP_RELEASE_KEY_ALIAS
keyPassword MYAPP_RELEASE_KEY_PASSWORD
}
}
buildTypes {
release {
...
signingConfig signingConfigs.release
}
}
}
...
|
terminal进入项目下的android目录,运行如下代码:
1
|
. /gradlew assembleRelease
|
签名打包成功后你会在 "android/app/build/outputs/apk/"目录下看到签名成功后的app-release.apk文件。
提示:如果你需要对apk进行混淆打包 编辑android/app/build.gradle:
1
2
3
4
|
/**
* Run Proguard to shrink the Java bytecode in release builds.
*/
def enableProguardInReleaseBuilds = true
|
如何在gradle中不使用明文密码?
上文中直接将证书密码以明文的形式写在了gradle.properties文件中,虽然可以将此文件排除在版本控制之外,但也无法保证密码的安全,下面将向大家分享一种方法避免在gradle中直接使用明文密码。
通过“钥匙串访问(Keychain Access)”工具保护密码安全
下面阐述的方法只在OS X上可行。
我们可以通过将发布证书密码委托在“钥匙串访问(Keychain Access)”工具中,然后通过gradle访问“钥匙串访问”工具来获取证书密码。
具体步骤:
-
cmd+space打开“钥匙串访问(Keychain Access)”工具。
-
在登录选项中新钥匙串,如图:

提示: 你可以在terminal中运行如下命令检查新建的钥匙串是否成功。
1
|
security find -generic-password -s android_keystore -w
|
3. 在build.gradle中访问你的秘钥串,将下列代码编辑到android/app/build.gradle中:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
|
def getPassword(String currentUser, String keyChain) {
def stdout = new ByteArrayOutputStream()
def stderr = new ByteArrayOutputStream()
exec {
commandLine 'security' , '-q' , 'find-generic-password' , '-a' , currentUser, '-s' , keyChain, '-w'
standardOutput = stdout
errorOutput = stderr
ignoreExitValue true
}
//noinspection GroovyAssignabilityCheck
stdout.toString().trim()
}
// Add this line
def pass = getPassword( "YOUR_USER_NAME" , "android_keystore" )
...
android {
...
defaultConfig { ... }
signingConfigs {
release {
storeFile file (MYAPP_RELEASE_STORE_FILE)
storePassword pass // Change this
keyAlias MYAPP_RELEASE_KEY_ALIAS
keyPassword pass // Change this
}
}
buildTypes {
release {
...
signingConfig signingConfigs.release
}
}
}
...
|
注意事项
钥匙串访问(Keychain Access)工具只是帮我们托管了,证书密码,证书明和alias还是需要我们在gradle.properties中设置一下的。
发布应用
通过上述步骤我们完成了将Flutter代码打包,并生成了APK,并放到了assets目录下,接下来我们就可在各大Android市场上传我们的应用了。