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 ...
随机推荐
- 如何解决在ie下,Echarts多次使用setOption更改数据时,数据错乱问题
一.问题描述 根据用户的操作,通过Ajax请求,获取某段时间内的某数据趋势折线图数据.用户切换数据项或更改时间段时,ie中渲染的折线图包含了上一次获取的数据,导致数据错乱,如下图所示: 二.代码 数据 ...
- keepalived工作原理和配置说明
keepalived是什么 keepalived是集群管理中保证集群高可用的一个服务软件,其功能类似于heartbeat,用来防止单点故障. keepalived工作原理 keepalived是以VR ...
- 测试修改hosts文件py小工具
import sys,osparm_list=sys.argvHOST_PATH=r'C:\liuliang\flask_test\test\hosts'class HostFile(object): ...
- Unix中的I/O模型
本文所指的I/O均是网络I/O. 一. POSIX对同步.异步I/O的定义 我们先大致看看POSIX对同步.异步的定义,不用细究,重点看我标红的部分就行. 同步I/O会导致请求进程阻塞,直到I/O操作 ...
- JDK--box和unbox
目录 什么是装箱.拆箱 基本类型和包装类型 为什么会有基本类型? 为什么还要有包装类型 两者区别 两者互转 源码分析(JDK1.8版本) valueOf方法 1.Integer.Short.Byte. ...
- 【重学计算机】操作系统D1章:计算机操作系统概述
1. 计算机软硬件系统 冯诺伊曼结构 以运算单元为核心,控制流由指令流产生 程序和数据存储在主存中 主存是按地址访问,线性编址 指令由操作码和地址码组成 数据以二进制编码 其他:参考<重学计算机 ...
- 《k8s-1.13版本源码分析》-测试环境搭建(k8s-1.13版本单节点环境搭建)
本文原始地址(gitbook格式):https://farmer-hutao.github.io/k8s-source-code-analysis/prepare/debug-environment. ...
- 实现AutoMapper(1.0版本)
最近有个需求就是实体之间自动转换,网上肯定有很多现成的实现,不过还是自己写了一个,就当对java高级特性的一个熟悉的过程.这中间包含了泛型,反射,lamada表达式.对于想了解java高级特性的人来说 ...
- 如何在CentOS上创建Kubernetes集群
欢迎大家前往腾讯云+社区,获取更多腾讯海量技术实践干货哦~ 本文由编程男孩 发表于云+社区专栏 介绍 Kubernetes(常简称为K8s)是用于自动部署.扩展和管理容器化(containerized ...
- 面向对象OOP概念描述
面向对象三大特征:封装,继承,多态 封装就是把一个对象的属性私有化,同时提供一些可以被外界访问的属性的方法 继承就是在已经存在的类的定义作为基础,建立新的技术.新定义的类可以添加新的数据或功能,也可以 ...