如果你现在正坐在一台 mac 前面,准备创建自己的第一个 Flutter 项目,这篇内容应该能帮你少走不少弯路。我自己就是在这个环境里从零开始,把 SDK 安装、环境变量、模拟器、依赖管理这些问题一个一个踩过来的,中间报错报到自己都开始怀疑人生。但后来把这些坑整理清楚了回头看,发现大部分问题都是套路,而且几乎每个新人都会在同一批地方跌倒。
这篇不是把官方文档原样念一遍,而是专门写“在 mac 上创建 Flutter 项目”这件事本身:环境怎么装、命令怎么敲、第一次运行到模拟器上会遇到哪些问题,以及这些问题的排查逻辑。适合刚接触 Flutter 的新手,也适合那种装完环境之后发现第一步始终迈不出去的人。
1. 装环境前,先把这三个问题想明白
1.1 Flutter 项目依赖的“底料”到底有哪些
先搞清楚一件事:Flutter 本身是一个跨端 UI 框架,界面逻辑用 Dart 语言来写,编译之后可以运行在 iOS、Android、Web、桌面等多个平台。在 mac 上做 Flutter 开发有一个独特的优势——同一台电脑上,你可以同时跑 iOS 和 Android 两端的模拟器,Windows 用户想跑 iOS 是完全没有办法的。
但 Flutter 并不是“装完 SDK 就能跑”,它必须依赖系统里的原生工具链。在 mac 上,这个链条大致是这样:
- iOS 端:需要 Xcode(包含编译器和模拟器)、iOS 模拟器运行时,以及一个叫 CocoaPods 的工具。CocoaPods 是 iOS 生态里的依赖管理器,Flutter 项目只要引入了带原生插件的包,iOS 构建就会调用它。
- Android 端:需要 Android 官方 IDE 或命令行工具、Android SDK Platform,以及模拟器镜像。
打个比方:Flutter SDK 是菜谱和锅,原生工具链是灶台和燃气。菜谱再全,没有灶台你依然开不了火。很多人卡在第一步,不是 Flutter 出了问题,而是前置的“灶台”没准备好。
1.2 下载 Flutter SDK:压缩包还是包管理器?
在 mac 上安装 Flutter SDK,主流方法有两种:直接下载官方压缩包解压,或者通过 mac 上的包管理器安装。我自己强烈推荐用压缩包的方式,原因有三个:
- 安装路径完全可控。你知道 Flutter 解压在哪,出问题可以直接顺着路径去查。
- 不会自动升级。包管理器安装的版本可能跟着仓库更新走,对于新手来说,版本突变意味着环境不稳定。
- 后期切换版本容易,删掉旧目录换新目录就行,不影响其他东西。
解压之后的目录,建议放在用户目录下面,比如~/development/flutter。尽量不要往系统级目录放开发工具,一方面写权限容易出问题,另一方面系统升级时还可能出现冲突。
下载完成后,先看一眼目录结构,确认flutter/bin/flutter这个文件确实存在再继续。很多人下载完解压后直接敲flutter发现没有这个命令,顺着原因查下去,多半是路径看错了,或者根本没解压对层级。
1.3 环境变量配置:为什么 flutter 命令不生效
在 mac 上,终端默认使用的是 zsh,对应的配置文件是~/.zshrc。所谓“配环境变量”,本质上是告诉终端:当你输入flutter的时候,要去flutter/bin这个目录里找到一个同名可执行文件。
打开~/.zshrc,在末尾加一行:
export PATH="$PATH:$HOME/development/flutter/bin"然后执行:
source ~/.zshrc接着用flutter --version验证是否生效。
这里面最常见的坑有两个。第一个,是把 PATH 写成了文件本身,比如写成了/Users/xxx/development/flutter/bin/flutter。这是错的,PATH 里应该写“包含可执行文件的目录”,不是可执行文件本身。第二个,是改完配置之后忘了执行source,或者直接新开了一个终端窗口,发现还是找不到命令。新开窗口本身会加载 zshrc,但如果你是在同一个终端里改的,就需要主动 source 一次。
提示:如果
flutter已经生效,先跑一下flutter doctor。这个命令会检查环境缺什么,缺 Xcode 组件、Android SDK、许可证之类的问题,它都会列出来。后面遇到的大部分环境问题,都能从这份报告里找到线索。
2. 创建项目:flutter create 的正确玩法
2.1 命令格式与命名规则
环境没问题之后,找一个干净的目录,执行创建命令:
flutter create first_flutter_app这时候有几个命名规范要特别注意:
- 项目名只能使用小写字母、数字和下划线,不能以数字开头。
- 不能使用连字符,也就是中划线
-。 - 建议全部使用下划线风格,也就是常见的 snake_case。
原因很简单:项目名会被同时用作 Android 的包名、iOS 的 bundle identifier、以及 Dart 的包名。如果名字里带了连字符,Dart 标识符直接不合法,生成出来的代码会在编译阶段报错。这种错误对新手来说非常迷惑,因为你明明没有写错任何代码。
如果你希望生成的包名带上组织标识,可以这样:
flutter create --org com.example --project-name first_flutter_app .注意最后有一个点,表示“在当前目录生成项目”。这种写法适合你已经建好了目录,且目录名可能与项目名不一致的情况。项目名是给代码用的,目录名只是你存放的位置,两者没必要强行一致。
2.2 生成出来的目录结构怎么读
项目创建完成后,会得到一堆目录和文件。新手看到这些很容易慌,其实大多数内容在第一阶段根本不用碰。先认识几个关键的:
| 目录/文件 | 作用 |
|---|---|
lib/main.dart | Dart 代码入口,默认是一个计数器 Demo |
pubspec.yaml | 项目配置和依赖清单 |
android/ | Android 原生工程目录 |
ios/ | iOS 原生工程目录 |
test/ | 测试代码,默认有一个 widget_test.dart |
.dart_tool/ | 构建缓存目录,不要手动修改 |
我特别想强调一句:android/和ios/是“受控目录”。如果不是很清楚自己在做什么,不要手动去改里面的配置文件。因为 Flutter 在版本升级时可能会重新生成部分文件,你手动改过,后面就会冒出一堆莫名其妙的冲突。
lib/main.dart是项目的主入口。默认代码是一个 StatefulWidget,界面上有一个计数数字和一个浮动按钮,点击一次数字加一。你可以不用完全读懂每一行,但至少要知道:void main()是入口函数,runApp()用来启动根组件。
2.3 pubspec.yaml 里藏着项目依赖
用文本编辑器打开pubspec.yaml,会看到一段依赖声明:
dependencies: flutter: sdk: flutter cupertino_icons: ^1.0.2版本号前面的^表示允许兼容性更新。比如^1.0.2的意思是允许自动升级到 1.x 系列的最新版本,但不会跳到 2.0。这个机制叫做语义化版本控制,它能让你既跟上小版本修复,又不会因为主版本变化导致接口不兼容。
cupertino_icons是 iOS 风格图标库,默认带进来的,暂时不管它就行。以后你要装第三方包,比如网络请求、状态管理、图片加载之类的库,就在dependencies:下面加一行,然后执行:
flutter pub get这个命令会根据 yaml 文件里的声明下载依赖包,并生成一个锁文件。依赖装没装上,看这个命令的输出就能确认。
3. 把项目跑到模拟器上的完整实操
3.1 先跑 iOS 还是 Android?
第一次跑项目,我的建议非常明确:在 mac 上优先跑 iOS 模拟器,而不是 Android。
原因是 iOS 模拟器基本开箱即用,不需要额外下载几百兆的系统镜像;Android 那边则先要安装 IDE、下载 SDK Platform、创建虚拟设备,光是这几个步骤就够新手绕一阵子了。第一次跑通的首要目标,是尽量减少变量,环境能少装一件就少装一件。
Android 后面再补不迟,反正 Flutter 项目本身是跨端的,代码不用改,只是换个设备跑。
3.2 iOS 模拟器运行全流程
第一步,启动 iOS 模拟器,在终端输入:
open -a Simulator也可以直接从启动台打开“模拟器”软件,效果一样。
第二步,确认 Flutter 能识别到这个设备:
flutter devices看到类似 iOS Simulator 的设备条目,就可以继续了。如果这里找不到设备,八成是模拟器还没完全启动,或者 Flutter SDK 版本与系统版本存在兼容问题。先等模拟器完全加载完,再重新执行一次flutter devices,很多时候问题就解决了。
第三步,进入项目目录并运行:
flutter run第一次运行会非常慢,因为要完整编译原生工程,几分钟到十几分钟都有可能,取决于机器性能。看到控制台出现运行日志,模拟器里出现计数器界面,第一个 Flutter 项目就跑通了。
3.3 热重载:为什么按 r 一下代码就变了
flutter run运行中,终端会进入交互模式,几个快捷键是新手一定要记住的:
- 小写
r:热重载(Hot Reload),把最新代码推送到正在运行的 App 里,同时保留当前页面状态。 - 大写
R:热重启(Hot Restart),重新执行整个 App,状态重置,但不需要重新安装。 - 小写
q:退出运行。
热重载的原理是:Flutter 在调试模式下运行在一个 Dart 虚拟机里,虚拟机可以接收新的编译产物,不需要重新编译成原生 App、也不需要重新安装到模拟器。所以改完代码按一下 r,几乎秒级就能看到界面变化。
但也有些修改,热重载是无效的。比如改了main()入口函数、改了 iOS 或 Android 原生目录里的代码、改了pubspec.yaml里的依赖声明。遇到这些情况,直接按大写 R 做热重启,或者干脆退出重新flutter run,别在那干等。
3.4 Android 模拟器:绕不开的 SDK 和 AVD
如果还是想体验一把 Android 端,流程也并不复杂,只是准备时间长一点:
- 安装 Android 官方 IDE。
- 在 IDE 里安装 Android SDK Platform,一般选择当前稳定版本的 API 级别即可。
- 在终端执行
flutter doctor --android-licenses,按提示接受全部许可证。 - 在 IDE 里创建虚拟设备,选一个系统镜像。
- 启动模拟器之后,重新执行
flutter devices,再执行flutter run。
这里面最耗时的环节是下载系统镜像,动辄上百兆甚至更多。另外注意一点:模拟器镜像的架构要跟你的 Mac 芯片匹配。自研芯片架构的 Mac,优先选arm64-v8a镜像;老款 x86_64 架构的 Mac,选x86_64镜像。选错架构不是完全不能用,但性能和稳定性都会打折扣,碰到诡异问题也别奇怪。
还有一个很多人会遇到的情况:首次构建 Android 工程时,Gradle 和依赖包要现下载,这个过程可能比 iOS 首次编译还要慢。耐心等,只要网络状况正常,第一次成功之后后续构建就会快很多。
4. 新手必看:第一次跑 Flutter 一定会遇到的报错
4.1 一直卡在 Waiting for another flutter command
如果你在某个终端里强制中断过一次flutter命令,比如按了 Ctrl+C,之后你再执行flutter run,有可能报一句很长的英文错误,里面提到 “Waiting for another flutter command to release the startup lock”。
原因是:Flutter 在 SDK 目录下的bin/cache里留下了一个锁文件,上一次命令非正常退出,锁没释放,新命令就一直等。
解决方法是手动删掉锁文件。找到你的 Flutter SDK 解压目录,执行:
rm -rf <你的 Flutter SDK 路径>/bin/cache/lockfile然后重新运行flutter run就行。
提示:这一类开发工具尽量不要同时跑多个命令,比如一边
flutter run一边执行flutter upgrade,非常容易触发锁竞争。命令行工具虽然支持并行,但第一次接触时,一个命令干完再敲下一个是最稳妥的。
4.2 Xcode 首次使用要同意许可协议
flutter doctor报告里如果出现 Xcode 相关问题,或者运行 iOS 构建时提示需要同意许可协议,不要慌,这不是你的配置错误。这是因为 Xcode 装好以后,还需要一个明确的“同意”动作,法律上的确认步骤。
在终端执行:
sudo xcodebuild -license accept输入密码等它执行完,再重新跑一遍flutter doctor,这个问题就消失了。这里再多说一句,sudo不是万能的,涉及系统级写入才需要它,日常 Flutter 命令都不需要管理员权限。
4.3 CocoaPods 安装卡住或失败
iOS 构建过程中,一旦你的项目加入了带原生插件的依赖,Flutter 就会调用 CocoaPods 来管理原生依赖。也就是pod install这个步骤。第一次接触的新手,大概率在“安装 CocoaPods 本身”或者“pod install 下载依赖”这两个环节卡住。
安装 CocoaPods 比较省心的方法是用 mac 包管理器,终端执行:
brew install cocoapods装完验证一下:
pod --version能输出版本号,说明安装成功。如果 pod 相关命令提示 Ruby 环境错误,不要急着去动系统的 Ruby 环境,也不要拿着 sudo 到处乱试。直接把 CocoaPods 卸了重装一次,大部分环境类问题都能解决。
pod install 的持续卡顿,基本都是网络状况导致的,不是命令写错了。找一个网络顺畅的时间段再执行,或者换一个网络环境,会顺利很多。
4.4 设备明明开着,flutter devices 就是看不到
这种情况在 iOS 和 Android 模拟器上都可能出现。排查顺序是这样的:
- 看模拟器是不是真的完成启动了。启动动画还没走完之前,Flutter 经常扫描不到设备。
- 重新执行
flutter devices,设备列表不会自动实时刷新,命令行工具需要手动重新查询。 - 执行
flutter emulators,看看 Flutter 视角下有哪些模拟器可用。 - 以上都不行,重启模拟器,再重启终端,然后再执行
flutter devices。
Android 那边还有一个特殊点:模拟器从 IDE 启动后,可能需要多等一会儿,让系统服务和 ADB 完成连接。等一会儿再查询,比反复重启要有效得多。
4.5 芯片架构不匹配导致的编译问题
现在的 mac 分两种主流架构,旧款的 x86_64 和新款的自研芯片架构。如果你下载 Flutter SDK 时选错了架构,或者接手的项目里带了针对另一种架构编译的原生依赖,就可能看到架构相关的编译错误。
检查当前 Flutter SDK 的架构,可以执行:
file <你的 Flutter SDK 路径>/bin/flutter如果输出显示 Mach-O 通用二进制文件但包含的架构与你的机器不对应,直接重新下载对应架构的 Flutter SDK,替换掉旧目录即可。模拟器镜像也一样,记住:自研芯片选 arm64,旧款架构选 x86_64。
4.6 别一上来就用升级命令折腾版本
新手最容易犯的错误之一,就是项目刚跑通,马上执行flutter upgrade升级到最新版本,结果项目里的依赖、Gradle 版本全部跟着不匹配,报出一堆错误。这类环境问题排查起来非常消耗信心。
我的建议很直接:第一个项目阶段,不要主动升级 Flutter SDK。稳定版本一用到底。等所有流程都摸熟了,再考虑版本策略。Flutter 项目里的pubspec.yaml和原生工程配置,都对版本敏感,随意更新往往得不偿失。
5. 下定决心把第一个项目做出点东西
5.1 给自己定一个二十行代码就够的小目标
Demo 跑通了,接下来如果你不知道该学什么,我的建议是:不要急着啃复杂的架构设计,先改手头的 Demo。
把中间那个计数器数字,改成一句你想显示的话;把浮动按钮的颜色改掉;再加一个重置按钮,让数字回到零。控制在很小的改动范围内,让身体记住“改代码、按 r、看效果”这个循环。第一次真正建立信心,是把这个计数器改成“点击按钮后,在屏幕上显示当前时间”。这个需求涉及文本展示、按钮事件、状态更新和时间处理,覆盖了 Flutter 开发最常用的一组基础能力,又不会复杂到让人放弃。
5.2 学会读 Flutter 抛出来的错误
运行阶段满屏红字是常态,新手不要慌。先说一个关键技巧:错误信息要读最后几行,不是最前面那几行。最前面的通常是一长串调用栈,最后一段才是错误原因和建议动作。
比如编辑器里报Undefined class或者Target of URI doesn't exist,翻译过来是“某个类找不到”或“某个导入路径不对”。对应检查两处:文件开头有没有对应的 import 语句,以及改完pubspec.yaml之后有没有执行flutter pub get。这两个问题占了新手报错的很大比例。
5.3 把这个项目当成一套开发菜单
这篇文章的核心是“在 mac 上创建第一个 Flutter 项目”,但你真正拿到手的,是一整套可以反复使用的开发流程。项目本身是个 Demo,流程却是通用的。
- 想上架 iOS,后续去研究签名和发布配置。
- 想上架 Android,后续去关注打包体积和权限声明。
- 想布局多端,后续去研究响应式 UI。
这些都是后话。当前最该做的,是把这个最小可运行的 App 牢牢握在手里,把环境问题清理干净,把“创建项目-运行-改代码-看效果”这个循环变成肌肉记忆。
5.4 两个提高效率的小习惯
最后分享两个我直到现在都在用的小习惯。
第一个,flutter run最好指定设备。尤其是在 iOS 和 Android 模拟器同时开着的时候,不指定设备,终端会弹出一个设备列表让你手动选择,很打断思路。设备比较多时,我都是先执行flutter devices拿到设备 ID,然后直接:
flutter run -d <设备ID>第二个,遇到报错先定位再搜索。把错误信息的最后几行复制下来,再带着自己的操作上下文去搜,比直接搜索整段报错效率高很多。这个方法在我后来做正式项目时一样管用,新项目第一次跑通之后,再回头处理版本升级、多端适配这些事,心态就会完全不一样。