本文主要以一个简单的android应用程序的开发与发布过程对flutter进行一个比较基础的介绍。很多过程其实就是按照官方文档做的,只是中间会穿插一些遇到的问题以及解决方案。
1. 安装flutter
以macOS为例。
1.1 国内的镜像配置
flutter的sdk也好,使用过程中也好,都需要从远程下载,所以我们配置国内的源来加速这些过程。
1.1.1 配置源
根据使用的shell不同,编辑不同的rc文件即可,我使用的是zsh,所以编辑~/.zshrc文件,增加一个源的配置。下面给出国内的一些源,根据自己的速度测试情况来使用。不管选择哪个镜像,只需要把对应的代码加入到rc文件中即可。
Flutter 社区
社区主镜像,采用多种方式同步 Flutter 开发者资源(推荐)。
export PUB_HOSTED_URL=https://pub.flutter-io.cn
export FLUTTER_STORAGE_BASE_URL=https://storage.flutter-io.cn
上海交大 Linux 用户组
使用反向代理方式建立的 Flutter 镜像,数据与站源实时同步。 Pub API 返回值未做处理,可能造成无法访问的情况。
export PUB_HOSTED_URL=https://dart-pub.mirrors.sjtug.sjtu.edu.cn
export FLUTTER_STORAGE_BASE_URL=https://mirrors.sjtug.sjtu.edu.cn
清华大学 TUNA 协会
定时与 Flutter 社区 Storage 镜像同步,Pub API 采取定时主动抓取策略,镜像配置了完善的失败回源策略(推荐)。
export PUB_HOSTED_URL=https://mirrors.tuna.tsinghua.edu.cn/dart-pub
export FLUTTER_STORAGE_BASE_URL=https://mirrors.tuna.tsinghua.edu.cn/flutter
CNNIC
基于 TUNA 协会的镜像服务,数据策略与 TUNA 一致,通过非教育网的域名访问。
export PUB_HOSTED_URL=http://mirrors.cnnic.cn/dart-pub
export FLUTTER_STORAGE_BASE_URL=http://mirrors.cnnic.cn/flutter
腾讯云开源镜像站
定时(每天凌晨)与 TUNA 协会镜像同步,数据有延迟,访问速度有待反馈。
export PUB_HOSTED_URL=https://mirrors.cloud.tencent.com/dart-pub
export FLUTTER_STORAGE_BASE_URL=https://mirrors.cloud.tencent.com/flutter
1.1.2 下载sdk
从国内下载sdk,只需要从上述镜像中选择一个,然后访问FLUTTER_STORAGE_BASE_URL对应的地址加/flutter_infra/releases/目录去下载即可。
编写本文时,使用的版本是flutter_macos_v1.12.13+hotfix.5-stable, 已经上传到百度云了: https://pan.baidu.com/s/1jwEjfzJAaUUrFVHPFZOkog
1.2 解压并设置环境变量
我的sdk下载位置是 ~/Downloads/flutter_macos_v1.12.13+hotfix.5-stable.zip
,sdk准备解压到的位置是 ~/codes/private/flutter-projects/sdks
, 所以在命令行执行命令 :
cd ~/codes/private/flutter-projects/sdks
unzip ~/Downloads/flutter_macos_v1.12.13+hotfix.5-stable.zip
命令完成后,我们在rc文件里增加flutter相关的命令到环境变量:
export PATH=$PATH:/Users/z/codes/private/flutter-projects/sdks/flutter/bin/
保存rc文件后,执行source ~/.zshrc
使环境变量对当前终端生效。
1.3 运行flutter doctor检查环境是否正常
在命令行执行flutter doctor
命令,如果正常的话,会有类似输出:
╔════════════════════════════════════════════════════════════════════════════╗
║ Welcome to Flutter! - https://flutter.dev ║
║ ║
║ The Flutter tool uses Google Analytics to anonymously report feature usage ║
║ statistics and basic crash reports. This data is used to help improve ║
║ Flutter tools over time. ║
║ ║
║ Flutter tool analytics are not sent on the very first run. To disable ║
║ reporting, type 'flutter config --no-analytics'. To display the current ║
║ setting, type 'flutter config'. If you opt out of analytics, an opt-out ║
║ event will be sent, and then no further information will be sent by the ║
║ Flutter tool. ║
║ ║
║ By downloading the Flutter SDK, you agree to the Google Terms of Service. ║
║ Note: The Google Privacy Policy describes how data is handled in this ║
║ service. ║
║ ║
║ Moreover, Flutter includes the Dart SDK, which may send usage metrics and ║
║ crash reports to Google. ║
║ ║
║ Read about data we send with crash reports: ║
║ https://github.com/flutter/flutter/wiki/Flutter-CLI-crash-reporting ║
║ ║
║ See Google's privacy policy: ║
║ https://www.google.com/intl/en/policies/privacy/ ║
╚════════════════════════════════════════════════════════════════════════════╝
Doctor summary (to see all details, run flutter doctor -v):
[✓] Flutter (Channel stable, v1.12.13+hotfix.5, on Mac OS X 10.15.2 19C57, locale zh-Hans-CN)
[✗] Android toolchain - develop for Android devices
✗ Unable to locate Android SDK.
Install Android Studio from: https://developer.android.com/studio/index.html
On first launch it will assist you in installing the Android SDK components.
(or visit https://flutter.dev/setup/#android-setup for detailed instructions).
If the Android SDK has been installed to a custom location, set ANDROID_HOME to that location.
You may also want to add it to your PATH environment variable.
[✗] Xcode - develop for iOS and macOS
✗ Xcode installation is incomplete; a full installation is necessary for iOS development.
Download at: https://developer.apple.com/xcode/download/
Or install Xcode via the App Store.
Once installed, run:
sudo xcode-select --switch /Applications/Xcode.app/Contents/Developer
sudo xcodebuild -runFirstLaunch
✗ CocoaPods not installed.
CocoaPods is used to retrieve the iOS and macOS platform side's plugin code that responds to your plugin usage on the Dart side.
Without CocoaPods, plugins will not work on iOS or macOS.
For more info, see https://flutter.dev/platform-plugins
To install:
sudo gem install cocoapods
[!] Android Studio (not installed)
[!] VS Code (version 1.41.1)
✗ Flutter extension not installed; install from
https://marketplace.visualstudio.com/items?itemName=Dart-Code.flutter
[!] Connected device
! No devices available
! Doctor found issues in 5 categories.
里面的问题我们慢慢解决,只要看到类似输出,我们前面的步骤就算完成了。
1.4 安装android开发环境
flutter需要完整的Android Studio开发环境以提供全部的依赖支持。
1.4.1 安装android studio
a. 下载并安装Android Studio
b. 启动Android Studio, 然后通过 ‘Android Studio Setup Wizard’安装最新的 Android SDK, Android SDK Platform-Tools, 以及 Android SDK Build-Tools, 他们都是 Flutter开发 Android所需要的。
c. 打开avd编辑器,创建一个虚拟设备,如果不知道步骤,可以参考如下内容:
- Enable VM acceleration on your machine.
- Launch Android Studio > Tools > Android > AVD Manager and select Create Virtual Device. (The Android submenu is only present when inside an Android project.)
- Choose a device definition and select Next.
- Select one or more system images for the Android versions you want to > emulate, and select Next. An x86 or x86_64 image is recommended.
- Under Emulated Performance, select Hardware - GLES 2.0 to enable hardware acceleration.
- Verify the AVD configuration is correct, and select Finish.
For details on the above steps, see Managing AVDs.- In Android Virtual Device Manager, click Run in the toolbar. The emulator starts up and displays the default canvas for your selected OS version and device.
1.5 安装代码开发工具
1.5.1 VS Code
下载vscode,然后搜索flutter以及dart的扩展进行安装即可,安装后可能需要重启vscode。
安装后,cmd+shift+p
,然后输入Run Flutter Doctor
验证安装是否正确。如果vscode找不到sdk,那么会弹出错误,选择手动指定我们之前下载的flutter sdk的路径即可。
2. 编写代码
2.1 创建工程
在vscode里面,cmd+shift+p
,然后输入Flutter New Project
,然后根据提示输入名称创建一个project,创建成功后,会直接打开main.dart。
2.2 启动工程
保持编辑器打开main.dart的状态,切换到debug页签点击debug flutter app即可启动调试。
如果卡在Running gradle assembleDebug
,那应该是因为gradle源的问题,我们可以修改到阿里云的源。
a. 修改项目中android/build.gradle
文件,把两个repository配置修改一下
buildscript {
ext.kotlin_version = '1.3.50'
repositories {
// google()
// jcenter()
maven { url 'https://maven.aliyun.com/repository/google' }
maven { url 'https://maven.aliyun.com/repository/jcenter' }
maven { url 'http://maven.aliyun.com/nexus/content/groups/public' }
}
dependencies {
classpath 'com.android.tools.build:gradle:3.5.0'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
}
}
allprojects {
repositories {
// google()
// jcenter()
maven { url 'https://maven.aliyun.com/repository/google' }
maven { url 'https://maven.aliyun.com/repository/jcenter' }
maven { url 'http://maven.aliyun.com/nexus/content/groups/public' }
}
}
rootProject.buildDir = '../build'
subprojects {
project.buildDir = "${rootProject.buildDir}/${project.name}"
}
subprojects {
project.evaluationDependsOn(':app')
}
task clean(type: Delete) {
delete rootProject.buildDir
}
b. 修改Flutter的配置文件, 该文件在Flutter安装目录/packages/flutter_tools/gradle/flutter.gradle
buildscript {
repositories {
//修改的地方
//google()
//jcenter()
maven { url 'https://maven.aliyun.com/repository/google' }
maven { url 'https://maven.aliyun.com/repository/jcenter' }
maven { url 'http://maven.aliyun.com/nexus/content/groups/public' }
}
dependencies {
classpath 'com.android.tools.build:gradle:3.2.1'
}
}
我在启动的时候,还遇到了一个奇怪的问题,一直卡在Installing build/app/outputs/apk/app.apk...
,关掉调试工具,在控制台输入flutter run --verbose
后,成功运行了app,之后再退出这个命令,回到vscode中打开调试,就正常了。
2.3 hot-reload
修改main.dart,把文字改成你已经点击了这么多次按钮
,保存文件,更新直接会同步到设备上:
2.4 编写一个入门程序
2.4.1 编写一个hello world
替换main.dart为一下内容:
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'hello world',
home: Scaffold(
appBar: AppBar(
title: Text('标题'),
),
body: Center(
child: Text('Hello World'),
),
),
);
}
}
如果服务还开着的话,模拟器应该直接会变成如下界面
2.4.2 添加依赖的使用
依赖的添加是在pubspec.yaml
的dependencies
里面编辑的,我们打开刚才项目的pubspec.yaml
增加一行对english_words的依赖。 更多的依赖可以到dart的依赖库里面找 https://pub.dev/
如果服务还在运行的话,应该会发现控制台自动执行了
flutter pub get
来安装我们最新声明的依赖。如果没有,那么就手动执行这个命令即可。
然后我们修改main.dart如下:
import 'package:flutter/material.dart';
import 'package:english_words/english_words.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
final wordPair = WordPair.random();
return MaterialApp(
title: 'hello world',
home: Scaffold(
appBar: AppBar(
title: Text('标题'),
),
body: Center(
child: Text(wordPair.asPascalCase),
),
),
);
}
}
如果一切正常的话,应该就可以看到如下的一个随机名字的输出了:
2.4.3 一个名字列表的app
我们写一个稍微复杂一点的app:写一个列表,列表可以无限滚动,边滚动,边加载新的随机字符串,同时列表每个元素中间都用分割线分开。
完整代码如下:
import 'package:flutter/material.dart';
import 'package:english_words/english_words.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Startup Name Generator',
home: RandomWords(),
);
}
}
class RandomWords extends StatefulWidget {
@override
RandomWordsState createState() => RandomWordsState();
}
class RandomWordsState extends State<RandomWords> {
final _suggestions = <WordPair>[]; /* 存放内容的列表 */
final _biggerFont = const TextStyle(fontSize: 18.0);
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Startup Name Generator'),
),
body: _buildSuggestions(),
);
}
Widget _buildSuggestions() {
return ListView.builder(
// itemCount: 20, /* 如果是有限列表,那么通过指定itemCount即可设置列表元素数量。 */
padding: const EdgeInsets.all(2.0),
itemBuilder: /*itemBuilder是渲染列表元素的方法。 ListView在滚动的时候或者初始化的时候,会自动计算需要获取哪些位置的数据,然后通过调用这个方法来获取内容*/
(context, i) {
if (i.isOdd) return Divider(); /*如果是奇数,就渲染一个分割线*/
final index = i ~/ 2; /*~/运算是除去2之后的整数部分*/
if (index >= _suggestions.length) {
_suggestions.addAll(generateWordPairs().take(10)); /*如果现在要取的列表的数据下标已经超过了已有元素的个数,就再往list里面增加10个数据*/
}
return _buildRow(_suggestions[index]); /* 渲染一行数据 */
});
}
Widget _buildRow(WordPair pair) {
return ListTile(
title: Text(
pair.asPascalCase,
style: _biggerFont,
),
);
}
}
细节在代码里面都注释了,这里再解释一下整体的思路:
我们修改myapp的build方法,直接渲染RandomWords组件,然后RandomWords组件的build方法里面会渲染Scaffold组件,然后在body中调用_buildSuggestions方法来渲染ListView。RandomWordsState里面使用_suggestions列表存放数据内容,使用_biggerFont存放字体样式。_buildSuggestions方法里面编写了ListView的渲染逻辑(如果是奇数就渲染分割线,如果是偶数就渲染数据元素,数据元素的渲染由_buildRow方法提供。 如果发现渲染元素的时候,元素不够用,那么就加载十个元素到_suggestions列表中,然后把对应位置的数据进行渲染)
3 构建程序
3.1 检查 App Manifest
查看默认应用程序清单文件(位于<app dir>/android/app/src/main/
中的AndroidManifest.xml
文件),并验证这些值是否正确,特别是:
application
: 编辑application
标签, 这是应用的名称。uses-permission
: 如果您的应用程序代码不需要Internet访问,请删除android.permission.INTERNET
权限。标准模板包含此标记是为了启用Flutter工具和正在运行的应用程序之间的通信。
3.2 查看构建配置
查看”build.gradle”,它位于<app dir>/android/app/
,验证这些值是否正确,尤其是:
-
defaultConfig
:
3.3 添加启动图标
当一个新的Flutter应用程序被创建时,它有一个默认的启动器图标。要自定义此图标:
查看Android启动图标 设计指南,然后创建图标。
在
<app dir>/android/app/src/main/res/
目录中,将图标文件放入使用配置限定符命名的文件夹中。默认mipmap-
文件夹演示正确的命名约定。在
AndroidManifest.xml
中,将application
标记的android:icon
属性更新为引用上一步中的图标(例如<application android:icon="@mipmap/ic_launcher" ...
)。要验证图标是否已被替换,请运行您的应用程序并检查应用图标
3.4 app签名
3.4.1 创建 keystore
如果您有现有keystore,请跳至下一步。如果没有,请通过在运行以下命令来创建一个: keytool -genkey -v -keystore ~/key.jks -keyalg RSA -keysize 2048 -validity 10000 -alias key
注意:保持文件私密; 不要将它加入到公共源代码控制中。
注意: keytool
可能不在你的系统路径中。它是Java JDK的一部分,它是作为Android Studio的一部分安装的。有关具体路径,请百度。
3.4.2 引用应用程序中的keystore
创建一个名为<app dir>/android/key.properties
的文件,其中包含对密钥库的引用:
storePassword=<前面创建秘钥时对应的store密码>
keyPassword=<前面创建秘钥时对应的key密码>
keyAlias=key
storeFile=<jks文件的路径,例如/Users/<user name>/key.jks>
注意: 保持文件私密; 不要将它加入公共源代码控制中
3.4.3 在gradle中配置签名
通过编辑<app dir>/android/app/build.gradle
文件为您的应用配置签名
-
替换:
android {
为:
def keystorePropertiesFile = rootProject.file("key.properties") def keystoreProperties = new Properties() keystoreProperties.load(new FileInputStream(keystorePropertiesFile)) android {
-
替换:
buildTypes { release { // TODO: Add your own signing config for the release build. // Signing with the debug keys for now, so `flutter run --release` works. signingConfig signingConfigs.debug } }
为:
signingConfigs { release { keyAlias keystoreProperties['keyAlias'] keyPassword keystoreProperties['keyPassword'] storeFile file(keystoreProperties['storeFile']) storePassword keystoreProperties['storePassword'] } } buildTypes { release { signingConfig signingConfigs.release } }
现在,您的应用的release版本将自动进行签名。
3.5 开启混淆
默认情况下 flutter 不会开启 Android 的混淆。
如果使用了第三方 Java 或 Android 库,也许你想减小 apk 文件的大小或者防止代码被逆向破解。
3.5.1 配置混淆
创建 /android/app/proguard-rules.pro
文件,并添加以下规则:
#Flutter Wrapper
-keep class io.flutter.app.** { *; }
-keep class io.flutter.plugin.** { *; }
-keep class io.flutter.util.** { *; }
-keep class io.flutter.view.** { *; }
-keep class io.flutter.** { *; }
-keep class io.flutter.plugins.** { *; }
上述配置只混淆了 Flutter 引擎库,任何其他库(比如 Firebase)需要添加与之对应的规则。
3.5.2 开启混淆/压缩
打开 /android/app/build.gradle
文件,定位到 buildTypes
块。
在 release
配置中将 minifyEnabled
和 useProguard
设为 true
,再将混淆文件指向上一步创建的文件。
android {
...
buildTypes {
release {
signingConfig signingConfigs.release
minifyEnabled true
useProguard true
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
}
3.6 解决一些问题
3.6.1 mac上的权限问题
下一步执行构建的时候,可能会报一些命令是从网上下载的,是否要删除之类的提示,这个时候我们需要通过finder定位到具体报错的文件位置,然后按住ctrl再点击文件,就会出现运行选项,点击运行后,这个文件后面就不会报错了。
3.6.2 关于android包名的问题
我们前面创建app的时候,vscode默认创建的android包名为com.example.xxxxx,如果需要修改的话,要全局搜索这个包名然后替换,有非常非常多的地方需要替换,如果替换错误,可能会导致app无法打包或者无法正确安装、运行。
3.7 构建一个发布版(release)APK
本节介绍如何构建发布版(release)APK。如果您完成了前一节中的签名步骤,则会对APK进行签名。
使用命令行:
-
cd <app dir>
(<app dir>
为您的工程目录). - 运行
flutter build apk
(flutter build
默认会包含--release
选项).
打包好的发布APK位于<app dir>/build/app/outputs/apk/app-release.apk
。
3.8 在设备上安装发行版APK
按照以下步骤在已连接的Android设备上安装上一步中构建的APK
使用命令行:
- 用USB您的Android设备连接到您的电脑
-
cd <app dir>
. - 运行
flutter install
.
3.9 将APK发布到Google Play商店
将应用的release版发布到Google Play商店的详细说明,请参阅 Google Play publishing documentation. (国内不存在的,但你可以发布到国内的各种应用商店)