Flutter 即学即用系列博客——03 在旧有项目引入 Flutter
前言
其实如果打算在实际项目中引入 Flutter,完全将旧有项目改造成纯 Flutter 项目的可能性比较小,更多的是在旧有项目引入 Flutter。
因此本篇我们就说一说如何在旧有项目引入 Flutter。
官方 WIKI 有说明,但是里面坑还是不少的,变化也是存在的。
因此就让我们来看一看。
目录
1. 按照官网实现基本引入
上面为GitHub WIKI 的引入方式,通过 Module 的形式进行引入。
可以看出文档还是在不断更新的。
下面我们说下具体的步骤:
第一步:创建 Flutter Module
假设已经存在的 Android 项目路径为 /Users/nesger/Desktop/nesger_folder/project/studio/MyApp,那么我们在同级目录下面创建 Flutter Module。在终端执行如下命令:
cd /Users/nesger/Desktop/nesger_folder/project/studio/
flutter create -t module my_flutter
执行命令之后,就创建了一个带有 dart 代码的 Flutter Module,并且能够看到一个隐藏的文件夹 .android。
第二步:让主 APP 依赖 Flutter Module
这里,主 APP 指的就是 Android 项目 MyApp。
在 MyApp 的 settings.gradle 添加下面代码:
setBinding(new Binding([gradle: this]))
evaluate(new File(
settingsDir.parentFile,
'my_flutter/.android/include_flutter.groovy'
))
在需要使用 Flutter Module 的 MyApp 的对应 Module 添加依赖,比如本例子中就是到 MyApp 中的 app 的 build.gradle 添加
dependencies {
implementation project(':flutter')
}
添加完之后有个报错如下:
Manifest merger failed : uses-sdk:minSdkVersion 15 cannot be smaller than version 16 declared in library [:flutter] /Users/nesger/Desktop/nesger_folder/project/studio/my_flutter/.android/Flutter/build/intermediates/merged_manifests/debug/processDebugManifest/merged/AndroidManifest.xml as the library might be using APIs not available in 15
Suggestion: use a compatible library with a minSdk of at most 15,
or increase this project's minSdk version to at least 16,
or use tools:overrideLibrary="com.nesger.myflutter" to force usage (may lead to runtime failures)
从这里可以看到是由于我们 MyApp 的 uses-sdk:minSdkVersion 与 Flutter Module 的不一致。
控制台也给出了解决方法,我们这里简单的升下我们 MyApp 的 uses-sdk:minSdkVersion 即可。
改完编译就没问题了。
第三步:使用 Flutter Module 提供的 API 在主 APP 中创建 FlutterView
我们的主界面布局如下,就是有一个按钮而已。
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
>
<Button
android:onClick="onClick"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:text="create flutter view"
/>
</RelativeLayout>
然后在代码里面对应位置添加如下代码:
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);
运行到手机上面,可以看到下面效果:
点击按钮之后,可以看到 Flutter 页面显示出来了
到这里我们基本就实现了在旧有项目引入 Flutter 了。
那么上面的代码有个地方,就是"route1"到底是什么呢?
顾名思义,你可以认为是一个路由。也就是用来区分不同 Flutter 页面的。
假设你的 Flutter 有多个页面,那么你如何确定要加载哪个页面呢?就可以通过这个来区分。
所以在 Flutter Module 的 main.dart 文件里面,对于存在多个页面的情况,我们可以写下面的模板代码:
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),
);
}
}
这段代码我们可以重点关注 switch 那一块代码。这里会根据不同的路由,返回不同的页面。
第四步:热重载和调试 dart 代码
首先定位到 Flutter Module 路径,这里为/Users/nesger/Desktop/nesger_folder/project/studio/my_flutter。
接着执行命令flutter attach,会看到控制台输出
Waiting for a connection from Flutter on SM G9350...
然后我们直接运行或者以 debug 模式运行项目。
接着点击按钮,触发 Flutter 代码,会看到控制台输出
Done.
Syncing files to device SM G9350... 1.2sTo hot reload changes while running, press "r". To hot restart (and rebuild state), press "R".
An Observatory debugger and profiler on SM G9350 is available at: http://127.0.0.1:53562/
For a more detailed help message, press "h". To detach, press "d"; to quit, press "q".
这个跟我们之前讲到的热重载类似,这里就不重复了。
除了直接运行旧有项目来启动 Flutter 之外,其实更多时候我们编写 Flutter 是独立的,可以直接运行 Flutter 来调试和修改 dart 代码。
我一般倾向于直接执行 flutter run,而不是按照官网那样通过 flutter attach,然后以 debug 模型启动旧有项目。
等到 Flutter Module 都调试 OK 之后,再和旧有项目一起运行查看效果。
2. 修改配置允许 Flutter Module 在任意位置
大家可以看到,官网的例子的 Flutter Module 是在与 Android 原项目同层级的目录下面创建的。
这样其实对于我们开发不是很方便。
首先,我们需要在 Android Studio 分别打开两个项目,这样不方便修改和调试 dart 代码。
其次,一般在公司里面,项目都是用 git 之类的项目管理工具来管理的。如果按照官网的例子,其他开发者下载原项目的代码之后还需要额外下载 Flutter 代码仓库。
所以其实更多的情况,我们希望 Flutter Module 是在我们主项目下面当成主项目的代码来使用,这样不仅方便修改和调试,而且其他开发者也不需要进行额外处理。
简单回顾一下上面的引入步骤:
1.创建 Module
2.修改项目的 settings.gradle
3.添加 flutter module 依赖
其中重点需要关注的就是 2 了。因为 2 里面指定的一个文件是跟路径相关的。
我们在 MyApp 项目下面创建 sub 文件夹,移动之前的 module 到 sub 文件夹下面。
执行下面命令:(确保当前在 MyApp 项目下面)
mkdir sub
cd sub/
mv ../../my_flutter .
执行完之后 module 的位置就变化了。你会发现代码里面 Flutter 相关代码和包都报错了。clean 一下,会有报错:
java.io.FileNotFoundException: /Users/nesger/Desktop/nesger_folder/project/studio/my_flutter/.android/include_flutter.groovy
提示文件找不到。
这是必然的,因为我们刚刚迁移了 flutter module 的位置。
所以说要允许 Flutter Module 配置在任意位置,重点就是第二步项目的 settings.gradle 的配置了。或者说 include_flutter.groovy 文件的位置是否指定正确。
我们看下配置信息:
include ':app'
setBinding(new Binding([gradle: this]))
evaluate(new File(
settingsDir.parentFile,
'my_flutter/.android/include_flutter.groovy'
))
new File(settingsDir.parentFile,'my_flutter/.android/include_flutter.groovy' ) 解读下这句话的意思就是指定 include_flutter.groovy 的所在位置。这里的意思是在 settings 文件所在目录(settingsDir)的父目录有个文件(settingsDir.parentFile)my_flutter/.android/include_flutter.groovy。看下下面的文件放置位置图就清楚了:
所以官网在跟项目同级创建 flutter module 是没问题的。但是我们现在改了,应该怎样设置呢?
上下图,然后大家考虑一下答案,再往下翻,相信聪明的你一定知道,改法有多种,下面提供一下几种方案。
Tips:注意相对路径的使用,重点是找到 include_flutter.groovy
解法一:(推荐)
include ':app'
setBinding(new Binding([gradle: this]))
evaluate(new File(
settingsDir,
'sub/my_flutter/.android/include_flutter.groovy'
))
在 settings 所在目录有 sub/my_flutter/.android/include_flutter.groovy 文件
解法二:
include ':app'
setBinding(new Binding([gradle: this]))
evaluate(new File(
settingsDir.parentFile,
'MyApp/sub/my_flutter/.android/include_flutter.groovy'
))
在 settings 所在目录的父目录有 MyApp/sub/my_flutter/.android/include_flutter.groovy 文件
有了上面图文并茂的讲解加上一个实际的 Sample,相信不管 flutter module 放在哪里你到可以关联到了。
3. 引入自己项目报错处理方法
我们新建一个 Android 项目然后按照上述导入可以正常运行。
然而,理想很丰满,现实很骨感,本人在导入到实际工程项目时,一运行到 Flutter 相关代码,控制台就报出下面信息,并且 APP crash。
2019-02-15 09:35:00.355 4366-4366/? A/DEBUG: *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
2019-02-15 09:35:00.355 4366-4366/? A/DEBUG: Build fingerprint: 'samsung/hero2qltezc/hero2qltechn:8.0.0/R16NW/G9350ZCS3CRJ2:user/release-keys'
2019-02-15 09:35:00.355 4366-4366/? A/DEBUG: Revision: '15'
2019-02-15 09:35:00.355 4366-4366/? A/DEBUG: ABI: 'arm'
2019-02-15 09:35:00.355 4366-4366/? A/DEBUG: pid: 3072, tid: 3072, name: pkgname >>> pkgname <<<
2019-02-15 09:35:00.355 4366-4366/? A/DEBUG: signal 6 (SIGABRT), code -6 (SI_TKILL), fault addr --------
2019-02-15 09:35:00.360 4366-4366/? A/DEBUG: Abort message: '[FATAL:flutter/shell/common/shell.cc(212)] Check failed: vm. Must be able to initialize the VM.
'
2019-02-15 09:35:00.360 4366-4366/? A/DEBUG: r0 00000000 r1 00000c00 r2 00000006 r3 00000008
2019-02-15 09:35:00.360 4366-4366/? A/DEBUG: r4 00000c00 r5 00000c00 r6 fff50940 r7 0000010c
2019-02-15 09:35:00.360 4366-4366/? A/DEBUG: r8 00000000 r9 fff50d04 sl d74ec880 fp fff51048
2019-02-15 09:35:00.361 4366-4366/? A/DEBUG: ip 00000000 sp fff50930 lr e9ebea17 pc e9eefb74 cpsr 200f0010
2019-02-15 09:35:00.365 4366-4366/? A/DEBUG: backtrace:
2019-02-15 09:35:00.365 4366-4366/? A/DEBUG: #00 pc 0004bb74 /system/lib/libc.so (tgkill+12)
2019-02-15 09:35:00.365 4366-4366/? A/DEBUG: #01 pc 0001aa13 /system/lib/libc.so (abort+54)
2019-02-15 09:35:00.365 4366-4366/? A/DEBUG: #02 pc 0053ea03 /data/app/pkgname-nNNvK7M4bKRp1ys0OFeS7g==/lib/arm/libflutter.so (offset 0x4e5000)
2019-02-15 09:35:00.365 4366-4366/? A/DEBUG: #03 pc 00536ba3 /data/app/pkgname-nNNvK7M4bKRp1ys0OFeS7g==/lib/arm/libflutter.so (offset 0x4e5000)
2019-02-15 09:35:00.365 4366-4366/? A/DEBUG: #04 pc 0005d295 /data/app/pkgname-nNNvK7M4bKRp1ys0OFeS7g==/oat/arm/base.odex (offset 0x49000)
其中 pkgname 是实际项目包名,这里做了替换
此刻的心情见下图:
在经过了搜索引擎的搜索和 GitHub 上面 flutter 的相关 Issues 阅读,最终得出了解决方案。
通用解决步骤:
- 本项目执行清理命令。./gradlew clean
- 进入 flutter module 项目执行清理命令。flutter packages get;flutter clean
- 进入 flutter module 的 .android 项目执行清理命令和打包操作。./gradlew clean;./gradlew assemble
- 回到本项目执行打包命令。./gradlew assemble
通过实际例子来加深认识吧。还是以我们上面的 MyApp 为例进行说明。module 现在是在 MyApp 下面的 sub 目录下面。
那么我们直接在 terminal 执行下面命令即可:
./gradlew clean;cd sub/my_flutter/;flutter packages get;flutter clean;cd -;cd sub/my_flutter/.android/;./gradlew clean;./gradlew assemble;cd -;./gradlew assemble
分号分隔了每条命令,总结起来就是
清理项目;进入flutter module;更新包信息和清理;返回当前目录;进入flutter module .android 项目目录;清理打包;返回当前目录;打包
后续假设你 flutter module 没有更新过。那么以后修改本地项目之后,就直接执行./gradlew assemble。
切记不要执行 clean 或者 rebuild 。也不要点击 IDE 运行按钮。因为 IDE 运行按钮会默认先 clean。
当然上面的 assemble 命令学习 Android 的都懂,就是打出所有安装包。如果你只要 debug 包,可以改为 assembleDebug。
另外如果你要安装到设备,可以改为 installDebug。
这里就不展开了。
这里先留个悬念,打出的 debug 包可以用,但是 release 包依然会 crash。原因在后面混淆文章我们再讲。
4.推荐集成管理方式
我们知道,一般公司对于项目都有对应的管理工具。
这里假设项目是通过 GitLab 进行管理的。
那么我们要如何集成呢?
以上面为例子,假设 MyApp 项目下面有 sub 子目录,子目录下面创建了 my_flutter 模块。
因为 my_flutter 模块是跨平台使用的,除了 Android 端,iOS 端也要用。因此大概率会放到 GitLab 仓库上面。
所以如何来保证你本地的 my_flutter 是最新的,同时你做的修改能够同步到 MyApp GitLab 同时又同步到 my_flutter GitLab 呢?
这边推荐使用 git subtree 来管理。
涉及代码仓库公用的都推荐 git subtree 来管理。
如何使用呢?(以我们上面的例子来说明)
1)在主项目仓库新增子仓库。
git subtree add --prefix=sub/my_flutter 子仓库git地址 master --squash
(--squash参数表示不拉取历史信息,而只生成一条commit信息。)
上面的子仓库git地址指的是 my_flutter 所放的地址。
接下来执行git status可以看到有 commit 记录。
然后可以执行git push命令将新创建的子仓库推送到 MyApp 的代码仓库中。
2)拉取子仓库更新
使用git subtree pull命令。
比如这里 my_flutter 更新了,使用如下命令拉取:
git subtree pull --prefix=sub/my_flutter 子仓库git地址 master --squash
表示从 master 分支拉取更新。如果你想从 develop 或者其他分支拉取更新,则做对应修改即可。
3)推送更新到子仓库
使用git subtree push命令。
比如这里本地 my_flutter 修改了,使用如下命令推送:
git subtree push --prefix=sub/my_flutter 子仓库git地址 develop
表示将更新推送到 develop 分支。如果你想推送到其他分支,则将 develop 改为对应推送分支名即可。
4)简化 git subtree 命令
大家可以看到上面的命令中子仓库 git 地址比较固定而且每个命令都有用到。
并且相对比较长,比如 https://github.com/nesger/FlutterNote.git 这个。
因此,我们可以给这个起个 alias(别名)。
举个例子,假设上面的子仓库git地址为 https://github.com/nesger/FlutterNote.git,那么我们可以执行如下操作:
git remote add -f my_flutter https://github.com/nesger/FlutterNote.git
这样上面的原命令
git subtree add --prefix=sub/my_flutter https://github.com/nesger/FlutterNote.git master --squash
git subtree pull --prefix=sub/my_flutter https://github.com/nesger/FlutterNote.git master --squash
git subtree push --prefix=sub/my_flutter https://github.com/nesger/FlutterNote.git develop
可以对应修改为:
git subtree add --prefix=sub/my_flutter my_flutter master --squash
git subtree pull --prefix=sub/my_flutter my_flutter master --squash
git subtree push --prefix=sub/my_flutter my_flutter develop
可以看到命令简化了很多。尤其这个命令使用比较频繁。可以提高效率。
温馨提示:
在使用git subtree pull命令进行子仓库更新之前,需要保证本地没有修改。
什么意思?
就是你在本地执行git status .时提示没有修改的文件。
这个时候你再去拉取才不会拉取失败。否则会有下面提示:
Working tree has modifications. Cannot add.
所以一般 flutter module 有更新后,先推送到主项目仓库,再推送到子仓库。
如果是临时不重要修改,则先 revert 或者将修改文件保存在另外位置。
总之拉取子仓库更新的时候本地不要有修改的文件。
上述git subtree相关命令都是在主项目的目录下面执行的。
更多阅读:
Flutter 即学即用系列博客——01 环境搭建
Flutter 即学即用系列博客——02 一个纯 Flutter Demo 说明
Flutter 即学即用系列博客——03 在旧有项目引入 Flutter的更多相关文章
- Flutter 即学即用系列博客——09 EventChannel 实现原生与 Flutter 通信(一)
前言 紧接着上一篇,这一篇我们讲一下原生怎么给 Flutter 发信号,即原生-> Flutter 还是通过 Flutter 官网的 Example 来讲解. 案例 接着上一次,这一次我们让原生 ...
- Flutter 即学即用系列博客——09 MethodChannel 实现原生与 Flutter 通信(二)
前言 上一篇我们讲解了如何通过 EventChannel 实现 Android -> Flutter 的通信. 并且也看到了 Flutter 内部 EventChannel 源码也是对 Meth ...
- Flutter 即学即用系列博客——05 StatelessWidget vs StatefulWidget
前言 上一篇我们对 Flutter UI 有了一个基本的了解. 这一篇我们通过自定义 Widget 来了解下如何写一个 Widget? 然而 Widget 有两个,StatelessWidget 和 ...
- Flutter 即学即用系列博客——04 Flutter UI 初窥
前面三篇可以算是一个小小的里程碑. 主要是介绍了 Flutter 环境的搭建.如何创建 Flutter 项目以及如何在旧有 Android 项目引入 Flutter. 这一篇我们来学习下 Flutte ...
- Flutter 即学即用系列博客——08 MethodChannel 实现 Flutter 与原生通信
背景 前面我们讲了很多 Flutter 相关的知识点,但是我们并没有介绍怎样实现 Flutter 与原生的通信. 比如我在 Flutter UI 上面点击了一个按钮,我希望原生做一些处理,那么原生怎么 ...
- Flutter 即学即用系列博客——06 超实用 Widget 集锦
本篇文章我们来讲讲一些比较常用的 Widget. 大家验证的时候使用下面的代码替换 main.dart 代码,然后在 //TODO 语句返回下面常用 Widget 示例的代码. import 'pac ...
- Flutter 即学即用系列博客总结篇
前言 迟到的总结篇,其实大家看我之前发的系列博客最后一篇,发文时间是 3 月 29 日.距离现在快两个月了. 主要是因为有很多事情在忙,所以这篇就耽搁了. 今天终于可以跟大家会面了. 系列博客背景 F ...
- Flutter 即学即用系列博客——02 一个纯 Flutter Demo 说明
前言 上一篇文章我们搭建好了 Flutter 的开发环境. Flutter 即学即用--01 环境搭建 这一篇我们通过 Flutter 的一个 Demo 来了解下 Flutter. 开发系统:MAC ...
- Flutter 即学即用系列博客——10 混淆
前言 之前的博客我们都是在 debug 的模式下进行开发的. 实际发布到市场或者给到用户的都是 release 包. 而对于 Android 来说,release 包一个重要的步骤就是混淆. Andr ...
随机推荐
- php与html实现交互的基本操作
今天我们来实现php与html页面注册和登录的效果.中国有句古话叫: 第一步:我们来了解一些php的基本格式. <?php php代码 ?> 第二步:了解php与js的一些基本区别 我们在 ...
- Java基础-抽象类和接口
抽象类与接口是Java语言中对抽象概念进行定义的两种机制,正是由于他们的存在才赋予java强大的面向对象的能力.他们两者之间对抽象概念的支持有很大的相似,甚至可以互换,但是也有区别. 抽象定义: 抽象 ...
- HTML标题
HTML 标题 在 HTML 文档中,标题很重要. HTML 标题 标题(Heading)是通过 <h1> - <h6> 标签进行定义的. <h1> 定义最大的标题 ...
- Tensorflow源码解析1 -- 内核架构和源码结构
1 主流深度学习框架对比 当今的软件开发基本都是分层化和模块化的,应用层开发会基于框架层.比如开发Linux Driver会基于Linux kernel,开发Android app会基于Android ...
- .NetCore WebApi——基于JWT的简单身份认证与授权(Swagger)
上接:.NetCore WebApi——Swagger简单配置 任何项目都有权限这一关键部分.比如我们有许多接口.有的接口允许任何人访问,另有一些接口需要认证身份之后才可以访问:以保证重要数据不会泄露 ...
- 【EXCEL-折线图】百折不挠 | 用EXCEL画出与众不同的折线图(曲线图)
很多熟悉EXLCE的朋友都知道EXCEL在生成统计图表方面的强大功能,我们在写各类总结.报告.方案等文档时常涉及到各类统计数字,将统计数字用图表的形式展示出来,既直观又美观.下面我分享一种不一样的折线 ...
- 使用Http-Repl工具测试ASP.NET Core 2.2中的Web Api项目
今天,Visual Studio中没有内置工具来测试WEB API.使用浏览器,只能测试http GET请求.您需要使用Postman,SoapUI,Fiddler或Swagger等第三方工具来执行W ...
- Springcloud Gateway 路由管理
Spring Cloud Gateway 是 Spring Cloud 的一个全新项目,该项目是基于 Spring 5.0,Spring Boot 2.0 和 Project Reactor 等技术开 ...
- 微服务容错限流Hystrix入门
为什么需要容错限流 复杂分布式系统通常有很多依赖,如果一个应用不能对来自依赖 故障进行隔离,那么应用本身就处在被拖垮的风险中.在一个高流量的网站中,某个单一后端一旦发生延迟,将会在数秒内导致 所有应用 ...
- Android 启动优化
对与Android的项目来说,app的启动速度是非常重要的.因为用户打开你的app给别人的第一体验就是打开软件的速度.但是app的启动速度是比较难以缩短的,因为一般来说开发者在app的启动入口都会创建 ...