irpas技术客

Flutter混合开发、安卓,ios_你的益达啊_flutter ios android

irpas 8029

date:12.29,for flutter >v1.12.x

更新说明:

适配Flutter >=v1.12.x????

在Flutter的应用场景中,有时候一个APP只有部分页面是由Flutter实现的,比如:我们常用的闲鱼App,它宝贝详情页面是由Flutter实现的,这种开发模式被称为混合开发。

混合开发的一些其他应用场景:

在原有项目中加入Flutter页面,在Flutter项目中加入原生页面

原生页面中嵌入Flutter模块

Flutter页面中嵌入原生模块

以上这些都属于Flutter混合开发的范畴,那么如何进行Flutter混合开发呢?

Flutter混合开发的教程我们分为上下两篇,上篇主要介绍如何在现有的Android应用上进行Flutter混合开发,下篇主要介绍如何在现有的iOS应用上进行Flutter混合开发。

将Flutter集成到现有的Android应用中需要如下几个主要步骤:

首先,创建Flutter module;

为已存在的Android应用添加Flutter module依赖;

在Java中调用Flutter module;

编写Dart代码;

运行项目;

热重启/重新加载;

调试Dart代码;

发布应用;

升职加薪、迎娶白富美,走向人生巅峰!;

提示:为了能够顺利的进行Flutter混合开发,请确认你的项目结构是这样子的:

1

2

3

4

-?flutter_hybrid

????-?flutter_module

????-?FlutterHybridAndroid?

????-?FlutterHybridiOS

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?的方式(无法自定义插件)

使用复写FlutterActivity?的方式(可以自定义插件)

直接启动一个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)

???????);

???}

});

这种方式虽然简单,但是无法自定义插件,所以无法直接实现Dart和Native之间的通信,如果需要通信的话可以实现下面?介绍的方式。

使用复写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的热重启/重新加载呢:

打开一个模拟器,或连接一个设备到电脑上;

关闭我们的APP,然后运行flutter attach;

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代码

混合开发的模式下,如何更好更高效的调试我们的代码呢,接下来我就跟大家分享一种混合开发模式下高效调试代码的方式:

关闭APP(这步很关键)

点击AndroidStudio的Flutter Attach按钮(需要首先安装Flutter与Dart插件)

启动APP

接下来就可以像调试普通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呢?

第一步:生成Android签名证书

如果你已经有签名证书可以绕过此步骤。 签名APK需要一个证书用于为APP签名,生成签名证书可以Android Studio以可视化的方式生成,也可以使用终端采用命令行的方式生成,需要的可以自行Google这里不再敖述。?

第二步:设置gradle变量

将你的签名证书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密码替换掉 *****。

第三步:在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

...??

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??

???????}??

???}??

}??

...

第四步:签名打包APK

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市场上传我们的应用了。


1.本站遵循行业规范,任何转载的稿件都会明确标注作者和来源;2.本站的原创文章,会注明原创字样,如未注明都非原创,如有侵权请联系删除!;3.作者投稿可能会经我们编辑修改或补充;4.本站不提供任何储存功能只提供收集或者投稿人的网盘链接。

标签: #Flutter #iOS #Android #date1229for